/**
 * TypedEventEmitter - A type-safe event emitter with advanced features
 *
 * Provides type-safe event emission/listening, priority handling, wildcard listeners,
 * event history tracking, and an adapter for gradual migration from native EventEmitter.
 */
import { EventEmitter } from 'events';
import { logger } from '../utils/logger.js';
import { FilteredEventEmitter } from './filtered-emitter.js';
/**
 * TypedEventEmitter - A type-safe event emitter with advanced features
 */
export class TypedEventEmitter {
    emitter;
    listeners = new Map();
    wildcardListeners = [];
    eventHistory = [];
    maxHistorySize;
    stats;
    listenerIdCounter = 0;
    enabled = true;
    constructor(options = {}) {
        this.emitter = new EventEmitter();
        this.maxHistorySize = options.maxHistorySize ?? 100;
        this.stats = {
            totalEmitted: 0,
            totalListeners: 0,
            eventCounts: {},
        };
    }
    /**
     * Generate a unique listener ID
     */
    generateListenerId() {
        return `listener_${++this.listenerIdCounter}_${Date.now()}`;
    }
    /**
     * Enable or disable the event emitter
     */
    setEnabled(enabled) {
        this.enabled = enabled;
    }
    /**
     * Check if the emitter is enabled
     */
    isEnabled() {
        return this.enabled;
    }
    /**
     * Emit an event
     */
    emit(type, event) {
        if (!this.enabled) {
            return false;
        }
        const fullEvent = {
            ...event,
            type: type,
            timestamp: Date.now(),
        };
        // Update stats
        this.stats.totalEmitted++;
        this.stats.eventCounts[type] = (this.stats.eventCounts[type] || 0) + 1;
        this.stats.lastEmitted = fullEvent;
        // Get listeners for this event type
        const typeListeners = this.listeners.get(type) || [];
        // Combine with wildcard listeners
        const allListeners = [...typeListeners, ...this.wildcardListeners];
        // Sort by priority (higher priority first)
        allListeners.sort((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0));
        // Track history
        this.addToHistory(fullEvent, allListeners.length);
        // Execute listeners
        const listenersToRemove = [];
        for (const wrapper of allListeners) {
            // Apply filter if present
            if (wrapper.options.filter && !wrapper.options.filter(fullEvent)) {
                continue;
            }
            try {
                const result = wrapper.listener(fullEvent);
                // Handle async listeners
                if (result instanceof Promise) {
                    result.catch((error) => {
                        // Emit error if there are listeners, otherwise log
                        if (this.emitter.listenerCount('error') > 0) {
                            this.emitter.emit('error', error);
                        }
                        else {
                            logger.error('Unhandled async listener error:', error);
                        }
                    });
                }
            }
            catch (error) {
                // Emit error if there are listeners, otherwise log silently
                // This allows emit() to continue without throwing
                if (this.emitter.listenerCount('error') > 0) {
                    this.emitter.emit('error', error);
                }
                else {
                    logger.error('Unhandled listener error:', error);
                }
            }
            // Mark once listeners for removal
            if (wrapper.options.once) {
                listenersToRemove.push(wrapper.id);
            }
        }
        // Remove once listeners
        for (const id of listenersToRemove) {
            this.removeListenerById(id);
        }
        return allListeners.length > 0;
    }
    /**
     * Add an event listener
     */
    on(type, listener, options = {}) {
        const id = this.generateListenerId();
        const wrapper = {
            listener,
            options,
            id,
        };
        const typeKey = type;
        if (!this.listeners.has(typeKey)) {
            this.listeners.set(typeKey, []);
        }
        this.listeners.get(typeKey).push(wrapper);
        this.stats.totalListeners++;
        return id;
    }
    /**
     * Add a one-time event listener
     */
    once(type, listener, options = {}) {
        return this.on(type, listener, { ...options, once: true });
    }
    /**
     * Add a wildcard listener that receives all events
     */
    onAny(listener, options = {}) {
        const id = this.generateListenerId();
        const wrapper = {
            listener,
            options,
            id,
        };
        this.wildcardListeners.push(wrapper);
        this.stats.totalListeners++;
        return id;
    }
    /**
     * Remove an event listener by ID
     */
    off(listenerId) {
        return this.removeListenerById(listenerId);
    }
    /**
     * Remove all listeners for a specific event type
     */
    offAll(type) {
        if (type) {
            const typeKey = type;
            const count = this.listeners.get(typeKey)?.length ?? 0;
            this.listeners.delete(typeKey);
            this.stats.totalListeners -= count;
        }
        else {
            // Remove all listeners
            let _totalRemoved = 0;
            for (const listeners of this.listeners.values()) {
                _totalRemoved += listeners.length;
            }
            _totalRemoved += this.wildcardListeners.length;
            this.listeners.clear();
            this.wildcardListeners = [];
            this.stats.totalListeners = 0;
        }
    }
    /**
     * Remove a listener by ID
     */
    removeListenerById(id) {
        // Check type-specific listeners
        for (const [type, listeners] of this.listeners.entries()) {
            const index = listeners.findIndex((w) => w.id === id);
            if (index !== -1) {
                listeners.splice(index, 1);
                this.stats.totalListeners--;
                if (listeners.length === 0) {
                    this.listeners.delete(type);
                }
                return true;
            }
        }
        // Check wildcard listeners
        const wildcardIndex = this.wildcardListeners.findIndex((w) => w.id === id);
        if (wildcardIndex !== -1) {
            this.wildcardListeners.splice(wildcardIndex, 1);
            this.stats.totalListeners--;
            return true;
        }
        return false;
    }
    /**
     * Add event to history
     */
    addToHistory(event, listenerCount) {
        this.eventHistory.push({
            event,
            timestamp: event.timestamp,
            listenerCount,
        });
        // Trim history if needed
        while (this.eventHistory.length > this.maxHistorySize) {
            this.eventHistory.shift();
        }
    }
    /**
     * Get event history
     */
    getHistory() {
        return [...this.eventHistory];
    }
    /**
     * Get filtered event history
     */
    getFilteredHistory(filter) {
        return this.eventHistory.filter((entry) => filter(entry.event));
    }
    /**
     * Clear event history
     */
    clearHistory() {
        this.eventHistory = [];
    }
    /**
     * Get listener count for a specific event type
     */
    listenerCount(type) {
        if (type) {
            return (this.listeners.get(type)?.length ?? 0) + this.wildcardListeners.length;
        }
        return this.stats.totalListeners;
    }
    /**
     * Get event names that have listeners
     */
    eventNames() {
        return Array.from(this.listeners.keys());
    }
    /**
     * Get event statistics
     */
    getStats() {
        return { ...this.stats };
    }
    /**
     * Reset statistics
     */
    resetStats() {
        this.stats = {
            totalEmitted: 0,
            totalListeners: this.stats.totalListeners,
            eventCounts: {},
        };
    }
    /**
     * Wait for a specific event (returns a Promise)
     */
    waitFor(type, options = {}) {
        return new Promise((resolve, reject) => {
            const { timeout, filter } = options;
            let timeoutId;
            const listenerId = this.once(type, (event) => {
                if (timeoutId) {
                    clearTimeout(timeoutId);
                }
                resolve(event);
            }, { filter });
            if (timeout) {
                timeoutId = setTimeout(() => {
                    this.off(listenerId);
                    reject(new Error(`Timeout waiting for event: ${String(type)}`));
                }, timeout);
            }
        });
    }
    /**
     * Pipe events to another emitter
     */
    pipe(type, target, options = {}) {
        return this.on(type, (event) => {
            const transformedEvent = options.transform ? options.transform(event) : event;
            target.emit(type, transformedEvent);
        });
    }
    /**
     * Create a filtered view of this emitter
     */
    filter(type, predicate) {
        return new FilteredEventEmitter(this, type, predicate);
    }
    /**
     * Dispose the emitter and clean up resources
     */
    dispose() {
        this.offAll();
        this.clearHistory();
        this.emitter.removeAllListeners();
    }
}
// ============================================================================
// EventEmitter Adapter
// ============================================================================
/**
 * Adapter that wraps native EventEmitter to provide TypedEventEmitter interface.
 * This allows gradual migration from EventEmitter to TypedEventEmitter.
 *
 * Usage:
 * ```typescript
 * // Old code (EventEmitter)
 * class MyClass extends EventEmitter {
 *   doSomething() {
 *     this.emit('event', { data: 'value' });
 *   }
 * }
 *
 * // New code (TypedEventEmitterAdapter)
 * class MyClass extends TypedEventEmitterAdapter<MyEvents> {
 *   doSomething() {
 *     this.emitTyped('event', { data: 'value' });
 *   }
 * }
 * ```
 */
export class TypedEventEmitterAdapter extends EventEmitter {
    typedEmitter;
    constructor(options = {}) {
        super();
        this.typedEmitter = new TypedEventEmitter(options);
    }
    /**
     * Type-safe emit (new API)
     */
    emitTyped(type, event) {
        // Emit through both systems for backward compatibility
        const fullEvent = {
            ...event,
            type: type,
            timestamp: Date.now(),
        };
        // Emit through native EventEmitter (for old listeners)
        super.emit(type, fullEvent);
        // Emit through TypedEventEmitter (for new listeners)
        return this.typedEmitter.emit(type, event);
    }
    /**
     * Type-safe listener (new API)
     */
    onTyped(type, listener, options) {
        return this.typedEmitter.on(type, listener, options);
    }
    /**
     * Type-safe once listener (new API)
     */
    onceTyped(type, listener, options) {
        return this.typedEmitter.once(type, listener, options);
    }
    /**
     * Remove typed listener by ID
     */
    offTyped(listenerId) {
        return this.typedEmitter.off(listenerId);
    }
    /**
     * Wait for a typed event
     */
    waitForTyped(type, options) {
        return this.typedEmitter.waitFor(type, options);
    }
    /**
     * Get the underlying TypedEventEmitter
     */
    getTypedEmitter() {
        return this.typedEmitter;
    }
    /**
     * Get event statistics
     */
    getEventStats() {
        return this.typedEmitter.getStats();
    }
    /**
     * Get event history
     */
    getEventHistory() {
        return this.typedEmitter.getHistory();
    }
    /**
     * Dispose both emitters
     */
    dispose() {
        this.typedEmitter.dispose();
        this.removeAllListeners();
    }
}
//# sourceMappingURL=typed-emitter.js.map