/**
 * Event Emitter
 *
 * Browser-compatible EventEmitter with a Node.js-like API surface.
 * Kept lightweight and allocation-lean for hot paths.
 */
const isListenerList = (value) => Array.isArray(value);
export class EventEmitter {
    constructor() {
        // Brand for ExcelTS browser stream objects.
        // Use a string key (not a Symbol) so it still works if the bundle ends up
        // containing multiple copies of this module.
        this.__excelts_stream = true;
        this._listeners = new Map();
        this._maxListeners = EventEmitter.defaultMaxListeners;
    }
    addListener(event, listener) {
        return this.on(event, listener);
    }
    _listenerCount(value) {
        if (!value) {
            return 0;
        }
        return isListenerList(value) ? value.length : 1;
    }
    _hasListeners(event) {
        return this._listenerCount(this._listeners.get(event)) > 0;
    }
    on(event, listener) {
        const existing = this._listeners.get(event);
        // Warn if exceeding max listeners (skip check if maxListeners is 0 = unlimited)
        if (this._maxListeners > 0) {
            const count = this._listenerCount(existing);
            if (count >= this._maxListeners) {
                // Avoid hard dependency on console for bundle/minified builds
                console?.warn?.(`MaxListenersExceededWarning: Possible EventEmitter memory leak detected. ` +
                    `${count + 1} ${String(event)} listeners added. ` +
                    `Use emitter.setMaxListeners() to increase limit`);
            }
        }
        if (!existing) {
            this._listeners.set(event, listener);
        }
        else if (isListenerList(existing)) {
            existing.push(listener);
        }
        else {
            this._listeners.set(event, [existing, listener]);
        }
        // Node emits 'newListener' only if someone is listening to it.
        if (event !== "newListener" && this._hasListeners("newListener")) {
            this.emit("newListener", event, listener);
        }
        return this;
    }
    prependListener(event, listener) {
        const existing = this._listeners.get(event);
        if (!existing) {
            this._listeners.set(event, listener);
        }
        else if (isListenerList(existing)) {
            existing.unshift(listener);
        }
        else {
            this._listeners.set(event, [listener, existing]);
        }
        if (event !== "newListener" && this._hasListeners("newListener")) {
            this.emit("newListener", event, listener);
        }
        return this;
    }
    once(event, listener) {
        const onceWrapper = (...args) => {
            this.off(event, onceWrapper);
            listener.apply(this, args);
        };
        onceWrapper.listener = listener;
        return this.on(event, onceWrapper);
    }
    prependOnceListener(event, listener) {
        const onceWrapper = (...args) => {
            this.off(event, onceWrapper);
            listener.apply(this, args);
        };
        onceWrapper.listener = listener;
        return this.prependListener(event, onceWrapper);
    }
    removeListener(event, listener) {
        return this.off(event, listener);
    }
    off(event, listener) {
        const existing = this._listeners.get(event);
        if (!existing) {
            return this;
        }
        if (!isListenerList(existing)) {
            if (existing === listener || existing.listener === listener) {
                this._listeners.delete(event);
                if (event !== "removeListener" && this._hasListeners("removeListener")) {
                    this.emit("removeListener", event, listener);
                }
            }
            return this;
        }
        const listeners = existing;
        if (listeners.length === 0) {
            this._listeners.delete(event);
            return this;
        }
        // Fast path: direct match
        const directIdx = listeners.indexOf(listener);
        if (directIdx !== -1) {
            listeners.splice(directIdx, 1);
        }
        else {
            // Slow path: check for once wrapper
            for (let i = 0, len = listeners.length; i < len; i++) {
                if (listeners[i].listener === listener) {
                    listeners.splice(i, 1);
                    break;
                }
            }
        }
        if (listeners.length === 0) {
            this._listeners.delete(event);
        }
        else if (listeners.length === 1) {
            this._listeners.set(event, listeners[0]);
        }
        if (event !== "removeListener" && this._hasListeners("removeListener")) {
            this.emit("removeListener", event, listener);
        }
        return this;
    }
    emit(event, ...args) {
        const existing = this._listeners.get(event);
        if (!existing) {
            return false;
        }
        if (!isListenerList(existing)) {
            try {
                existing.apply(this, args);
            }
            catch (err) {
                if (event !== "error") {
                    this.emit("error", err);
                }
            }
            return true;
        }
        const listeners = existing;
        const len = listeners.length;
        if (len === 0) {
            return false;
        }
        if (len === 1) {
            try {
                listeners[0].apply(this, args);
            }
            catch (err) {
                if (event !== "error") {
                    this.emit("error", err);
                }
            }
            return true;
        }
        if (len === 2) {
            const l0 = listeners[0];
            const l1 = listeners[1];
            try {
                l0.apply(this, args);
            }
            catch (err) {
                if (event !== "error") {
                    this.emit("error", err);
                }
            }
            try {
                l1.apply(this, args);
            }
            catch (err) {
                if (event !== "error") {
                    this.emit("error", err);
                }
            }
            return true;
        }
        // Snapshot to allow removal during emit
        const snapshot = listeners.slice();
        for (let i = 0; i < snapshot.length; i++) {
            try {
                snapshot[i].apply(this, args);
            }
            catch (err) {
                if (event !== "error") {
                    this.emit("error", err);
                }
            }
        }
        return true;
    }
    removeAllListeners(event) {
        if (event !== undefined) {
            this._listeners.delete(event);
        }
        else {
            this._listeners.clear();
        }
        return this;
    }
    listenerCount(event) {
        return this._listenerCount(this._listeners.get(event));
    }
    listeners(event) {
        const value = this._listeners.get(event);
        if (!value) {
            return [];
        }
        return isListenerList(value) ? value.slice() : [value];
    }
    rawListeners(event) {
        const value = this._listeners.get(event);
        if (!value) {
            return [];
        }
        return isListenerList(value) ? value.slice() : [value];
    }
    eventNames() {
        return [...this._listeners.keys()];
    }
    setMaxListeners(n) {
        this._maxListeners = n;
        return this;
    }
    getMaxListeners() {
        return this._maxListeners;
    }
}
EventEmitter.defaultMaxListeners = 10;
