'use strict';

/**
 * AssetResolver — single entry point for all asset-side metadata in EVOLV.
 *
 * Namespaces are declared at construction (see ./namespaces/*). Each namespace
 * exposes a `loadAll()` returning a `Map<id, payload>`; AssetResolver routes
 * resolve(name, id) calls into the right namespace, caches the loaded map, and
 * exposes async `refresh(name?)` for future HttpBackend hydration.
 *
 * Resolution is sync by contract: the first resolve() for an unwarmed
 * namespace pulls everything into cache, all subsequent calls are O(1).
 *
 * Backend abstraction (./backends/*) is where File vs Http lives — namespaces
 * just hold a backend reference and call backend.loadAll().
 *
 * See ./README.md for the full extension story.
 */

class AssetResolver {
    constructor(namespaces = []) {
        this._slots = new Map();
        for (const ns of namespaces) {
            if (!ns || !ns.name || typeof ns.loadAll !== 'function') {
                throw new TypeError('AssetResolver: namespace must declare { name, loadAll() }');
            }
            this._slots.set(ns.name, { ns, cache: null });
        }
    }

    _ensureWarm(name) {
        const slot = this._slots.get(name);
        if (!slot) throw new Error(`AssetResolver: unknown namespace '${name}'`);
        if (slot.cache === null) slot.cache = slot.ns.loadAll();
        return slot;
    }

    resolve(namespace, id) {
        const slot = this._ensureWarm(namespace);
        const key = String(id ?? '').toLowerCase();
        if (!key) return null;
        return slot.cache.get(key) ?? null;
    }

    list(namespace) {
        const slot = this._ensureWarm(namespace);
        return [...slot.cache.keys()];
    }

    namespaces() {
        return [...this._slots.keys()];
    }

    async refresh(namespace) {
        if (namespace) {
            const slot = this._slots.get(namespace);
            if (!slot) throw new Error(`AssetResolver: unknown namespace '${namespace}'`);
            slot.cache = typeof slot.ns.refresh === 'function'
                ? await slot.ns.refresh()
                : slot.ns.loadAll();
            return;
        }
        for (const slot of this._slots.values()) {
            slot.cache = typeof slot.ns.refresh === 'function'
                ? await slot.ns.refresh()
                : slot.ns.loadAll();
        }
    }

    /**
     * Cross-namespace helper: given a softwareType + model id, walk the
     * editor menu tree to return { supplier, type, units, raw } so domain
     * code doesn't have to persist supplier/type/unit on the node.
     */
    resolveAssetMetadata(softwareType, modelId) {
        if (!softwareType || !modelId) return null;
        const tree = this.resolve('menu', softwareType);
        if (!tree || !Array.isArray(tree.suppliers)) return null;
        const norm = String(modelId).toLowerCase();
        for (const supplier of tree.suppliers) {
            for (const type of supplier.types || []) {
                for (const model of type.models || []) {
                    const candidate = String(model.id || model.name || '').toLowerCase();
                    if (candidate === norm) {
                        return {
                            supplier:   supplier.name,
                            supplierId: supplier.id || supplier.name,
                            type:       type.name,
                            typeId:     type.id || type.name,
                            model:      model.name,
                            modelId:    model.id || model.name,
                            units:      Array.isArray(model.units) ? [...model.units] : [],
                            raw:        model,
                        };
                    }
                }
            }
        }
        return null;
    }
}

module.exports = AssetResolver;
