"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OneToManyMapStar = void 0;
const NOT_FOUND_INDEX = -1;
/**
 * Data structure able to efficiently manage a set of models related to a set of properties in a one to many relation.
 */
class OneToManyMapStar {
    #modelToRelationMap;
    #relationToModelsMaps;
    #spec;
    constructor(spec) {
        this.#modelToRelationMap = new Map();
        this.#relationToModelsMaps = {};
        for (const specProperty of Reflect.ownKeys(spec)) {
            this.#relationToModelsMaps[specProperty] = new Map();
        }
        this.#spec = spec;
    }
    add(model, relation) {
        this.#buildOrGetModelArray(model).push(relation);
        for (const relationKey of Reflect.ownKeys(relation)) {
            this.#buildOrGetRelationModels(relationKey, relation[relationKey]).push(model);
        }
    }
    clone() {
        const modelToCloneModelMap = this.#buildModelToCloneModelMap();
        const relationToCloneRelationMap = this.#buildRelationToRelationModelMap();
        const properties = Reflect.ownKeys(this.#spec);
        const clone = this._buildNewInstance(this.#spec);
        this.#pushRelationEntriesIntoRelationMap(this.#modelToRelationMap, clone.#modelToRelationMap, modelToCloneModelMap, relationToCloneRelationMap);
        for (const property of properties) {
            this.#pushModelEntriesIntoModelMap(this.#relationToModelsMaps[property], clone.#relationToModelsMaps[property], modelToCloneModelMap);
        }
        return clone;
    }
    get(key, value) {
        return this.#relationToModelsMaps[key].get(value);
    }
    getAllKeys(key) {
        return this.#relationToModelsMaps[key].keys();
    }
    removeByRelation(key, value) {
        const models = this.get(key, value);
        if (models === undefined) {
            return;
        }
        const uniqueModelsSet = new Set(models);
        for (const model of uniqueModelsSet) {
            const relations = this.#modelToRelationMap.get(model);
            if (relations === undefined) {
                throw new Error('Expecting model relation, none found');
            }
            for (const relation of relations) {
                if (relation[key] === value) {
                    this.#removeModelFromRelationMaps(model, relation);
                }
            }
            this.#modelToRelationMap.delete(model);
        }
    }
    _buildNewInstance(spec) {
        return new OneToManyMapStar(spec);
    }
    _cloneModel(model) {
        return model;
    }
    _cloneRelation(relation) {
        return relation;
    }
    #buildModelToCloneModelMap() {
        const modelToCloneModelMap = new Map();
        for (const model of this.#modelToRelationMap.keys()) {
            const clonedModel = this._cloneModel(model);
            modelToCloneModelMap.set(model, clonedModel);
        }
        return modelToCloneModelMap;
    }
    #buildRelationToRelationModelMap() {
        const relationToCloneRelationMap = new Map();
        for (const relations of this.#modelToRelationMap.values()) {
            for (const relation of relations) {
                const clonedRelation = this._cloneRelation(relation);
                relationToCloneRelationMap.set(relation, clonedRelation);
            }
        }
        return relationToCloneRelationMap;
    }
    #buildOrGetModelArray(model) {
        let relations = this.#modelToRelationMap.get(model);
        if (relations === undefined) {
            relations = [];
            this.#modelToRelationMap.set(model, relations);
        }
        return relations;
    }
    #buildOrGetRelationModels(relationKey, relationValue) {
        let models = this.#relationToModelsMaps[relationKey].get(relationValue);
        if (models === undefined) {
            models = [];
            this.#relationToModelsMaps[relationKey].set(relationValue, models);
        }
        return models;
    }
    #getCloneModel(model, modelToCloneModelMap) {
        const clonedModel = modelToCloneModelMap.get(model);
        if (clonedModel === undefined) {
            throw new Error('Expecting model to be cloned, none found');
        }
        return clonedModel;
    }
    #getCloneRelation(relation, relationToCloneRelationMap) {
        const clonedRelation = relationToCloneRelationMap.get(relation);
        if (clonedRelation === undefined) {
            throw new Error('Expecting relation to be cloned, none found');
        }
        return clonedRelation;
    }
    #pushModelEntriesIntoModelMap(source, target, modelToCloneModelMap) {
        for (const [relationValue, models] of source) {
            const modelsClone = new Array();
            for (const model of models) {
                modelsClone.push(this.#getCloneModel(model, modelToCloneModelMap));
            }
            target.set(relationValue, modelsClone);
        }
    }
    #pushRelationEntriesIntoRelationMap(source, target, modelToCloneModelMap, relationToCloneRelationMap) {
        for (const [model, relations] of source) {
            const relationsClone = new Array();
            for (const relation of relations) {
                relationsClone.push(this.#getCloneRelation(relation, relationToCloneRelationMap));
            }
            target.set(this.#getCloneModel(model, modelToCloneModelMap), relationsClone);
        }
    }
    #removeModelFromRelationMaps(model, relation) {
        for (const relationKey of Reflect.ownKeys(relation)) {
            this.#removeModelFromRelationMap(model, relationKey, relation[relationKey]);
        }
    }
    #removeModelFromRelationMap(model, relationKey, relationValue) {
        const relationModels = this.#relationToModelsMaps[relationKey].get(relationValue);
        if (relationModels !== undefined) {
            const index = relationModels.indexOf(model);
            if (index !== NOT_FOUND_INDEX) {
                relationModels.splice(index, 1);
            }
            if (relationModels.length === 0) {
                this.#relationToModelsMaps[relationKey].delete(relationValue);
            }
        }
    }
}
exports.OneToManyMapStar = OneToManyMapStar;
//# sourceMappingURL=OneToManyMapStar.js.map