"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.tryGetSchema = exports.treeNodeApi = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../core/index.js");
const index_js_2 = require("../feature-libraries/index.js");
const index_js_3 = require("../util/index.js");
const proxies_js_1 = require("./proxies.js");
const proxyBinding_js_1 = require("./proxyBinding.js");
const schemaCaching_js_1 = require("./schemaCaching.js");
const schemaTypes_js_1 = require("./schemaTypes.js");
const leafNodeSchema_js_1 = require("./leafNodeSchema.js");
const internal_2 = require("@fluidframework/runtime-utils/internal");
const internal_3 = require("@fluidframework/telemetry-utils/internal");
/**
 * The `Tree` object holds various functions for analyzing {@link TreeNode}s.
 */
exports.treeNodeApi = {
    parent: (node) => {
        const editNode = (0, proxyBinding_js_1.getFlexNode)(node).parentField.parent.parent;
        if (editNode === undefined) {
            return undefined;
        }
        const output = (0, proxies_js_1.getOrCreateNodeProxy)(editNode);
        (0, internal_1.assert)(!(0, index_js_2.isTreeValue)(output), 0x87f /* Parent can't be a leaf, so it should be a node not a value */);
        return output;
    },
    key: (node) => {
        // If the parent is undefined, then this node is under the root field,
        // so we know its key is the special root one.
        const parent = exports.treeNodeApi.parent(node);
        if (parent === undefined) {
            return index_js_1.rootFieldKey;
        }
        // The flex-domain strictly operates in terms of "stored keys".
        // To find the associated developer-facing "view key", we need to look up the field associated with
        // the stored key from the flex-domain, and get view key its simple-domain counterpart was created with.
        const storedKey = getStoredKey(node);
        const parentSchema = exports.treeNodeApi.schema(parent);
        const viewKey = getViewKeyFromStoredKey(parentSchema, storedKey);
        return viewKey;
    },
    on: (node, eventName, listener) => {
        const flex = (0, proxyBinding_js_1.getFlexNode)(node);
        switch (eventName) {
            case "nodeChanged":
                return flex.on("nodeChanged", listener);
            case "treeChanged":
                return flex.on("treeChanged", listener);
            default:
                return (0, internal_1.unreachableCase)(eventName);
        }
    },
    status: (node) => {
        return (0, proxyBinding_js_1.getFlexNode)(node, true).treeStatus();
    },
    is: (value, schema) => {
        const actualSchema = tryGetSchema(value);
        if (actualSchema === undefined) {
            return false;
        }
        if ((0, index_js_3.isReadonlyArray)(schema)) {
            for (const singleSchema of schema) {
                const testSchema = (0, index_js_2.isLazy)(singleSchema) ? singleSchema() : singleSchema;
                if (testSchema === actualSchema) {
                    return true;
                }
            }
            return false;
        }
        else {
            return schema === actualSchema;
        }
    },
    schema(node) {
        return tryGetSchema(node) ?? (0, index_js_3.fail)("Not a tree node");
    },
    shortId(node) {
        const flexNode = (0, proxyBinding_js_1.getFlexNode)(node);
        let shortId;
        for (const field of flexNode.boxedIterator()) {
            if (field.schema.kind === index_js_2.FieldKinds.identifier) {
                if (shortId !== undefined) {
                    throw new internal_3.UsageError("shortId() may not be called on a node with more than one identifier. Consider converting extraneous identifier fields to string fields.");
                }
                const identifier = field.boxedAt(0);
                (0, internal_1.assert)(identifier !== undefined, 0x927 /* The identifier must exist */);
                const identifierValue = identifier.value;
                const localNodeKey = identifier.context.nodeKeyManager.tryLocalizeNodeKey(identifierValue);
                shortId =
                    localNodeKey !== undefined ? (0, index_js_3.extractFromOpaque)(localNodeKey) : identifierValue;
            }
        }
        return shortId;
    },
};
/**
 * Returns a schema for a value if the value is a {@link TreeNode} or a {@link TreeLeafValue}.
 * Returns undefined for other values.
 */
function tryGetSchema(value) {
    switch (typeof value) {
        case "string":
            return leafNodeSchema_js_1.stringSchema;
        case "number":
            return leafNodeSchema_js_1.numberSchema;
        case "boolean":
            return leafNodeSchema_js_1.booleanSchema;
        case "object": {
            if ((0, proxies_js_1.isTreeNode)(value)) {
                // This case could be optimized, for example by placing the simple schema in a symbol on tree nodes.
                return (0, schemaCaching_js_1.tryGetSimpleNodeSchema)((0, proxyBinding_js_1.getFlexNode)(value).schema);
            }
            if (value === null) {
                return leafNodeSchema_js_1.nullSchema;
            }
            if ((0, internal_2.isFluidHandle)(value)) {
                return leafNodeSchema_js_1.handleSchema;
            }
        }
        default:
            return undefined;
    }
}
exports.tryGetSchema = tryGetSchema;
/**
 * Gets the stored key with which the provided node is associated in the parent.
 */
function getStoredKey(node) {
    // Note: the flex domain strictly works with "stored keys", and knows nothing about the developer-facing
    // "view keys".
    const parentField = (0, proxyBinding_js_1.getFlexNode)(node).parentField;
    if (parentField.parent.schema.kind.multiplicity === index_js_1.Multiplicity.Sequence) {
        // The parent of `node` is an array node
        return parentField.index;
    }
    // The parent of `node` is an object, a map, or undefined (and therefore `node` is a root/detached node).
    return parentField.parent.key;
}
/**
 * Given a node schema, gets the view key corresponding with the provided {@link FieldProps.key | stored key}.
 */
function getViewKeyFromStoredKey(schema, storedKey) {
    // Only object nodes have the concept of a "stored key", differentiated from the developer-facing "view key".
    // For any other kind of node, the stored key and the view key are the same.
    if (schema.kind !== schemaTypes_js_1.NodeKind.Object) {
        return storedKey;
    }
    const fields = schema.info;
    // Invariants:
    // - The set of all view keys under an object must be unique.
    // - The set of all stored keys (including those implicitly created from view keys) must be unique.
    // To find the view key associated with the provided stored key, first check for any stored key matches (which are optionally populated).
    // If we don't find any, then search for a matching view key.
    for (const [viewKey, fieldSchema] of Object.entries(fields)) {
        if (fieldSchema instanceof schemaTypes_js_1.FieldSchema && fieldSchema.props?.key === storedKey) {
            return viewKey;
        }
    }
    if (fields[storedKey] === undefined) {
        (0, index_js_3.fail)("Existing stored key should always map to a view key");
    }
    return storedKey;
}
//# sourceMappingURL=treeNodeApi.js.map