import { ready } from "@devowl-wp/react-utils";

import { CookieConsentManager } from "../apply/manager.js";
import { getUserDecision } from "../decision/getUserDecision.js";
import { BANNER_PRE_DECISION_SHOW_EVENT } from "../events/bannerPredecisionShow.js";
import { BANNER_PRE_DECISION_SHOW_INTERACTIVE_EVENT } from "../events/bannerPredecisionShowInteractive.js";

import type { CookieConsentManagerOptions } from "../apply/manager.js";
import type { GetServiceSelectionOptions } from "../decision/getServiceSelection.js";
import type { Decision } from "../decision/getUserDecision.js";
import type { BannerPredecisionShowEvent } from "../events/bannerPredecisionShow.js";

type PreDecisionGatewayResult =
    /**
     * This can only happen for bots or custom bypasses; no consent is saved in Consent API.
     */
    | "all"
    /**
     * This can only happen for custom bypasses; no consent is saved in Consent API.
     */
    | "essentials"
    /**
     * This can only happen for "Do not track", consent is marked as DNT in Consent API.
     */
    | "dnt"
    /**
     * Current user selected decision is applied as cookie, nothing is saved in Consent API.
     * If no consent is available, it automatically falls back to `consent`.
     */
    | "consent"
    /**
     * Do not show banner dialog, simply do nothing.
     */
    | "nothing";

/**
 * This options are available to all predecision gateways.
 */
type PreDecisionGatewayOptions = Pick<
    CookieConsentManagerOptions,
    "decisionCookieName" | "revisionHash" | "supportsCookiesName" | "groups"
> & {
    getUserDecision: () => false | Decision;
};

type PreDecisionGateway<Arguments extends any[] = any[]> = (
    options: PreDecisionGatewayOptions,
    ...args: Arguments
) => Promise<PreDecisionGatewayResult | false>;

async function preDecisionGateway<Arguments extends any[] = any[]>(
    options: (PreDecisionGatewayOptions & Omit<GetServiceSelectionOptions, "type">) | CookieConsentManager,
    handler: {
        /**
         * If do not track is returned by a predecision gateway, you should save the state in your
         * API and call the `apply` function manually.
         */
        onIsDoNotTrack: (apply: () => Promise<void>) => void;
        /**
         * No predecision is set, so we should show a visual cookie banner.
         */
        onShowCookieBanner: () => void;
        /**
         * If `true`, the user consent is invalidated if the user has not made any explicit decision. This could be
         * useful for e.g. the banner-less mode: First visit a website and the banner-less mode does not show a banner, afterward
         * visit another page (e.g. a landing page) and show the cookie banner so the user is forced to give a consent
         * for e.g. Google Analytics.
         */
        isInvalidateImplicitUserConsent?: boolean;
        gateways: PreDecisionGateway<Arguments>[];
        args: Arguments;
    },
) {
    const { gateways, args, onIsDoNotTrack, onShowCookieBanner, isInvalidateImplicitUserConsent } = handler;
    let show = true;

    const useOptions = options instanceof CookieConsentManager ? options.getOptions() : options;
    const useOptionsForGateway = {
        ...useOptions,
        getUserDecision: () => {
            const decision = getUserDecision(useOptions.decisionCookieName);

            if (decision) {
                // Invalidate when revision changes ("Request new consent")
                if (decision.revision !== useOptions.revisionHash) {
                    return false;
                }

                // Custom invalidation
                if (isInvalidateImplicitUserConsent && decision.buttonClicked?.startsWith("implicit_")) {
                    return false;
                }
            }

            return decision;
        },
    };

    for (const gateway of gateways) {
        const result = await gateway(useOptionsForGateway, ...args);
        if (result !== false) {
            show = false;
            const applyWithOptions = (type: GetServiceSelectionOptions["type"]) =>
                import(/* webpackChunkName: "banner-lazy", webpackMode: "lazy-once" */ "../apply/apply.js").then(
                    ({ apply }) => apply({ type, ...useOptions }),
                );

            if (result === "all") {
                applyWithOptions("all");
            } else if (result === "essentials") {
                applyWithOptions("essentials");
            } else if (result === "dnt") {
                onIsDoNotTrack(() => applyWithOptions("essentials"));
            } else if (result === "consent") {
                // Apply services for the current user decision
                applyWithOptions("consent");
            }
            break;
        }
    }

    if (show) {
        onShowCookieBanner();

        document.dispatchEvent(new CustomEvent(BANNER_PRE_DECISION_SHOW_INTERACTIVE_EVENT));

        // Wait all events are initialized
        await ready();

        document.dispatchEvent(
            new CustomEvent<BannerPredecisionShowEvent>(BANNER_PRE_DECISION_SHOW_EVENT, {
                detail: {},
            }),
        );
    }
}

export { type PreDecisionGatewayResult, type PreDecisionGateway, type PreDecisionGatewayOptions, preDecisionGateway };
