"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const PropertySymbol = __importStar(require("../PropertySymbol.cjs"));
const EventPhaseEnum_js_1 = __importDefault(require("./EventPhaseEnum.cjs"));
const WindowErrorUtility_js_1 = __importDefault(require("../window/WindowErrorUtility.cjs"));
const WindowBrowserContext_js_1 = __importDefault(require("../window/WindowBrowserContext.cjs"));
const BrowserErrorCaptureEnum_js_1 = __importDefault(require("../browser/enums/BrowserErrorCaptureEnum.cjs"));
/**
 * Handles events.
 */
class EventTarget {
    [PropertySymbol.listeners] = {
        capturing: new Map(),
        bubbling: new Map()
    };
    [PropertySymbol.listenerOptions] = {
        capturing: new Map(),
        bubbling: new Map()
    };
    /**
     * Return a default description for the EventTarget class.
     */
    get [Symbol.toStringTag]() {
        return 'EventTarget';
    }
    /**
     * Adds an event listener.
     *
     * @param type Event type.
     * @param listener Listener.
     * @param options An object that specifies characteristics about the event listener.(currently only once)
     * @param options.once
     */
    addEventListener(type, listener, options) {
        options = typeof options === 'boolean' ? { capture: options } : options || {};
        const eventPhase = options.capture ? 'capturing' : 'bubbling';
        let listeners = this[PropertySymbol.listeners][eventPhase].get(type);
        let listenerOptions = this[PropertySymbol.listenerOptions][eventPhase].get(type);
        if (!listeners) {
            listeners = [];
            listenerOptions = [];
            this[PropertySymbol.listeners][eventPhase].set(type, listeners);
            this[PropertySymbol.listenerOptions][eventPhase].set(type, listenerOptions);
        }
        if (listeners.includes(listener)) {
            return;
        }
        listeners.push(listener);
        listenerOptions.push(options);
    }
    /**
     * Adds an event listener.
     *
     * @param type Event type.
     * @param listener Listener.
     */
    removeEventListener(type, listener) {
        const bubblingListeners = this[PropertySymbol.listeners].bubbling.get(type);
        if (bubblingListeners) {
            const index = bubblingListeners.indexOf(listener);
            if (index !== -1) {
                bubblingListeners.splice(index, 1);
                this[PropertySymbol.listenerOptions].bubbling.get(type).splice(index, 1);
                return;
            }
        }
        const capturingListeners = this[PropertySymbol.listeners].capturing.get(type);
        if (capturingListeners) {
            const index = capturingListeners.indexOf(listener);
            if (index !== -1) {
                capturingListeners.splice(index, 1);
                this[PropertySymbol.listenerOptions].capturing.get(type).splice(index, 1);
            }
        }
    }
    /**
     * Dispatches an event.
     *
     * @see https://www.w3.org/TR/DOM-Level-3-Events/#event-flow
     * @see https://www.quirksmode.org/js/events_order.html#link4
     * @param event Event.
     * @returns The return value is false if event is cancelable and at least one of the event handlers which handled this event called Event.preventDefault().
     */
    dispatchEvent(event) {
        if (!event[PropertySymbol.target]) {
            event[PropertySymbol.target] = this;
            this.#goThroughDispatchEventPhases(event);
            return !(event[PropertySymbol.cancelable] && event[PropertySymbol.defaultPrevented]);
        }
        this.#callDispatchEventListeners(event);
        return !(event[PropertySymbol.cancelable] && event[PropertySymbol.defaultPrevented]);
    }
    /**
     * Adds an event listener.
     *
     * TODO:
     * Was used by with IE8- and Opera. React believed Happy DOM was a legacy browser and used them, but that is no longer the case, so we should remove this method after that this is verified.
     *
     * @deprecated
     * @param type Event type.
     * @param listener Listener.
     */
    attachEvent(type, listener) {
        this.addEventListener(type.replace('on', ''), listener);
    }
    /**
     * Removes an event listener.
     *
     * TODO:
     * Was used by IE8- and Opera. React believed Happy DOM was a legacy browser and used them, but that is no longer the case, so we should remove this method after that this is verified.
     *
     * @deprecated
     * @param type Event type.
     * @param listener Listener.
     */
    detachEvent(type, listener) {
        this.removeEventListener(type.replace('on', ''), listener);
    }
    /**
     * Goes through dispatch event phases.
     *
     * @param event Event.
     */
    #goThroughDispatchEventPhases(event) {
        const composedPath = event.composedPath();
        // Capturing phase
        event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.capturing;
        for (let i = composedPath.length - 1; i >= 0; i--) {
            event[PropertySymbol.currentTarget] = composedPath[i];
            composedPath[i].dispatchEvent(event);
            if (event[PropertySymbol.propagationStopped] ||
                event[PropertySymbol.immediatePropagationStopped]) {
                event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.none;
                event[PropertySymbol.target] = null;
                event[PropertySymbol.currentTarget] = null;
                return;
            }
        }
        // At target phase
        event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.atTarget;
        event[PropertySymbol.currentTarget] = this;
        event[PropertySymbol.target].dispatchEvent(event);
        // Bubbling phase
        event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.bubbling;
        if (event[PropertySymbol.bubbles] &&
            !event[PropertySymbol.propagationStopped] &&
            !event[PropertySymbol.immediatePropagationStopped]) {
            for (let i = 1, max = composedPath.length; i < max; i++) {
                event[PropertySymbol.currentTarget] = composedPath[i];
                composedPath[i].dispatchEvent(event);
                if (event[PropertySymbol.propagationStopped] ||
                    event[PropertySymbol.immediatePropagationStopped]) {
                    event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.none;
                    event[PropertySymbol.target] = null;
                    event[PropertySymbol.currentTarget] = null;
                    return;
                }
            }
        }
        // None phase (done)
        event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.none;
        event[PropertySymbol.target] = null;
        event[PropertySymbol.currentTarget] = null;
    }
    /**
     * Handles dispatch event listeners.
     *
     * @param event Event.
     */
    #callDispatchEventListeners(event) {
        const window = this[PropertySymbol.window];
        const browserSettings = window ? new WindowBrowserContext_js_1.default(window).getSettings() : null;
        const eventPhase = event.eventPhase === EventPhaseEnum_js_1.default.capturing ? 'capturing' : 'bubbling';
        if (event.eventPhase !== EventPhaseEnum_js_1.default.capturing) {
            const onEventName = 'on' + event.type.toLowerCase();
            if (typeof this[onEventName] === 'function') {
                // We can end up in a never ending loop if the listener for the error event on Window also throws an error.
                if (window &&
                    (this !== window || event.type !== 'error') &&
                    !browserSettings?.disableErrorCapturing &&
                    browserSettings?.errorCapture === BrowserErrorCaptureEnum_js_1.default.tryAndCatch) {
                    WindowErrorUtility_js_1.default.captureError(window, this[onEventName].bind(this, event));
                }
                else {
                    this[onEventName].call(this, event);
                }
            }
        }
        // We need to clone the arrays because the listeners may remove themselves while we are iterating.
        const listeners = this[PropertySymbol.listeners][eventPhase].get(event.type)?.slice();
        if (!listeners) {
            return;
        }
        const listenerOptions = this[PropertySymbol.listenerOptions][eventPhase]
            .get(event.type)
            ?.slice();
        for (let i = 0, max = listeners.length; i < max; i++) {
            const listener = listeners[i];
            const options = listenerOptions[i];
            if (options?.passive) {
                event[PropertySymbol.isInPassiveEventListener] = true;
            }
            // We can end up in a never ending loop if the listener for the error event on Window also throws an error.
            if (window &&
                (this !== window || event.type !== 'error') &&
                !browserSettings?.disableErrorCapturing &&
                browserSettings?.errorCapture === BrowserErrorCaptureEnum_js_1.default.tryAndCatch) {
                if (listener.handleEvent) {
                    WindowErrorUtility_js_1.default.captureError(window, listener.handleEvent.bind(listener, event));
                }
                else {
                    WindowErrorUtility_js_1.default.captureError(window, listener.bind(this, event));
                }
            }
            else {
                if (listener.handleEvent) {
                    listener.handleEvent(event);
                }
                else {
                    listener.call(this, event);
                }
            }
            event[PropertySymbol.isInPassiveEventListener] = false;
            if (options?.once) {
                // At this time, listeners and listenersOptions are cloned arrays. When the original value is deleted,
                // The value corresponding to the cloned array is not deleted. So we need to delete the value in the cloned array.
                listeners.splice(i, 1);
                listenerOptions.splice(i, 1);
                this.removeEventListener(event.type, listener);
                i--;
                max--;
            }
            if (event[PropertySymbol.immediatePropagationStopped]) {
                return;
            }
        }
    }
}
exports.default = EventTarget;
//# sourceMappingURL=EventTarget.cjs.map