"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventEmitter = exports.createEmitter = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const util_1 = require("../util");
/**
 * Create an {@link ISubscribable} that can be instructed to emit events via the {@link IEmitter} interface.
 *
 * A class can delegate handling {@link ISubscribable} to the returned value while using it to emit the events.
 * See also `EventEmitter` which be used as a base class to implement {@link ISubscribable} via extension.
 * @alpha
 */
function createEmitter(noListeners) {
    return new ComposableEventEmitter(noListeners);
}
exports.createEmitter = createEmitter;
/**
 * Provides an API for subscribing to and listening to events.
 * Classes wishing to emit events may either extend this class:
 * @example
 * ```ts
 * interface MyEvents {
 *   "loaded": () => void;
 * }
 *
 * class MyClass extends EventEmitter<MyEvents> {
 *   private load() {
 *     this.emit("loaded");
 *   }
 * }
 * ```
 * Or, compose over it:
 * @example
 * ```ts
 * class MyClass implements ISubscribable<MyEvents> {
 *   private readonly events = EventEmitter.create<MyEvents>();
 *
 *   private load() {
 *     this.events.emit("loaded");
 *   }
 *
 *   public on<K extends keyof MyEvents>(eventName: K, listener: MyEvents[K]): () => void {
 *     return this.events.on(eventName, listener);
 *   }
 * }
 * ```
 */
class EventEmitter {
    // Because this is protected and not public, calling this externally (not from a subclass) makes sending events to the constructed instance impossible.
    // Instead, use the static `create` function to get an instance which allows emitting events.
    constructor(noListeners) {
        this.noListeners = noListeners;
        this.listeners = new Map();
    }
    emit(eventName, ...args) {
        const listeners = this.listeners.get(eventName);
        if (listeners !== undefined) {
            const argArray = args; // TODO: Current TS (4.5.5) cannot spread `args` into `listener()`, but future versions (e.g. 4.8.4) can.
            for (const listener of listeners.values()) {
                listener(...argArray);
            }
        }
    }
    emitAndCollect(eventName, ...args) {
        const listeners = this.listeners.get(eventName);
        if (listeners !== undefined) {
            const argArray = args;
            const resultArray = [];
            for (const listener of listeners.values()) {
                resultArray.push(listener(...argArray));
            }
            return resultArray;
        }
        return [];
    }
    /**
     * Register an event listener.
     * @param eventName - the name of the event
     * @param listener - the handler to run when the event is fired by the emitter
     * @returns a function which will deregister the listener when run.
     * This function will error if called more than once.
     */
    on(eventName, listener) {
        (0, util_1.getOrCreate)(this.listeners, eventName, () => new Set()).add(listener);
        return () => this.off(eventName, listener);
    }
    off(eventName, listener) {
        var _a, _b;
        const listeners = (_a = this.listeners.get(eventName)) !== null && _a !== void 0 ? _a : (0, util_1.fail)("Event has no listeners. Event deregistration functions may only be invoked once.");
        (0, common_utils_1.assert)(listeners.delete(listener), 0x4c1 /* Listener does not exist. Event deregistration functions may only be invoked once. */);
        if (listeners.size === 0) {
            this.listeners.delete(eventName);
            (_b = this.noListeners) === null || _b === void 0 ? void 0 : _b.call(this, eventName);
        }
    }
    hasListeners(eventName) {
        if (eventName === undefined) {
            return this.listeners.size !== 0;
        }
        return this.listeners.has(eventName);
    }
}
exports.EventEmitter = EventEmitter;
// This class exposes the constructor and the `emit` method of `EventEmitter`, elevating them from protected to public
class ComposableEventEmitter extends EventEmitter {
    constructor(noListeners) {
        super(noListeners);
    }
    emit(eventName, ...args) {
        return super.emit(eventName, ...args);
    }
    emitAndCollect(eventName, ...args) {
        return super.emitAndCollect(eventName, ...args);
    }
}
//# sourceMappingURL=events.js.map