"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DetachedFieldIndex = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../codec/index.js");
const index_js_2 = require("../../util/index.js");
const detachedFieldIndexCodec_js_1 = require("./detachedFieldIndexCodec.js");
/**
 * The tree index records detached field IDs and associates them with a change atom ID.
 */
class DetachedFieldIndex {
    /**
     * @param name - A name for the index, used as a prefix for the generated field keys.
     * @param rootIdAllocator - An ID allocator used to generate unique field keys.
     */
    constructor(name, rootIdAllocator, revisionTagCodec, options) {
        this.name = name;
        this.rootIdAllocator = rootIdAllocator;
        this.revisionTagCodec = revisionTagCodec;
        // TODO: don't store the field key in the index, it can be derived from the root ID
        this.detachedNodeToField = new Map();
        this.options = options ?? { jsonValidator: index_js_1.noopValidator };
        this.codec = (0, detachedFieldIndexCodec_js_1.makeDetachedNodeToFieldCodec)(revisionTagCodec, this.options);
    }
    clone() {
        const clone = new DetachedFieldIndex(this.name, (0, index_js_2.idAllocatorFromMaxId)(this.rootIdAllocator.getMaxId()), this.revisionTagCodec, this.options);
        (0, index_js_2.populateNestedMap)(this.detachedNodeToField, clone.detachedNodeToField, true);
        return clone;
    }
    *entries() {
        for (const [major, innerMap] of this.detachedNodeToField) {
            if (major !== undefined) {
                for (const [minor, entry] of innerMap) {
                    yield { id: { major, minor }, root: entry };
                }
            }
            else {
                for (const [minor, entry] of innerMap) {
                    yield { id: { minor }, root: entry };
                }
            }
        }
    }
    /**
     * Removes all entries from the index.
     */
    purge() {
        this.detachedNodeToField.clear();
    }
    updateMajor(current, updated) {
        const innerCurrent = this.detachedNodeToField.get(current);
        if (innerCurrent !== undefined) {
            this.detachedNodeToField.delete(current);
            const innerUpdated = this.detachedNodeToField.get(updated);
            if (innerUpdated === undefined) {
                this.detachedNodeToField.set(updated, innerCurrent);
            }
            else {
                for (const [minor, entry] of innerCurrent) {
                    (0, internal_1.assert)(innerUpdated.get(minor) === undefined, 0x7a9 /* Collision during index update */);
                    innerUpdated.set(minor, entry);
                }
            }
        }
    }
    /**
     * Returns a field key for the given ID.
     * This does not save the field key on the index. To do so, call {@link createEntry}.
     */
    toFieldKey(id) {
        return (0, index_js_2.brand)(`${this.name}-${id}`);
    }
    /**
     * Returns the FieldKey associated with the given id.
     * Returns undefined if no such id is known to the index.
     */
    tryGetEntry(id) {
        return (0, index_js_2.tryGetFromNestedMap)(this.detachedNodeToField, id.major, id.minor);
    }
    /**
     * Returns the FieldKey associated with the given id.
     * Fails if no such id is known to the index.
     */
    getEntry(id) {
        const key = this.tryGetEntry(id);
        (0, internal_1.assert)(key !== undefined, 0x7aa /* Unknown removed node ID */);
        return key;
    }
    deleteEntry(nodeId) {
        const found = (0, index_js_2.deleteFromNestedMap)(this.detachedNodeToField, nodeId.major, nodeId.minor);
        (0, internal_1.assert)(found, 0x7ab /* Unable to delete unknown entry */);
    }
    /**
     * Associates the DetachedNodeId with a field key and creates an entry for it in the index.
     */
    createEntry(nodeId, count = 1) {
        const root = this.rootIdAllocator.allocate(count);
        if (nodeId !== undefined) {
            for (let i = 0; i < count; i++) {
                (0, internal_1.assert)((0, index_js_2.tryGetFromNestedMap)(this.detachedNodeToField, nodeId.major, nodeId.minor + i) === undefined, 0x7ce /* Detached node ID already exists in index */);
                (0, index_js_2.setInNestedMap)(this.detachedNodeToField, nodeId.major, nodeId.minor + i, root + i);
            }
        }
        return root;
    }
    encode() {
        return this.codec.encode({
            data: this.detachedNodeToField,
            maxId: this.rootIdAllocator.getMaxId(),
        });
    }
    /**
     * Loads the tree index from the given string, this overrides any existing data.
     */
    loadData(data) {
        const detachedFieldIndex = this.codec.decode(data);
        this.rootIdAllocator = (0, index_js_2.idAllocatorFromMaxId)(detachedFieldIndex.maxId);
        this.detachedNodeToField = detachedFieldIndex.data;
    }
}
exports.DetachedFieldIndex = DetachedFieldIndex;
//# sourceMappingURL=detachedFieldIndex.js.map