/**
 * Utility class for managing connection between listeners to signals
 */
export class SignalBinding {
    /**
     * State flag
     * @type {boolean}
     */
    #linked = false;

    get linked() {
        return this.#linked;
    }

    /**
     *
     * @param {Signal} signal
     * @param {function} handler
     * @param {*} [context] will be passed as thisArg to the handler
     * @constructor
     */
    constructor(signal, handler, context) {
        if (typeof handler !== "function") {
            throw new TypeError(`handler must be a function, instead was '${typeof handler}'`);
        }

        if (typeof signal !== "object") {
            throw new TypeError(`signal must be of an object, instead was '${typeof signal}'`)
        }

        if (typeof signal.add !== "function") {
            throw new TypeError(`signal.add must be a function, instead was '${typeof signal.add}'`);
        }

        /**
         * Binding signal
         * @type {Signal}
         */
        this.signal = signal;

        /**
         * Signal handler to be attached to the signal
         * @type {Function}
         */
        this.handler = handler;

        /**
         *
         * @type {*}
         */
        this.context = context;

    }

    /**
     * Attaches handler to the signal
     * Idempotent
     */
    link() {
        if (this.#linked) {
            return;
        }

        this.#linked = true;
        this.signal.add(this.handler, this.context);
    }

    /**
     * Detaches handler from the signal
     * Idempotent
     */
    unlink() {
        if (!this.#linked) {
            return;
        }

        this.#linked = false;
        this.signal.remove(this.handler, this.context);
    }

    /**
     * Creates a {@link SignalBinding} and links it immediately. Utility method
     * @param {Signal} signal
     * @param {function} handler
     * @param {*} [context]
     * @return {SignalBinding}
     */
    static bind(
        signal, handler, context
    ) {
        const r = new SignalBinding(signal, handler, context);

        r.link();

        return r;
    }
}
