import { TaskCancelledError } from '../errors/TaskCancelledError';
export const noopDisposable = { dispose: () => undefined };
export var Event;
(function (Event) {
    /**
     * Adds a handler that handles one event on the emitter.
     */
    Event.once = (event, listener) => {
        let syncDispose = false;
        let disposable;
        disposable = event(value => {
            listener(value);
            if (disposable) {
                disposable.dispose();
            }
            else {
                syncDispose = true; // callback can fire before disposable is returned
            }
        });
        if (syncDispose) {
            disposable.dispose();
            return noopDisposable; // no reason to keep the ref around
        }
        return disposable;
    };
    /**
     * Returns a promise that resolves when the event fires, or when cancellation
     * is requested, whichever happens first.
     */
    Event.toPromise = (event, signal) => {
        if (!signal) {
            return new Promise(resolve => Event.once(event, resolve));
        }
        if (signal.aborted) {
            return Promise.reject(new TaskCancelledError());
        }
        const toDispose = [];
        return new Promise((resolve, reject) => {
            const abortEvt = onAbort(signal);
            toDispose.push(abortEvt);
            toDispose.push(abortEvt.event(() => {
                reject(new TaskCancelledError());
            }));
            toDispose.push(Event.once(event, data => {
                resolve(data);
            }));
        }).finally(() => {
            for (const d of toDispose) {
                d.dispose();
            }
        });
    };
})(Event || (Event = {}));
/** Creates an Event that fires when the signal is aborted. */
export const onAbort = (signal) => {
    const evt = new OneShotEvent();
    if (signal.aborted) {
        evt.emit();
        return { event: evt.addListener, dispose: () => { } };
    }
    const dispose = () => signal.removeEventListener('abort', l);
    // @types/node is currently missing the event types on AbortSignal
    const l = () => {
        evt.emit();
        dispose();
    };
    signal.addEventListener('abort', l);
    return { event: evt.addListener, dispose };
};
/**
 * Base event emitter. Calls listeners when data is emitted.
 */
export class EventEmitter {
    constructor() {
        /**
         * Event<T> function.
         */
        this.addListener = listener => this.addListenerInner(listener);
    }
    /**
     * Gets the number of event listeners.
     */
    get size() {
        if (!this.listeners) {
            return 0;
        }
        else if (typeof this.listeners === 'function') {
            return 1;
        }
        else {
            return this.listeners.length;
        }
    }
    /**
     * Emits event data.
     */
    emit(value) {
        if (!this.listeners) {
            // no-op
        }
        else if (typeof this.listeners === 'function') {
            this.listeners(value);
        }
        else {
            for (const listener of this.listeners) {
                listener(value);
            }
        }
    }
    addListenerInner(listener) {
        if (!this.listeners) {
            this.listeners = listener;
        }
        else if (typeof this.listeners === 'function') {
            this.listeners = [this.listeners, listener];
        }
        else {
            this.listeners.push(listener);
        }
        return { dispose: () => this.removeListener(listener) };
    }
    removeListener(listener) {
        if (!this.listeners) {
            return;
        }
        if (typeof this.listeners === 'function') {
            if (this.listeners === listener) {
                this.listeners = undefined;
            }
            return;
        }
        const index = this.listeners.indexOf(listener);
        if (index === -1) {
            return;
        }
        if (this.listeners.length === 2) {
            this.listeners = index === 0 ? this.listeners[1] : this.listeners[0];
        }
        else {
            this.listeners = this.listeners.slice(0, index).concat(this.listeners.slice(index + 1));
        }
    }
}
/**
 * An event emitter that memorizes and instantly re-emits its last value
 * to attached listeners.
 */
export class MemorizingEventEmitter extends EventEmitter {
    constructor() {
        super(...arguments);
        /**
         * @inheritdoc
         */
        this.addListener = listener => {
            const disposable = this.addListenerInner(listener);
            if (this.lastValue) {
                listener(this.lastValue.value);
            }
            return disposable;
        };
    }
    /**
     * Gets whether this emitter has yet emitted any event.
     */
    get hasEmitted() {
        return !!this.lastValue;
    }
    /**
     * @inheritdoc
     */
    emit(value) {
        this.lastValue = { value };
        super.emit(value);
    }
}
/**
 * An event emitter that fires a value once and removes all
 * listeners automatically after doing so.
 */
class OneShotEvent extends EventEmitter {
    constructor() {
        super(...arguments);
        /**
         * @inheritdoc
         */
        this.addListener = listener => {
            if (this.lastValue) {
                listener(this.lastValue.value);
                return noopDisposable;
            }
            else {
                return this.addListenerInner(listener);
            }
        };
    }
    /**
     * @inheritdoc
     */
    emit(value) {
        this.lastValue = { value };
        super.emit(value);
        this.listeners = undefined;
    }
}//# sourceMappingURL=http://go/sourcemap/sourcemaps/63a2996a10d9e476b6c28e951dd7691d9c0cf480/node_modules/cockatiel/dist/esm/common/Event.js.map