"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
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 schemaFactory_js_1 = require("./schemaFactory.js");
const schemaTypes_js_1 = require("./schemaTypes.js");
const toFlexSchema_js_1 = require("./toFlexSchema.js");
/**
 * 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);
        const anchor = flex.anchorNode;
        switch (eventName) {
            case "nodeChanged": {
                let unsubscribeFromTreeChanged;
                return anchor.on("childrenChanged", () => {
                    if (unsubscribeFromTreeChanged === undefined) {
                        unsubscribeFromTreeChanged = anchor.on("subtreeChanged", () => {
                            listener();
                            unsubscribeFromTreeChanged?.();
                            unsubscribeFromTreeChanged = undefined;
                        });
                    }
                });
            }
            case "treeChanged":
                return anchor.on("subtreeChanged", () => listener());
            default:
                return (0, internal_1.unreachableCase)(eventName);
        }
    },
    status: (node) => {
        return (0, proxyBinding_js_1.getFlexNode)(node, true).treeStatus();
    },
    is: (value, schema) => {
        const flexSchema = (0, toFlexSchema_js_1.getFlexSchema)(schema);
        if ((0, index_js_2.isTreeValue)(value)) {
            return (flexSchema instanceof index_js_2.LeafNodeSchema && (0, index_js_2.valueSchemaAllows)(flexSchema.info, value));
        }
        return (0, proxyBinding_js_1.tryGetFlexNode)(value)?.is(flexSchema) ?? false;
    },
    schema(node) {
        if ((0, index_js_2.isTreeValue)(node)) {
            return (0, schemaFactory_js_1.schemaFromValue)(node);
        }
        return (0, schemaCaching_js_1.tryGetSimpleNodeSchema)((0, proxyBinding_js_1.getFlexNode)(node).schema);
    },
    // TODO:#7734,7736: Re-enable when persisted document format is optimized
    // shortId(node: TreeNode): number | undefined {
    // 	const flexNode = getFlexNode(node);
    // 	for (const field of flexNode.boxedIterator()) {
    // 		if (field.schema.kind === FieldKinds.identifier) {
    // 			const identifier = field.boxedAt(0);
    // 			assert(identifier !== undefined, 0x927 /* The identifier must exist */);
    // 			return extractFromOpaque(
    // 				identifier.context.nodeKeys.localize(identifier.value as StableNodeKey),
    // 			);
    // 		}
    // 	}
    // },
};
/**
 * 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=treeApi.js.map