import { HTML_ATTRIBUTE_BLOCKER_ID, HTML_ATTRIBUTE_DELEGATE_CLICK } from "./htmlAttributes.js";
import { OPT_IN_CONTENT_BLOCKER_ALL } from "../events/optInContentBlockerAll.js";
import { dispatchResizeEvent } from "../utils/dispatchResizeEvent.js";

import type { OptInContentBlockerAllEvent } from "../events/optInContentBlockerAll.js";

type DelegateClickElement = string | { selector: string; hide: boolean };

declare global {
    interface HTMLElement {
        consentDelegateClick: { element: HTMLElement; hide?: boolean };
    }
}

/**
 * Listen to clicked content blockers and automatically delegate the click
 * event to the underlying element. This is especially useful for overlays of videos
 * so the user does not need to click twice.
 */
function delegateClick(
    element: HTMLElement,
    {
        same,
        nextSibling,
        parentNextSibling,
    }: {
        same?: DelegateClickElement[];
        nextSibling?: DelegateClickElement[];
        parentNextSibling?: DelegateClickElement[];
    },
) {
    // Save found result
    let delegateClick: HTMLElement;
    let hide: boolean;

    const delegateClickAttribute = element.getAttribute(HTML_ATTRIBUTE_DELEGATE_CLICK);
    const nextElementSibling = element.nextElementSibling as HTMLElement;
    const parentElement = element.parentElement as HTMLElement;
    const parentElementNextSibling = parentElement?.nextElementSibling as HTMLElement;

    loop1: for (const [node, delegateClickElements] of [
        [
            // Same element (= the blocked element)
            element,
            [...(same || []), ...(delegateClickAttribute ? [JSON.parse(delegateClickAttribute)] : [])],
        ],
        [
            // Next sibling element to the blocked content
            nextElementSibling,
            nextSibling,
        ],
        [
            // Next sibling element of parent of the blocked content
            parentElementNextSibling,
            parentNextSibling,
        ],
    ] as [HTMLElement, DelegateClickElement[]][]) {
        if (node && delegateClickElements) {
            for (const delegateClickElement of delegateClickElements) {
                const selector =
                    typeof delegateClickElement === "string" ? delegateClickElement : delegateClickElement.selector;

                if (typeof delegateClickElement !== "string") {
                    hide = delegateClickElement.hide || false;
                }

                if (
                    // Special case: force self element to be delegated (useful in conjunction with `delegateClick()` selector syntax function)
                    selector === "self" ||
                    // Usual selector match
                    node.matches(selector)
                ) {
                    delegateClick = node;
                    break loop1;
                }

                // Check for children selector
                const childrenSelected = node.querySelector(selector) as HTMLElement;
                if (childrenSelected) {
                    delegateClick = childrenSelected;
                    break loop1;
                }

                // Fallback to the "real" clicked element when using confirm() / hero dialog
                const { consentDelegateClick } = element;
                if (selector === "beforeConfirm" && consentDelegateClick) {
                    delegateClick = consentDelegateClick.element;
                    ({ hide } = consentDelegateClick);
                    break loop1;
                }
            }
        }
    }

    // We delegate the click to an element, let's check if it is also blocked and listen to unblock
    if (delegateClick) {
        const fn = () =>
            setTimeout(() => {
                delegateClick.click();

                if (hide) {
                    delegateClick.style.setProperty("display", "none", "important");
                }

                dispatchResizeEvent(element);
            }, /* Let's delay so we can start after `manipulateDom#customTriggers()` */ 100);
        if (delegateClick.hasAttribute(HTML_ATTRIBUTE_BLOCKER_ID)) {
            delegateClick.addEventListener(
                OPT_IN_CONTENT_BLOCKER_ALL,
                (({ detail: { load } }: CustomEvent<OptInContentBlockerAllEvent>) => {
                    load.then(fn);
                }) as any,
                { once: true },
            );
        } else {
            fn();
        }
    }

    return delegateClick;
}

export { delegateClick };
