import {
    HTML_ATTRIBUTE_BLOCKER_ID,
    HTML_ATTRIBUTE_BY,
    HTML_ATTRIBUTE_CONFIRM,
    HTML_ATTRIBUTE_COOKIE_IDS,
    HTML_ATTRIBUTE_DELEGATE_CLICK,
    HTML_ATTRIBUTE_UNBLOCKED_TRANSACTION_COMPLETE,
    OPT_IN_CONTENT_BLOCKER_ALL,
} from "@devowl-wp/headless-content-unblocker";

import { unblockSync } from "./unblockSync.js";

type BlockerDefinition = { id: string | number; services: number[]; rules: string[] };

type Options = {
    ref?: HTMLElement;
    /**
     * Allows to set custom attributes on the `ref`.
     */
    attributes?: Record<string, any>;
    /**
     * If `true`, it will use the same mechanism as the selector syntax function `:confirm()` uses.
     */
    confirm?: boolean;
    /**
     * If given, it will be called when the unblocking is complete or element is not blocked. This is useful
     * if you want to maintain the sync behavior as it can also be run for non-blocked elements outside of the
     * `Promise` when passing a `HTMLElement` (short circuit).
     */
    callback?: () => void;
} & Parameters<typeof unblockSync>[2];

/**
 * `window.consentApi.unblock`: Check if a given URL / string is blocked by a Content Blocker:
 *
 * - When a Content Blocker exists, the Promise is only resolved after given consent
 * - When no Content Blocker exists, the Promise is immediate resolved
 *
 * Example (ES5):
 * ```js
 * (window.consentApi && window.consentApi.unblock("player.vimeo.com") || Promise.resolve()).then(function() {
 *     console.log("Consent for Vimeo given, unlock content...!");
 * });
 * ```
 *
 * Example (ES6, TS):
 * ```js
 * (window.consentApi?.unblock("player.vimeo.com") || Promise.resolve()).then(() => {
 *     console.log("Consent for Vimeo given, unlock content...!");
 * });
 * ```
 *
 * **Since v5.0.4**: The API also supports passing an `HTMLElement` as the first argument. When the element is blocked, the
 * returned Promise is resolved immediately after unblocking:
 *
 * ```
 * window.consentApi.unblock(document.querySelector("iframe")).then(() => console.log("I got loaded!"));
 * ```
 *
 * @param url
 * @param ref Create a visual content blocker to a given HTML element
 */
function unblock(blocker: Array<BlockerDefinition>, urlOrElement: string | HTMLElement, options?: Options) {
    const {
        ref = document.createElement("div"),
        attributes = {},
        confirm,
        callback,
        ...restOptions
    } = options instanceof HTMLElement ? ({ ref: options } satisfies Options) : options || {};
    let calledCallback = false;

    if (callback) {
        const isUnblocked =
            typeof urlOrElement === "string"
                ? !unblockSync(blocker, urlOrElement, restOptions)
                : urlOrElement instanceof HTMLElement &&
                  (!urlOrElement.hasAttribute(HTML_ATTRIBUTE_BLOCKER_ID) ||
                      urlOrElement.hasAttribute(HTML_ATTRIBUTE_UNBLOCKED_TRANSACTION_COMPLETE));

        if (isUnblocked) {
            calledCallback = true;
            callback();
        }
    }

    const promise = new Promise<void>((resolve) => {
        if (urlOrElement instanceof HTMLElement) {
            if (urlOrElement.hasAttribute(HTML_ATTRIBUTE_BLOCKER_ID)) {
                // The element is a blocked element
                if (urlOrElement.hasAttribute(HTML_ATTRIBUTE_UNBLOCKED_TRANSACTION_COMPLETE)) {
                    // It is already unblocked
                    resolve();
                } else {
                    // The element is blocked but the unblocking transaction is not complete yet
                    urlOrElement.addEventListener(OPT_IN_CONTENT_BLOCKER_ALL, (() => {
                        resolve();
                    }) as any);
                }
            } else {
                // This element is not interesting for us as it is not blocked
                resolve();
            }
            return;
        }

        if (urlOrElement === undefined) {
            resolve();
            return;
        }

        if (confirm) {
            Object.assign(attributes, {
                [HTML_ATTRIBUTE_CONFIRM]: "{}",
                [HTML_ATTRIBUTE_DELEGATE_CLICK]: JSON.stringify({ selector: "self" }),
            });
        }

        // Find matching blocker
        const isPseudoRef = !ref.parentElement;
        const found = unblockSync(blocker, urlOrElement);

        if (found) {
            // Add attributes to DOM element so it can be consumed by `manipulateDom`
            ref.setAttribute(HTML_ATTRIBUTE_BY, "services");
            ref.setAttribute(HTML_ATTRIBUTE_COOKIE_IDS, found.services.join(","));
            ref.setAttribute(HTML_ATTRIBUTE_BLOCKER_ID, found.id.toString());

            for (const k in attributes) {
                const v = attributes[k];
                ref.setAttribute(k, typeof v === "object" ? JSON.stringify(v) : v);
            }

            ref.addEventListener(OPT_IN_CONTENT_BLOCKER_ALL, (() => {
                resolve();
            }) as any);

            // Append blocking element to HTML DOM if not already exists
            if (isPseudoRef) {
                document.body.appendChild(ref);
            }
        } else {
            resolve();
        }
    });

    return callback && !calledCallback ? promise.then(callback) : promise;
}

export { unblock };
