/**
 * @module async/events.js
 * @author Krzysztof Putyra
 * @date Apt 01, 2025
 * @description Utility functions for extening event targets
 */

/**
 * An event representing a change in a value
 */
export class ChangeEvent extends CustomEvent {

    /**
     * Create an event
     * @param {string} type         default: 'change'
     * @param {*} param1            generic event options
     * @param {*} param1.previous   previous value; stored in detail.previous
     * @param {*} param1.current    current value; stored in detail.current
     */
    constructor(type, { previous, current, ...rest }) {
        super(type||'change', { ...rest, detail: { previous, current } });
    }

    /**
     * Returns the current value
     * @return {*}
     */
    get currentValue() { return this.detail.current; }

    /**
     * Returns the previous value
     * @return {*}
     */
    get previousValue() { return this.detail.previous; }
}


/**
 * A collection of event handlers created via onNNN properties
 * @const {WeakMap<object, Map>} eventHandlers
 */
const eventHandlers = new WeakMap();

/**
 * Adds onNNN properties to e class.
 * 
 * @param {function} classConstructor    a class that extends EventTarget
 * @param {Iterable<string>} eventNames
 */
export function defineEvents(classConstructor, ...eventNames)
{
    if (!(classConstructor.prototype instanceof EventTarget))
        throw new TypeError('events can be defined only for classes derived from EventTarget');

    Object.defineProperties(classConstructor.prototype, Object.fromEntries(
        eventNames.map(name => [
            'on'+name.toLowerCase(), {
                set(handler) {
                    // Ignore unsupported types
                    if (typeof handler !== 'function' && typeof handler !== 'undefined' && handler !== null) {
                        return;
                    }
                    // Get a list of event on-handlers for this object
                    let handlers = eventHandlers.get(this);
                    if (!handlers) {
                        handlers = new Map();
                        eventHandlers.set(this, handlers);
                    } else if (handlers.has(name)) {
                        // Remove the previously assigned event handler if any
                        this.removeEventListener(name, handlers.get(name));
                    }
                    // Assign the new handler unless null
                    if (handler) {
                        this.addEventListener(name, handler);
                        handlers.set(name, handler);
                    } else {
                        handlers.delete(name);
                    }
                },
                get() {
                    const handlers = eventHandlers.get(this);
                    return handlers && handlers.get(name);
                }
            }
        ])
    ));
}
