/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils";
import { brand, deleteFromNestedMap, idAllocatorFromMaxId, populateNestedMap, setInNestedMap, tryGetFromNestedMap, } from "../../util";
import { noopValidator } from "../../codec";
import { makeDetachedNodeToFieldCodec } from "./detachedFieldIndexCodec";
/**
 * The tree index records detached field IDs and associates them with a change atom ID.
 */
export 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, options) {
        this.name = name;
        this.rootIdAllocator = rootIdAllocator;
        // 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: noopValidator };
        this.codec = makeDetachedNodeToFieldCodec(this.options);
    }
    clone() {
        const clone = new DetachedFieldIndex(this.name, idAllocatorFromMaxId(this.rootIdAllocator.getNextId()), this.options);
        populateNestedMap(this.detachedNodeToField, clone.detachedNodeToField);
        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 };
                }
            }
        }
    }
    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) {
                    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 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 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);
        assert(key !== undefined, 0x7aa /* Unknown removed node ID */);
        return key;
    }
    deleteEntry(nodeId) {
        const found = deleteFromNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor);
        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++) {
                assert(tryGetFromNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor + i) === undefined, 0x7ce /* Detached node ID already exists in index */);
                setInNestedMap(this.detachedNodeToField, nodeId.major, nodeId.minor + i, root + i);
            }
        }
        return root;
    }
    encode() {
        return this.codec.encode({
            data: this.detachedNodeToField,
            maxId: this.rootIdAllocator.getNextId(),
        });
    }
    /**
     * Loads the tree index from the given string, this overrides any existing data.
     */
    loadData(data) {
        const detachedFieldIndex = this.codec.decode(data);
        this.rootIdAllocator = idAllocatorFromMaxId(detachedFieldIndex.maxId);
        this.detachedNodeToField = detachedFieldIndex.data;
    }
}
//# sourceMappingURL=detachedFieldIndex.js.map