/**
 * @file EventBus.js
 * @description Event communication system for decoupled components
 * @author Implementation Team
 */

/**
 * @class EventBus
 * @description Simple pub/sub event bus for component communication
 *
 * @example
 * import EventBus from './EventBus.js';
 *
 * // Subscribe to event
 * EventBus.on('move-made', (data) => {
 *     console.log('Move:', data.move);
 * });
 *
 * // Emit event
 * EventBus.emit('move-made', { move: moveObject });
 */
class EventBus {
    constructor() {
        /**
         * @private
         * @property {Object<string, Array<Function>>} events - Event listeners map
         */
        this.events = {};
    }

    /**
     * Subscribe to an event
     *
     * @param {string} eventName - Name of the event
     * @param {Function} callback - Callback function
     * @returns {Function} Unsubscribe function
     *
     * @example
     * const unsubscribe = EventBus.on('piece-moved', handleMove);
     * // Later...
     * unsubscribe(); // Remove listener
     */
    on(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }

        this.events[eventName].push(callback);

        // Return unsubscribe function
        return () => this.off(eventName, callback);
    }

    /**
     * Subscribe to an event once (auto-unsubscribe after first call)
     *
     * @param {string} eventName - Name of the event
     * @param {Function} callback - Callback function
     * @returns {Function} Unsubscribe function
     */
    once(eventName, callback) {
        const onceWrapper = (...args) => {
            callback(...args);
            this.off(eventName, onceWrapper);
        };

        return this.on(eventName, onceWrapper);
    }

    /**
     * Unsubscribe from an event
     *
     * @param {string} eventName - Name of the event
     * @param {Function} callback - Callback function to remove
     */
    off(eventName, callback) {
        if (!this.events[eventName]) {
            return;
        }

        this.events[eventName] = this.events[eventName].filter(
            cb => cb !== callback
        );

        // Clean up empty event arrays
        if (this.events[eventName].length === 0) {
            delete this.events[eventName];
        }
    }

    /**
     * Emit an event to all subscribers
     *
     * @param {string} eventName - Name of the event
     * @param {*} data - Data to pass to listeners
     *
     * @example
     * EventBus.emit('game-over', { winner: 'white', reason: 'checkmate' });
     */
    emit(eventName, data) {
        if (!this.events[eventName]) {
            return;
        }

        this.events[eventName].forEach(callback => {
            try {
                callback(data);
            } catch (error) {
                console.error(`Error in event listener for '${eventName}':`, error);
            }
        });
    }

    /**
     * Remove all listeners for an event, or all events if no name specified
     *
     * @param {string} [eventName] - Optional event name to clear
     */
    clear(eventName) {
        if (eventName) {
            delete this.events[eventName];
        } else {
            this.events = {};
        }
    }

    /**
     * Get all event names that have listeners
     *
     * @returns {string[]} Array of event names
     */
    getEventNames() {
        return Object.keys(this.events);
    }

    /**
     * Get listener count for an event
     *
     * @param {string} eventName - Name of the event
     * @returns {number} Number of listeners
     */
    listenerCount(eventName) {
        return this.events[eventName] ? this.events[eventName].length : 0;
    }
}

// Export singleton instance
export default new EventBus();
