/**
 * @class Events
 * @description A basic event handling system that supports adding events, listeners, and emitting events.
 */
export default class Events
{
    /** Stores events and associated data. */
    private _events: Record<string, Array<any>>;

    /** Stores listeners for specific events. */
    private _listeners: Record<string, Array<{ event: string; fn: (data?: any) => void; context: any }>>;

    /** Initializes an empty events storage and listeners storage. */
    constructor()
    {
        this._events = {};
        this._listeners = {};
    }

    /**
     * Adds an event with associated data to be emitted later.
     * @param event
     * @param data
     */
    addEvent(event: string, data?: any): void
    {
        if (!this._events[event])
        {
            this._events[event] = [];
        }
        this._events[event].push(data);
    }

    /**
     * Registers a listener for a specific event.
     * @param event
     * @param fn
     * @param context
     */
    addListener(event: string, fn: (data?: any) => void, context: any): void
    {
        if (!this._listeners[event])
        {
            this._listeners[event] = [];
        }
        this._listeners[event].push({ event, fn, context });
    }

    /**
     * Removes a specific listener for an event.
     * @param event
     * @param fn
     * @param context
     */
    removeEventListener(event: string, fn: (data?: any) => void, context: any): void
    {
        if (!this._listeners[event]) return;
        this._listeners[event] = this._listeners[event].filter(
            (listener) => listener.fn !== fn || listener.context !== context
        );
    }

    /** Clears all stored events. */
    clearEvents(): void
    {
        this._events = {};
    }

    /**
     * Emits all stored events, invoking associated listeners with the event data.
     * The listeners for each event receive each piece of data associated with that event.
     */
    emitEvents(): void
    {
        for (const eventName in this._events)
        {
            const dataObjects = this._events[eventName];
            const listeners = this._listeners[eventName];

            if (!listeners) continue;

            dataObjects.forEach((data) =>
            {
                listeners.forEach((listener) =>
                {
                    listener.fn.call(listener.context, data);
                });
            });
            delete this._events[eventName];
        }
    }
}
