"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.IdentifierIndex = exports.identifierFieldSchema = exports.identifierSchema = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const core_1 = require("../core");
const util_1 = require("../util");
const editable_tree_1 = require("./editable-tree");
const modular_schema_1 = require("./modular-schema");
const defaultFieldKinds_1 = require("./defaultFieldKinds");
const contextuallyTyped_1 = require("./contextuallyTyped");
function isIdentifier(id) {
    return typeof id === "number";
}
/**
 * The tree schema for the identifier primitive
 */
exports.identifierSchema = modular_schema_1.TypedSchema.tree("identifier", { value: core_1.ValueSchema.Number });
/**
 * The field schema for fields which contain identifiers (see {@link identifierSchema})
 */
exports.identifierFieldSchema = modular_schema_1.TypedSchema.field(defaultFieldKinds_1.value, exports.identifierSchema);
/**
 * The identifier index allows nodes that have a special identifier field to be looked up via a query.
 */
class IdentifierIndex {
    constructor(identifierFieldKey, identifiers = []) {
        this.identifierFieldKey = identifierFieldKey;
        this.identifierFieldKeySymbol = (0, core_1.symbolFromKey)(identifierFieldKey);
        this.nodes = new Map(identifiers);
    }
    /**
     * Returns true if the given schema contains the global identifier field, otherwise false
     */
    identifiersAreInSchema(schema) {
        const fieldSchema = schema.globalFieldSchema.get(this.identifierFieldKey);
        if (fieldSchema === undefined) {
            return false;
        }
        // TODO: is there a better way to check "the field schema is `identifierFieldSchema`"?
        {
            if (fieldSchema.kind.identifier !== exports.identifierFieldSchema.kind.identifier) {
                return false;
            }
            if (fieldSchema.types === undefined) {
                return false;
            }
            return (0, util_1.compareSets)({ a: fieldSchema.types, b: exports.identifierFieldSchema.types });
        }
    }
    /**
     * Search the tree for all nodes with identifiers, and record them in this index for lookup.
     * This should be called each time the tree changes; each call to scan forgets all existing identifiers.
     * @param context - the editable tree context in which to search for identifiers
     */
    // TODO: This can be optimized by responding to deltas/changes to the tree, rather than rescanning the whole tree every time
    scanIdentifiers(context) {
        if (this.identifiersAreInSchema(context.schema)) {
            this.nodes.clear();
            for (let i = 0; i < context.root.length; i++) {
                for (const [id, node] of this.findIdentifiers(context.root.getNode(i))) {
                    (0, common_utils_1.assert)(!this.nodes.has(id), 0x5c5 /* Encountered duplicate node identifier */);
                    this.nodes.set(id, node);
                }
            }
        }
    }
    /**
     * Create a copy of this index which can be mutated without affecting this one.
     */
    clone(context) {
        const indexClone = new IdentifierIndex(this.identifierFieldKey);
        indexClone.scanIdentifiers(context);
        return indexClone;
    }
    // #region ReadonlyMap interface
    forEach(callbackfn, thisArg) {
        return this.nodes.forEach(callbackfn, thisArg);
    }
    get(key) {
        return this.nodes.get(key);
    }
    has(key) {
        return this.nodes.has(key);
    }
    get size() {
        return this.nodes.size;
    }
    entries() {
        return this.nodes.entries();
    }
    keys() {
        return this.nodes.keys();
    }
    values() {
        return this.nodes.values();
    }
    [Symbol.iterator]() {
        return this.nodes[Symbol.iterator]();
    }
    // #endregion ReadonlyMap interface
    *findIdentifiers(node) {
        if (this.identifierFieldKeySymbol in node) {
            const type = node[editable_tree_1.typeSymbol];
            if (type.extraGlobalFields || type.globalFields.has(this.identifierFieldKey)) {
                // Get the ID via a wrapped node rather than an unwrapped node (`node[identifierFieldKeySymbol]`)
                // so that the length of the field and the type of the value can be validated as correct
                const field = node[editable_tree_1.getField](this.identifierFieldKeySymbol);
                if (field.length >= 1) {
                    const identifierNode = field.getNode(0);
                    const id = identifierNode[contextuallyTyped_1.valueSymbol];
                    if (isIdentifier(id)) {
                        yield [id, node];
                    }
                }
            }
        }
        for (const f of node) {
            for (let i = 0; i < f.length; i++) {
                yield* this.findIdentifiers(f.getNode(i));
            }
        }
    }
}
exports.IdentifierIndex = IdentifierIndex;
//# sourceMappingURL=identifierIndex.js.map