/**
 * EventBus - Strict event-driven communication system
 * Enforces type safety and prevents direct module coupling
 */

class EventBus {
    constructor() {
        // Private event storage
        this._listeners = new Map();
        this._moduleRegistry = new Map();
        this._eventHistory = [];
        this._maxHistorySize = 1000;

        // Seal to prevent external modification
        Object.seal(this);
    }

    /**
     * Register a module with the event bus
     * @param {Module} module - Module instance
     */
    registerModule(module) {
        if (!module || typeof module.name !== 'string') {
            throw new Error('Invalid module: must have a name property');
        }

        if (this._moduleRegistry.has(module.name)) {
            throw new Error(`Module ${module.name} is already registered`);
        }

        this._moduleRegistry.set(module.name, module);
    }

    /**
     * Unregister a module and clean up its listeners
     * @param {string} moduleName - Name of module to unregister
     */
    unregisterModule(moduleName) {
        if (!this._moduleRegistry.has(moduleName)) {
            throw new Error(`Module ${moduleName} is not registered`);
        }

        // Remove all listeners for this module
        for (const [eventType, listeners] of this._listeners) {
            const filteredListeners = listeners.filter(listener => listener.module !== moduleName);
            if (filteredListeners.length === 0) {
                this._listeners.delete(eventType);
            } else {
                this._listeners.set(eventType, filteredListeners);
            }
        }

        this._moduleRegistry.delete(moduleName);
    }

    /**
     * Subscribe to an event type
     * @param {string} eventType - Type of event to listen for
     * @param {Function} callback - Function to call when event occurs
     * @param {string} moduleName - Name of the subscribing module
     */
    on(eventType, callback, moduleName) {
        this._validateEventType(eventType);
        this._validateCallback(callback);
        this._validateModule(moduleName);

        if (!this._listeners.has(eventType)) {
            this._listeners.set(eventType, []);
        }

        const listener = {
            callback,
            module: moduleName,
            id: this._generateId()
        };

        this._listeners.get(eventType).push(listener);
        return listener.id; // Return ID for unsubscribing
    }

    /**
     * Unsubscribe from an event
     * @param {string} eventType - Event type to unsubscribe from
     * @param {string} listenerId - ID returned from on() method
     */
    off(eventType, listenerId) {
        this._validateEventType(eventType);

        if (!this._listeners.has(eventType)) {
            return false;
        }

        const listeners = this._listeners.get(eventType);
        const index = listeners.findIndex(listener => listener.id === listenerId);

        if (index === -1) {
            return false;
        }

        listeners.splice(index, 1);

        if (listeners.length === 0) {
            this._listeners.delete(eventType);
        }

        return true;
    }

    /**
     * Emit an event to all subscribers
     * @param {string} eventType - Type of event
     * @param {*} data - Event data
     * @param {string} sourceModule - Module emitting the event
     */
    emit(eventType, data = null, sourceModule) {
        this._validateEventType(eventType);
        this._validateModule(sourceModule);

        const event = {
            type: eventType,
            data,
            source: sourceModule,
            timestamp: Date.now(),
            id: this._generateId()
        };

        // Add to history
        this._addToHistory(event);

        // Get listeners for this event type
        const listeners = this._listeners.get(eventType) || [];

        // Call all listeners (async to prevent blocking)
        listeners.forEach(listener => {
            try {
                // Prevent modules from listening to their own events (optional)
                if (listener.module !== sourceModule) {
                    setTimeout(() => listener.callback(event), 0);
                }
            } catch (error) {
                console.error(`Error in event listener for ${eventType}:`, error);
            }
        });

        return event.id;
    }

    /**
     * Get event history (for debugging)
     * @param {number} limit - Maximum number of events to return
     */
    getEventHistory(limit = 50) {
        return this._eventHistory.slice(-limit);
    }

    /**
     * Get registered modules (for debugging)
     */
    getRegisteredModules() {
        return Array.from(this._moduleRegistry.keys());
    }

    /**
     * Get active listeners (for debugging)
     */
    getActiveListeners() {
        const result = {};
        for (const [eventType, listeners] of this._listeners) {
            result[eventType] = listeners.map(l => l.module);
        }
        return result;
    }

    // Private validation methods
    _validateEventType(eventType) {
        if (!eventType || typeof eventType !== 'string') {
            throw new Error('Event type must be a non-empty string');
        }
    }

    _validateCallback(callback) {
        if (typeof callback !== 'function') {
            throw new Error('Callback must be a function');
        }
    }

    _validateModule(moduleName) {
        if (!moduleName || typeof moduleName !== 'string') {
            throw new Error('Module name must be a non-empty string');
        }

        if (!this._moduleRegistry.has(moduleName)) {
            throw new Error(`Module ${moduleName} is not registered with EventBus`);
        }
    }

    _generateId() {
        return Math.random().toString(36).substr(2, 9);
    }

    _addToHistory(event) {
        this._eventHistory.push(event);

        // Limit history size
        if (this._eventHistory.length > this._maxHistorySize) {
            this._eventHistory.shift();
        }
    }
}

// Freeze to prevent modification
Object.freeze(EventBus);
Object.freeze(EventBus.prototype);

export default EventBus;