import {
    HTML_ATTRIBUTE_BLOCKER_ID,
    HTML_ATTRIBUTE_BY,
    HTML_ATTRIBUTE_CAPTURE_PREFIX,
    HTML_ATTRIBUTE_CAPTURE_SUFFIX,
    HTML_ATTRIBUTE_COOKIE_IDS,
} from "../htmlAttributes.js";

type DecideToBlock = (url: string) => { id: number | string; by: string; requiredIds: (number | string)[] } | undefined;

/**
 * Hijacks `document.createElement` to intercept the creation of specific tag names
 * and apply custom logic to elements that may later receive sensitive attributes
 * (for example `src` on `<script>` elements).
 *
 * This helper focuses on the **element creation stage** only. It allows you to:
 *
 * - Detect when certain elements (e.g. `<script>`) are created.
 * - Mark or wrap these elements so that later attribute-setting logic (such as
 *   delayed `src` assignment) can act on them.
 *
 * Caveats:
 * - This is a global patch: it affects all code that calls `document.createElement`.
 *   Many libraries and frameworks rely heavily on this API, so the implementation
 *   must be conservative and standards-compliant.
 * - Elements created by the HTML parser (e.g. `<script src="...">` in the original
 *   HTML markup) are **not** intercepted here; only elements created via
 *   `document.createElement(...)` are affected.
 */
function hijackCreateElement(
    decideToBlock: DecideToBlock,
    descriptors: Array<[typeof HTMLElement, string, ((HTMLElement: HTMLElement) => boolean)?]> = [
        [HTMLScriptElement, "src"],
        [HTMLLinkElement, "href", (element) => element.getAttribute("rel") === "stylesheet"],
    ],
) {
    for (const [PrototypeElement, attribute, isBlockable] of descriptors) {
        try {
            const descriptor = Object.getOwnPropertyDescriptor(PrototypeElement.prototype, attribute);
            if (typeof descriptor?.set === "function" && descriptor.configurable) {
                Object.defineProperty(PrototypeElement.prototype, attribute, {
                    ...descriptor,
                    set: function (this: HTMLElement, value: any) {
                        if (!interceptElement(this, attribute, value, decideToBlock, isBlockable)) {
                            descriptor.set.call(this, value);
                        }
                    },
                });

                const originalSetAttribute = PrototypeElement.prototype.setAttribute;
                PrototypeElement.prototype.setAttribute = function (this: HTMLElement, name: string, value: any) {
                    if (name === attribute && this instanceof PrototypeElement) {
                        if (!interceptElement(this, attribute, value, decideToBlock, isBlockable)) {
                            originalSetAttribute.call(this, name, value);
                        }
                    } else {
                        originalSetAttribute.call(this, name, value);
                    }
                };
            }
        } catch (e) {
            console.error(
                `Failed to hijack and block for ${PrototypeElement.name}.${attribute}, what could be mostly be caused by an active ad-blocker. Cause:`,
                e,
            );
        }
    }
}

function interceptElement(
    element: HTMLElement,
    attribute: string,
    value: string,
    decideToBlock: DecideToBlock,
    isBlockable?: (element: HTMLElement) => boolean,
) {
    // Never intercept already blocked elements or avoid recursive calls when unblocking through `findAndUnblock`.
    if (element.hasAttribute(HTML_ATTRIBUTE_BLOCKER_ID) || (isBlockable && !isBlockable(element))) {
        return false;
    }

    let fullUrl = value;
    try {
        fullUrl = new URL(value, window.location.href).toString();
    } catch (e) {
        // Silence is golden.
    }

    const block = decideToBlock(fullUrl);

    if (block) {
        const { id, by, requiredIds } = block;
        element.setAttribute(HTML_ATTRIBUTE_BLOCKER_ID, `${id}`);
        element.setAttribute(HTML_ATTRIBUTE_BY, by);
        element.setAttribute(HTML_ATTRIBUTE_COOKIE_IDS, requiredIds.join(","));
        element.setAttribute(`${HTML_ATTRIBUTE_CAPTURE_PREFIX}-${attribute}-${HTML_ATTRIBUTE_CAPTURE_SUFFIX}`, fullUrl);
        return true;
    }

    return false;
}

export { hijackCreateElement };
