import { assert } from "../assert.js";

/**
 * @template T
 */
export class ModuleRegistry {

    /**
     *
     * @type {Map<string, T>}
     */
    #modules = new Map();
    /**
     *
     * @type {Map<T,Array<string>>}
     */
    #reverse_lookup = new Map();

    /**
     * @readonly
     * @type {ModuleRegistry}
     */
    proxy = new Proxy(this, {
        /**
         *
         * @param target
         * @param {string} p
         * @param receiver
         * @returns {Class}
         */
        get(target, p, receiver) {
            const module = target.get(p);

            if (module === undefined) {
                throw new Error(`Module '${p}' not found in the registry`);
            }

            return module;
        }
    });

    /**
     *
     * @param {function(name:string,module:T):void} visitor
     */
    traverse(visitor) {
        this.#modules.forEach((klass, name) => visitor(name, klass));
    }


    /**
     * @template {T} M
     * @param {String} name
     * @returns {M}
     */
    get(name) {
        return this.#modules.get(name);
    }

    /**
     *
     * @param {string} name
     * @returns {boolean}
     */
    has(name) {
        return this.#modules.has(name);
    }

    /**
     * @template {T} M
     * @param {string} name
     * @param {M} module
     * @returns {boolean}
     */
    add(name, module) {
        assert.defined(module, 'module');

        if (this.has(name)) {

            if (this.get(name) !== module) {
                console.error(`Module '${name}' is already registered, ignoring request. Existing module:`, this.get(name), ' New module:', module);
            }

            return false;
        }

        this.#modules.set(name, module);

        let names = this.#reverse_lookup.get(module);

        if (names === undefined) {
            names = [];
            this.#reverse_lookup.set(module, names);
        }

        names.push(name);

        return true;
    }

    /**
     * Find all names associated with the given module
     * @template M
     * @param {M} module
     * @returns {string[]}
     * @throws {Error} if module is not registered
     */
    findNamesByModule(module) {

        const names = this.#reverse_lookup.get(module);

        if (names === undefined) {
            throw new Error('Module not found');
        } else {
            return names;
        }
    }

    initialize() {

    }
}
