import { dispatchInitiatorExecution } from "./dispatchInitiatorExecution.js";
import { MEMORIZE_JQUERY_EVENT_PROPERTY } from "./memorizeJQueryEvent.js";
import { MEMORIZE_NATIVE_EVENT_PROPERTY } from "./memorizeNativeEvent.js";
import { isCurrentlyInTransaction } from "../../checker/transaction.js";
import { OPT_IN_CONTENT_BLOCKER_ALL } from "../../events/optInContentBlockerAll.js";

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

const OVERWRITE_PROPERTY = "rcbJQueryEventListener";

/**
 * Overwrite `jQuery(selector).on` and `jQuery.event.add` for special events
 * like `elementor/frontend/init`.
 */
function applyJQueryEventInitiator(
    doc: Document,
    element: any,
    eventName: string,
    {
        onBeforeExecute,
        isLoad,
    }: {
        onBeforeExecute?: (wereInTransaction: boolean) => void;
        /**
         * Use this if the event behaves like the `load` event and we need to wait until
         * all the page resources are loaded (even `<script async` scripts).
         */
        isLoad?: boolean;
    } = {
        onBeforeExecute: undefined,
        isLoad: false,
    },
) {
    const overwriteProp = `${OVERWRITE_PROPERTY}_${eventName}`;
    const memorizeProp = `${MEMORIZE_JQUERY_EVENT_PROPERTY}_${eventName}`;
    const memorizeNativeProp = `${MEMORIZE_NATIVE_EVENT_PROPERTY}_${eventName}`;
    const { jQuery } = (doc.defaultView || (doc as any).parentWindow) as any;

    if (!jQuery) {
        return;
    }

    const { event, Event } = jQuery;

    if (!event || !Event || event[overwriteProp]) {
        return;
    }

    const { add } = event;

    Object.assign(event, {
        [overwriteProp]: true,
        add: function (...args: [HTMLElement, string[] | string, any, any, string]) {
            // https://git.io/JsXSb
            const [elem, types, handler, data, selector] = args;
            const useTypes = Array.isArray(types) ? types : typeof types === "string" ? types.split(" ") : types;
            const memorizeExecutionPromise = event[memorizeProp] || elem[memorizeNativeProp]?.then(() => []);
            const inTransaction = isCurrentlyInTransaction();

            // Redirect to own thread to avoid variable order lacks (e. g. Uncode Gmaps Integration, Contact Form 7)
            const executeHandle = ([, ...eventParameters]: any[] = []) =>
                setTimeout(() => {
                    const afterExecution = dispatchInitiatorExecution<InitiatorJQueryEventDetails>({
                        type: "jQueryEvent",
                        elem,
                        types,
                        handler,
                        data,
                        selector,
                    });
                    onBeforeExecute?.(inTransaction);
                    handler?.(new Event(), ...eventParameters);
                    afterExecution();
                }, 0);

            if (types && elem === element) {
                for (const type of useTypes) {
                    const isRequestedEventName = type === eventName;

                    if (isRequestedEventName && inTransaction) {
                        document.addEventListener(
                            OPT_IN_CONTENT_BLOCKER_ALL,
                            (({ detail: { load } }: CustomEvent<OptInContentBlockerAllEvent>) => {
                                if (memorizeExecutionPromise) {
                                    memorizeExecutionPromise.then(executeHandle);
                                } else if (isLoad) {
                                    load.then(executeHandle);
                                } else {
                                    executeHandle();
                                }
                            }) as any,
                            { once: true },
                        );
                    } else if (isRequestedEventName && memorizeExecutionPromise) {
                        memorizeExecutionPromise.then(executeHandle);
                    } else {
                        add.apply(this, [elem, type, handler, data, selector]);
                    }
                }
            } else {
                add.apply(this, args);
            }
        },
    });
}

export { applyJQueryEventInitiator };
