import { putHtmlCodeToDom } from "@devowl-wp/headless-content-unblocker";

import { getServiceSelection } from "../decision/getServiceSelection.js";
import { OPT_IN_EVENT } from "../events/optIn.js";

import type { GetServiceSelectionOptions } from "../decision/getServiceSelection.js";
import type { OptInEvent } from "../events/optIn.js";
import type { OptInAllEvent } from "../events/optInAll.js";
import type { Service } from "../types/service.js";

type ApplyOptInOptions = {
    skipOptIn?: (service: Service) => boolean;
    /**
     * Instead of using a `BroadcastChannel` which breaks the bfcache of browsers, we are using a mix of
     * the `storage` events in combination with this property to determine if a decision got made in another tab.
     *
     * @see https://app.clickup.com/t/86951yt9g
     */
    triggeredByOtherTab?: boolean;
};

/**
 * Opt-in to a set of services.
 */
async function applyOptIn(options: GetServiceSelectionOptions & ApplyOptInOptions) {
    const allOptInServices: OptInAllEvent["services"] = [];
    const { isManagerActive, iterateServices } = getServiceSelection(options);
    const { skipOptIn } = options;

    // Handle managers like GTM and MTM
    let isManagerOptOut = false;
    let dataLayer: any[] = undefined;
    const waitPromises: Promise<void>[] = [];

    // Handle opt-ins sequentially
    await iterateServices(async (group, service, isOptIn, { getDataLayer, serviceIsManager }) => {
        const { codeDynamics, codeOptIn, executeCodeOptInWhenNoTagManagerConsentIsGiven } = service;
        const isManager = isManagerActive && serviceIsManager;
        if (isOptIn) {
            // Check if the script should explicitly only be loaded if no consent is given to the manager
            const isOnlyWhenNoConsentToManager = isManagerActive && executeCodeOptInWhenNoTagManagerConsentIsGiven;
            const isSkipOptIn = typeof skipOptIn === "function" ? skipOptIn(service) : false;

            // If this is a manager, calculate consent data for the datalayer and push it
            /* onlypro:start */
            if (isManager) {
                const realCookieBannerConsents: Record<string, boolean> = {};
                const realCookieBannerOptInEvents: Record<string, boolean> = {};
                const realCookieBannerOptOutEvents: Record<string, boolean> = {};
                await iterateServices((group, service, isOptIn, { serviceIsManager }) => {
                    if (!serviceIsManager) {
                        const { tagManagerOptInEventName, tagManagerOptOutEventName, uniqueName } = service;
                        if (tagManagerOptInEventName) {
                            realCookieBannerOptInEvents[tagManagerOptInEventName] = isOptIn;
                        }
                        if (tagManagerOptOutEventName) {
                            realCookieBannerOptOutEvents[tagManagerOptOutEventName] = !isOptIn;
                        }
                        if (uniqueName) {
                            realCookieBannerConsents[uniqueName] = isOptIn;
                        }
                    }
                });
                Object.assign(realCookieBannerConsents, realCookieBannerOptInEvents);
                Object.assign(realCookieBannerConsents, realCookieBannerOptOutEvents);

                dataLayer = getDataLayer();
                dataLayer.push({
                    realCookieBannerConsents,
                    realCookieBannerOptInEvents,
                    realCookieBannerOptOutEvents,
                });
            }
            /* onlypro:end */

            if (!isOnlyWhenNoConsentToManager && !isSkipOptIn && codeOptIn) {
                waitPromises.push(putHtmlCodeToDom(codeOptIn, codeDynamics));
            }

            const summary = { group, service };
            document.dispatchEvent(
                new CustomEvent<OptInEvent>(OPT_IN_EVENT, {
                    detail: summary,
                }),
            );

            allOptInServices.push(summary);
        } else if (isManager) {
            /* onlypro:start */
            // It is a manager but opt-out
            isManagerOptOut = true;
            /* onlypro:end */
        }
    });

    return { isManagerOptOut, dataLayer, services: allOptInServices, ready: Promise.all(waitPromises) };
}

export { type ApplyOptInOptions, applyOptIn };
