import { RouterClient } from "../../clients/routerClient";
import { Logger } from "../../clients/logger";
import FinsembleEvent from "./FinsembleEvent";
import { System } from "../system";
class EventManager {
    constructor(params) {
        var _a;
        this.eventList = [];
        // This flag is flipped to true when cleanup() is called to prevent spurious errors
        this.cleanedUp = false;
        this.openResponders = [];
        this.closedHandlersExecuted = 0;
        this.handlers = [];
        this.closeFinalHandler = null;
        this.name = params.name;
        this.thisWindowName = window.name;
        this.sourceType = (_a = params.sourceType) !== null && _a !== void 0 ? _a : "undefined";
        // Note: "beforeunload" is not 100% reliable if a window crashes or is killed by the operating system
        window.addEventListener("beforeunload", () => {
            this.cleanup();
        });
        this.currentWindow = System.Window.getCurrent();
    }
    /**
     * @private
     * @param event
     * @param handler
     */
    addListener(event, handler) {
        return this.addEventListener(event, handler);
    }
    /**
     * @private
     * @param event
     * @param handler
     */
    removeListener(event, handler) {
        return this.removeEventListener(event, handler);
    }
    /**
     * This handler will be called on a "closed" event, but is guaranteed to be called
     * after all other "closed" handlers have completed.
     *
     */
    closeFinal(handler) {
        this.closeFinalHandler = handler;
        const listenerGuid = `${this.name}_public_${Date.now()}_${Math.random()}`;
        // Registers an event handler with the private event manager
        RouterClient.transmit(`Finsemble.RegisterInterruptibleEvent.${this.name}`, {
            event: "closed",
            listenerGuid,
            windowName: this.thisWindowName,
        });
        const wrappedHandler = (...args) => {
            // If we have any other closed handlers then we don't call the closeFinal here, because it will be called later
            // when all those other handlers are completed
            // But if we don't have any other closed handlers then go ahead and run the closeFinal handler
            if (this.howManyClosedHandlers())
                return;
            if (this.closeFinalHandler)
                this.closeFinalHandler(...args);
            this.closeFinalHandler = null;
        };
        const responderName = `Finsemble.Interrupt.${listenerGuid}`;
        // Add a responder for when an event is triggered by the private event manager
        RouterClient.addResponder(responderName, (err, queryMessage) => {
            new FinsembleEvent({
                source: this.name,
                sourceType: this.sourceType,
                listenerGuid,
                handler: wrappedHandler,
                event: "closed",
            }, queryMessage);
        });
        this.openResponders.push(responderName);
    }
    howManyClosedHandlers() {
        return this.handlers.filter((fc) => fc.event === "closed").length;
    }
    /**
     * Duplicate entries with the same handler are okay but will result in the function being called twice.
     * @param {string} event
     * @param {Function} handler
     * @memberof EventManager
     */
    addEventListener(event, handler) {
        Logger.system.debug("PublicEventManager.addEventListener:", event);
        const listenerGuid = `${this.name}_public_${Date.now()}_${Math.random()}`;
        const wrappedHandler = event !== "closed"
            ? handler
            : (...args) => {
                handler(...args);
                this.closedHandlersExecuted++;
                // If we've run all the closed handlers, and there's a closeFinal handler, then go ahead and run it
                if (this.closedHandlersExecuted === this.howManyClosedHandlers() && this.closeFinalHandler) {
                    this.closeFinalHandler();
                    this.closeFinalHandler = null;
                }
            };
        this.handlers.push({
            event,
            handler: wrappedHandler,
            listenerGuid,
        });
        // Registers an event handler with the private event manager
        RouterClient.transmit(`Finsemble.RegisterInterruptibleEvent.${this.name}`, {
            event,
            listenerGuid,
            windowName: this.thisWindowName,
        });
        const responderName = `Finsemble.Interrupt.${listenerGuid}`;
        // Add a responder for when an event is triggered by the private event manager
        RouterClient.addResponder(responderName, (err, queryMessage) => {
            new FinsembleEvent({
                source: this.name,
                sourceType: this.sourceType,
                listenerGuid,
                handler: wrappedHandler,
                event,
            }, queryMessage);
        });
        this.openResponders.push(responderName);
        // We do this here because we need to tell FEA that a close-requested
        // event was added to the current window.
        // This is then used by FEA to cancel a close triggered by a system-close
        // event (ie. close from Taskbar or alt + f4) and pass the handling back to the
        // render process so that the closed-request handler can be invoked.
        if (event === "close-requested") {
            this.currentWindow.addEventListener("close-requested", wrappedHandler);
        }
        // Return an automatic unsubscriber
        return () => {
            this.removeEventListener(event, handler);
        };
    }
    /**
     * Removes an event listener added by addEventListener
     *
     * @deprecated Please use the Unsubscriber returned from addEventListener() instead.
     *
     * @param {string} event An event that was used with addEventListener to add a listener
     * @param {FinsembleEventHandler} handler The handler that was used with addEventListener for the event
     * @param {boolean} logWarning [true] If set to false then no warning will be logged if the handler isn't
     * found. This is generally a bad idea, so don't use it unless absolutely forced to. Currently "title-changed"
     * has an impossible problem.
     */
    removeEventListener(event, handler, logWarning = true) {
        Logger.system.debug("PublicEventManager.removeEventListener:", event);
        // If cleanup() was already called then all handlers have already been removed
        // This prevents spurious warnings from any stray cleanup code
        if (this.cleanedUp)
            return;
        // Find the first entry that matches - noting that there could be multiple entries for the same handler
        // and this legacy interface provides no disambiguation via listenerGuid
        const index = this.handlers.findIndex((entry) => entry.event === event && entry.handler === handler);
        if (index === -1) {
            if (logWarning)
                Logger.system.warn(`PublicEventManager.removeEventListener() attempt to remove unregistered event "${event}"`);
            return;
        }
        const { listenerGuid } = this.handlers[index];
        RouterClient.transmit(`Finsemble.UnregisterInterruptibleEvent.${this.name}`, {
            event,
            listenerGuid,
            windowName: this.thisWindowName,
        });
        RouterClient.removeResponder(`Finsemble.Interrupt.${listenerGuid}`);
        // We do this here because we need to tell FEA that a close-requested
        // event was removed to the current window. This will allow FEA to handle
        // system-close events with its default behavior.
        if (event === "close-requested") {
            this.currentWindow.removeEventListener("close-requested", handler);
        }
        this.handlers.splice(index, 1);
    }
    /**
     * @private
     * cleanup is called as part of the "unload" event. The browser provides very little time
     * to perform cleanup before terminating an app, so we must send a single message and hope
     * that it gets through. This should at some point be moved to a single, server-side cleanup
     * facility.
     */
    cleanup() {
        this.cleanedUp = true;
        this.handlers = [];
        // Close all the responders that we opened (one for each event that were listened on).
        this.openResponders.forEach((responderName) => {
            RouterClient.removeResponder(responderName);
        });
    }
}
export default EventManager;
//# sourceMappingURL=PublicEventManager.js.map