import { ensureScriptWrapper } from "./ensureScriptWrapper.js";
import {
    HTML_ATTRIBUTE_BLOCKER_CONNECTED,
    HTML_ATTRIBUTE_CAPTURE_CLICK_PREFIX,
    HTML_ATTRIBUTE_CAPTURE_PREFIX,
    HTML_ATTRIBUTE_CAPTURE_SUFFIX,
    HTML_ATTRIBUTE_WAS_PREVIOUSLY_DISPLAY_STYLE,
} from "./htmlAttributes.js";
import { OPT_IN_CONTENT_BLOCKER_ALL } from "../events/optInContentBlockerAll.js";
import { dispatchResizeEvent } from "../utils/dispatchResizeEvent.js";
import { findVisualParent } from "../visual/findVisualParent.js";

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

let tagTransformationCounter = 0;

const HTML_TAG_TRANSFORMATION_COUNTER = "consent-tag-transformation-counter";

/**
 * Transform e. g. `consent-original-href` to `href` of a given node.
 */
function transformToOriginalAttribute({
    node,
    allowClickOverrides,
    onlyModifyAttributes,
    visualParentSelectors,
    overwriteAttributeValue,
    overwriteAttributeNameWhenMatches,
}: {
    node: HTMLElement;
    allowClickOverrides?: boolean;
    /**
     * This is useful for e.g. inline scripts and does only touch the passed node itself.
     * It skips modifying the style of the parent visual content blocker.
     */
    onlyModifyAttributes?: boolean;
    visualParentSelectors?: Parameters<typeof findVisualParent>[1];
    overwriteAttributeValue?: (
        value: string,
        originalAttribute: string,
        node: HTMLElement,
    ) => { value: string; attribute?: string };
    overwriteAttributeNameWhenMatches?: Array<{
        matches: string;
        node: string;
        attribute: string;
        to: string;
    }>;
}) {
    return new Promise<{
        /**
         * If you pass `allowClickOverrides=true` this boolean indicates if a click
         * override got performed for this element.
         */
        performedClick: boolean;
        /**
         * The node we worked within this transformation. When unblocking a custom defined element
         * with `customElements.define`, you will get a new node reference.
         */
        workWithNode: HTMLElement;
    }>((resolve) => {
        let performedClick = false;

        // Scripts need to be handled differently cause we want to use `postscribe` and should not be attached
        // to the DOM -> not be executed
        const tagName = node.tagName.toLowerCase();
        const isScript = tagName === "script";
        const isIframe = tagName === "iframe";
        let workWithNode = isScript && !onlyModifyAttributes ? (node.cloneNode(true) as HTMLElement) : node;

        // Do the transformation from "blocked" state to "unblocked"
        for (const attribute of workWithNode.getAttributeNames()) {
            if (
                attribute.startsWith(HTML_ATTRIBUTE_CAPTURE_PREFIX) &&
                attribute.endsWith(HTML_ATTRIBUTE_CAPTURE_SUFFIX)
            ) {
                let originalAttribute = attribute.substr(HTML_ATTRIBUTE_CAPTURE_PREFIX.length + 1);
                originalAttribute = originalAttribute.slice(0, (HTML_ATTRIBUTE_CAPTURE_SUFFIX.length + 1) * -1);

                // Check if `allowClickOverride` is given and then the `consent-click-original` attribute may be used, if given
                const overwriteClickAttribute = `${HTML_ATTRIBUTE_CAPTURE_CLICK_PREFIX}-${originalAttribute}-${HTML_ATTRIBUTE_CAPTURE_SUFFIX}`;
                const hasOverwriteClickAttribute =
                    workWithNode.hasAttribute(overwriteClickAttribute) && allowClickOverrides;
                let value = workWithNode.getAttribute(hasOverwriteClickAttribute ? overwriteClickAttribute : attribute);

                if (hasOverwriteClickAttribute) {
                    performedClick = true;
                }

                // Allow to override attribute name by selector
                if (overwriteAttributeNameWhenMatches && value) {
                    for (const {
                        matches,
                        node: nodeMatch,
                        attribute: expectedAttribute,
                        to,
                    } of overwriteAttributeNameWhenMatches) {
                        if (
                            originalAttribute === expectedAttribute &&
                            workWithNode.matches(nodeMatch) &&
                            workWithNode.matches(
                                matches.replace(
                                    "%s",
                                    `${nodeMatch}[${
                                        performedClick ? overwriteClickAttribute : attribute
                                    }="${value.trim().replace(/"/g, `\\"`)}"]`,
                                ),
                            )
                        ) {
                            originalAttribute = to;
                        }
                    }
                }

                // Allow to override attribute value and name by custom function
                if (overwriteAttributeValue) {
                    const { value: newValue, attribute: newAttribute } = overwriteAttributeValue(
                        value,
                        originalAttribute,
                        workWithNode,
                    );

                    originalAttribute = newAttribute || originalAttribute;
                    value = newValue;
                }

                // Finally, set the valid values
                if (isIframe && originalAttribute === "src") {
                    try {
                        // For iframes, we need to avoid that the browser adds an extra history item and
                        // the back button does no longer work as expected (e.g. in Firefox).
                        // See also: https://discuss.emberjs.com/t/change-iframe-src-without-adding-history-entry/18645/11
                        // and https://bugzilla.mozilla.org/show_bug.cgi?id=301307
                        // To resolve this, we set the location manually in the `iframe` before setting the `src´ attribute
                        // Attention: The above mechanism may not be applied to Firefox Mobile as this leads to an App-Intent dialog
                        // when the Vimeo app is installed and the user accepts the service (e.g. Cookie Banner > Accept all).
                        const { userAgent } = navigator;
                        const isFirefoxMobile = /(firefox.*mobile|mobile.*firefox)/i.test(userAgent ?? "");
                        if (!isFirefoxMobile) {
                            (workWithNode as HTMLIFrameElement).contentWindow.location.replace(value);
                        }
                    } catch (e) {
                        console.log(e);
                        // CORS error? Use standard `setAttribute` method
                    }
                }

                workWithNode.setAttribute(originalAttribute, value);
                workWithNode.removeAttribute(attribute);
                workWithNode.removeAttribute(overwriteClickAttribute);

                // Automatically delegate a `click` event
                if (
                    allowClickOverrides &&
                    ["a"].indexOf(tagName) > -1 &&
                    (["onclick"].indexOf(originalAttribute.toLowerCase()) > -1 ||
                        workWithNode.getAttribute("href")?.startsWith("#"))
                ) {
                    workWithNode.addEventListener(OPT_IN_CONTENT_BLOCKER_ALL, (async ({
                        detail: { unblockedNodes },
                    }: CustomEvent<OptInContentBlockerAllEvent>) =>
                        unblockedNodes.forEach(() => {
                            workWithNode.click();
                            dispatchResizeEvent(workWithNode);
                        })) as any);
                }
            }
        }

        // Are there any non-transformed clicked attributes? E.g. only add an attribute when it got clicked (without original attribute)
        for (const attribute of workWithNode.getAttributeNames()) {
            if (
                attribute.startsWith(HTML_ATTRIBUTE_CAPTURE_CLICK_PREFIX) &&
                attribute.endsWith(HTML_ATTRIBUTE_CAPTURE_SUFFIX)
            ) {
                const value = workWithNode.getAttribute(attribute);
                let originalAttribute = attribute.substr(HTML_ATTRIBUTE_CAPTURE_CLICK_PREFIX.length + 1);
                originalAttribute = originalAttribute.slice(0, (HTML_ATTRIBUTE_CAPTURE_SUFFIX.length + 1) * -1);

                // Finally, set the valid values
                if (allowClickOverrides) {
                    workWithNode.setAttribute(originalAttribute, value);
                    performedClick = true;
                }

                workWithNode.removeAttribute(attribute);
            }
        }

        const result = { performedClick, workWithNode: node };
        if (onlyModifyAttributes) {
            result.performedClick = false;
            resolve(result);
            return;
        }

        // Allow transition of custom element tags (web components).
        // See also https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-customized-builtin-example)
        if (tagName.startsWith("consent-") && customElements) {
            const originalTag = tagName.substring(8);
            workWithNode.outerHTML = workWithNode.outerHTML
                .replace(
                    /^<consent-[^\s]+/m,
                    `<${originalTag} ${HTML_TAG_TRANSFORMATION_COUNTER}="${tagTransformationCounter}"`,
                )
                .replace(/<\/consent-[^\s]+>$/m, `</${originalTag}>`);
            workWithNode = document.querySelector(`[${HTML_TAG_TRANSFORMATION_COUNTER}="${tagTransformationCounter}"]`);
            tagTransformationCounter++;
            result.workWithNode = workWithNode;
        }

        // Correctly reset the `display` property
        const previousDisplayStyle = workWithNode.hasAttribute(HTML_ATTRIBUTE_WAS_PREVIOUSLY_DISPLAY_STYLE)
            ? // Was previously blocked (e.g. cookie banner is visible -> continue without consent)
              workWithNode.getAttribute(HTML_ATTRIBUTE_WAS_PREVIOUSLY_DISPLAY_STYLE)
            : // Did not get blocked, instant unblock (e.g. cookie banner -> accept all -> reload page)
              workWithNode.style.getPropertyValue("display");
        if (!previousDisplayStyle) {
            workWithNode.style.removeProperty("display");
        } else {
            workWithNode.style.setProperty("display", previousDisplayStyle);
        }
        workWithNode.removeAttribute(HTML_ATTRIBUTE_WAS_PREVIOUSLY_DISPLAY_STYLE);

        // Also make the parent visual visible again
        const [visualParent] = findVisualParent(node, visualParentSelectors || {});
        if (
            (visualParent !== node || visualParent?.hasAttribute(HTML_ATTRIBUTE_BLOCKER_CONNECTED)) &&
            // When the visual parent gets modified but it got reset from a memorized display state, do nothing
            !(visualParent === node && previousDisplayStyle)
        ) {
            visualParent.style.removeProperty("display");
        }

        if (!isScript) {
            resolve(result);
        } else {
            const { outerHTML } = workWithNode;
            ensureScriptWrapper(outerHTML, node as HTMLScriptElement).then(() => resolve(result));
        }
    });
}

export { transformToOriginalAttribute, HTML_TAG_TRANSFORMATION_COUNTER };
