"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Registry = void 0;
const Object_1 = require("./Object");
const evaluateDataValue_1 = require("./spec/evaluateDataValue");
const remapSpec_1 = require("./spec/remapSpec");
const util_1 = require("./spec/util");
const clone_1 = require("./util/clone");
const id_1 = require("./util/id");
const object_1 = require("./util/object");
const weakMerge_1 = require("./weakMerge");
class Registry {
    constructor(specs, specs_, specsCount) {
        this.specs = specs;
        this.specs_ = specs_ ?? new Object_1.Object_(specs);
        this.specsCount = specsCount ?? {};
        this.lock = {};
    }
    newSpecId() {
        return (0, id_1.uuidNotIn)(this.specs);
    }
    hasSpec(id) {
        return !!this.specs[id];
    }
    emptySpec(partial = {}) {
        const id = partial.id ?? this.newSpecId();
        const spec = (0, util_1.emptySpec)({ ...partial, id });
        this.newSpec(spec);
        return spec;
    }
    newSpec(spec, specId) {
        // console.log('newSpec', spec, specId)
        specId = specId ?? this.newSpecId();
        spec.id = specId;
        delete spec.system;
        this.setSpec(specId, spec);
        return spec;
    }
    getSpec(id) {
        return this.specs[id];
    }
    setSpec(specId, spec) {
        // console.log('Registry', 'setSpec', specId, spec)
        void this.specs_.set(specId, spec);
    }
    injectSpecs(newSpecs) {
        // console.log('injectSpecs', newSpecs)
        const specIdMap = {};
        const nextSpecs = {};
        const visited = new Set();
        const setSpec = (specId, spec, visited = new Set()) => {
            const { units } = spec;
            if (visited.has(specId)) {
                return;
            }
            visited.add(specId);
            for (const unitId in units) {
                const unit = units[unitId];
                if (this.hasSpec(unit.id)) {
                    //
                }
                else {
                    const spec = nextSpecs[unit.id];
                    if (spec) {
                        setSpec(unit.id, spec, visited);
                    }
                    else {
                        //
                    }
                }
                const { input = {} } = unit;
                for (const name in input) {
                    const { data } = input[name];
                    if (data !== undefined) {
                        const dataRef = (0, evaluateDataValue_1.evaluateDataValue)(data, (0, weakMerge_1.weakMerge)(this.specs, nextSpecs), {});
                        for (const path of dataRef.ref ?? []) {
                            const bundle = (0, object_1.deepGet)(dataRef.data, path);
                            const spec = nextSpecs[bundle.unit.id];
                            if (spec) {
                                setSpec(spec.id, spec, visited);
                            }
                            else {
                                //
                            }
                        }
                    }
                }
            }
            void this.specs_.set(specId, spec);
        };
        for (const specId in newSpecs) {
            const spec = newSpecs[specId];
            if (visited.has(specId)) {
                return;
            }
            if (specIdMap[specId]) {
                return;
            }
            let nextSpecId = specId;
            let hasSpec = false;
            while (this.hasSpec(nextSpecId)) {
                nextSpecId = this.newSpecId();
                hasSpec = true;
            }
            visited.add(specId);
            if (hasSpec) {
                if ((0, util_1.sameSpec)(spec, this.getSpec(specId))) {
                    //
                }
                else {
                    specIdMap[specId] = nextSpecId;
                }
            }
        }
        const specSet = new Set();
        for (const specId in newSpecs) {
            const spec = newSpecs[specId];
            const id = (0, remapSpec_1.remapSpec)(spec, specIdMap);
            if (this.hasSpec(id)) {
                if ((0, util_1.sameSpec)(spec, this.getSpec(specId))) {
                    //
                }
                else {
                    nextSpecs[id] = spec;
                }
            }
            else {
                nextSpecs[id] = spec;
            }
        }
        for (const specId in nextSpecs) {
            const spec = nextSpecs[specId];
            setSpec(specId, spec, specSet);
        }
        return specIdMap;
    }
    shouldFork(id) {
        return (this.specsCount[id] ?? 0) > 1 || (0, util_1.isSystemSpecId)(this.specs, id);
    }
    forkSpec(spec, specId) {
        // console.log('forkSpec', spec, specId)
        if (this.shouldFork(spec.id)) {
            const clonedSpec = (0, clone_1.clone)(spec);
            delete clonedSpec.system;
            const { id: newSpecId } = this.newSpec(clonedSpec, specId);
            return [newSpecId, clonedSpec];
        }
        else {
            return [spec.id, spec];
        }
    }
    registerUnit(id) {
        // console.log('registerUnit', { id })
        if (this.specsCount[id] === undefined) {
            this.specsCount[id] = 0;
        }
        this.specsCount[id] += 1;
    }
    unregisterUnit(id) {
        // console.log('unregisterUnit', { id })
        if (!this.specsCount[id]) {
            throw new Error(`cannot unregister unit: no spec with id ${id}`);
        }
        this.specsCount[id] -= 1;
        if (this.specsCount[id] === 0) {
            delete this.specsCount[id];
            if (!(0, util_1.isSystemSpecId)(this.specs, id) && !this.lock[id]) {
                this.deleteSpec(id);
            }
        }
    }
    deleteSpec(id) {
        // console.log('deleteSpec', id)
        void this.specs_.delete(id);
    }
    lockSpec(id) {
        this.lock[id] = true;
    }
    unlockSpec(id) {
        delete this.lock[id];
    }
}
exports.Registry = Registry;
//# sourceMappingURL=Registry.js.map