"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.TreeAlpha = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/id-compressor/internal");
const internal_3 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../simple-tree/index.js");
const index_js_2 = require("../util/index.js");
const index_js_3 = require("../codec/index.js");
const index_js_4 = require("../core/index.js");
const index_js_5 = require("../feature-libraries/index.js");
const independentView_js_1 = require("./independentView.js");
const schematizingTreeView_js_1 = require("./schematizingTreeView.js");
const identifier = (node) => {
    const nodeIdentifier = (0, index_js_1.getIdentifierFromNode)(node, "uncompressed");
    if (typeof nodeIdentifier === "number") {
        throw new TypeError("identifier should be uncompressed.");
    }
    return nodeIdentifier;
};
identifier.shorten = (branch, nodeIdentifier) => {
    const nodeKeyManager = branch
        .nodeKeyManager;
    const localNodeKey = nodeKeyManager.tryLocalizeNodeIdentifier(nodeIdentifier);
    return localNodeKey !== undefined ? (0, index_js_2.extractFromOpaque)(localNodeKey) : undefined;
};
identifier.lengthen = (branch, nodeIdentifier) => {
    const nodeKeyManager = branch
        .nodeKeyManager;
    return nodeKeyManager.stabilizeNodeIdentifier(nodeIdentifier);
};
identifier.getShort = (node) => {
    const shortIdentifier = (0, index_js_1.getIdentifierFromNode)(node, "compressed");
    return typeof shortIdentifier === "number" ? shortIdentifier : undefined;
};
identifier.create = (branch) => {
    const nodeKeyManager = branch
        .nodeKeyManager;
    return nodeKeyManager.stabilizeNodeIdentifier(nodeKeyManager.generateLocalNodeIdentifier());
};
Object.freeze(identifier);
/**
 * Extensions to {@link (Tree:variable)} and {@link (TreeBeta:variable)} which are not yet stable.
 * @see {@link (TreeAlpha:interface)}.
 * @alpha
 */
exports.TreeAlpha = {
    branch(node) {
        const kernel = (0, index_js_1.getKernel)(node);
        if (!kernel.isHydrated()) {
            return undefined;
        }
        const view = kernel.anchorNode.anchorSet.slots.get(schematizingTreeView_js_1.ViewSlot);
        (0, internal_1.assert)(view instanceof schematizingTreeView_js_1.SchematizingSimpleTreeView, 0xa5c /* Unexpected view implementation */);
        return view;
    },
    create(schema, data) {
        const mapTree = (0, index_js_1.unhydratedFlexTreeFromInsertable)(data, schema);
        const result = mapTree === undefined ? undefined : (0, index_js_1.getOrCreateNodeFromInnerNode)(mapTree);
        return result;
    },
    importConcise(schema, data) {
        // `importConcise` does not need to support all the formats that `create` does.
        // Perhaps it should error instead of hydrating nodes for example.
        // For now however, it is a simple wrapper around `create`.
        return this.create(schema, data);
    },
    importVerbose(schema, data, options) {
        const config = { ...options };
        // Create a config which is standalone, and thus can be used without having to refer back to the schema.
        const schemalessConfig = (0, index_js_1.applySchemaToParserOptions)(schema, config);
        if (data === undefined) {
            const field = (0, index_js_1.normalizeFieldSchema)(schema);
            if (field.kind !== index_js_1.FieldKind.Optional) {
                throw new internal_3.UsageError("undefined provided for non-optional field.");
            }
            return undefined;
        }
        const cursor = (0, index_js_1.cursorFromVerbose)(data, schemalessConfig);
        return (0, index_js_1.createFromCursor)(schema, cursor);
    },
    exportConcise,
    exportVerbose(node, options) {
        const config = { ...options };
        const cursor = borrowCursorFromTreeNodeOrValue(node);
        return (0, index_js_1.verboseFromCursor)(cursor, (0, index_js_1.tryGetSchema)(node) ?? (0, internal_1.fail)(0xace /* invalid input */), config);
    },
    exportCompressed(node, options) {
        const schema = (0, index_js_1.tryGetSchema)(node) ?? (0, internal_1.fail)(0xacf /* invalid input */);
        const format = (0, index_js_5.fluidVersionToFieldBatchCodecWriteVersion)(options.oldestCompatibleClient);
        const codec = (0, index_js_5.makeFieldBatchCodec)({ jsonValidator: index_js_3.noopValidator }, format);
        const cursor = borrowFieldCursorFromTreeNodeOrValue(node);
        const batch = [cursor];
        // If none provided, create a compressor which will not compress anything.
        const idCompressor = options.idCompressor ?? (0, internal_2.createIdCompressor)();
        const context = {
            encodeType: index_js_5.TreeCompressionStrategy.Compressed,
            idCompressor,
            originatorId: idCompressor.localSessionId, // TODO: Why is this needed?
            schema: { schema: (0, index_js_1.toStoredSchema)(schema), policy: index_js_5.defaultSchemaPolicy },
        };
        const result = codec.encode(batch, context);
        return result;
    },
    importCompressed(schema, compressedData, options) {
        const config = new index_js_1.TreeViewConfigurationAlpha({ schema });
        const content = {
            // Always use a v1 schema codec for consistency.
            schema: (0, index_js_1.extractPersistedSchema)(config, index_js_3.FluidClientVersion.v2_0),
            tree: compressedData,
            idCompressor: options.idCompressor ?? (0, internal_2.createIdCompressor)(),
        };
        const view = (0, independentView_js_1.independentInitializedView)(config, options, content);
        return index_js_1.TreeBeta.clone(view.root);
    },
    identifier,
    key2(node) {
        // If the parent is undefined, then this node is under the root field,
        const parent = index_js_1.treeNodeApi.parent(node);
        if (parent === undefined) {
            return undefined;
        }
        // The flex-domain strictly operates in terms of "stored keys".
        // To find the associated developer-facing "property key", we need to look up the field associated with
        // the stored key from the flex-domain, and get property key its simple-domain counterpart was created with.
        const storedKey = (0, index_js_1.getStoredKey)(node);
        const parentSchema = index_js_1.treeNodeApi.schema(parent);
        return (0, index_js_1.getPropertyKeyFromStoredKey)(parentSchema, storedKey);
    },
    child: (node, propertyKey) => {
        const flexNode = (0, index_js_1.getOrCreateInnerNode)(node);
        (0, internal_1.debugAssert)(() => !flexNode.context.isDisposed() || "The provided tree node has been disposed.");
        const schema = index_js_1.treeNodeApi.schema(node);
        switch (schema.kind) {
            case index_js_1.NodeKind.Array: {
                const sequence = flexNode.tryGetField(index_js_4.EmptyKey);
                // Empty sequence - cannot have children.
                if (sequence === undefined) {
                    return undefined;
                }
                const index = typeof propertyKey === "number"
                    ? propertyKey
                    : (0, index_js_1.asIndex)(propertyKey, Number.POSITIVE_INFINITY);
                // If the key is not a valid index, then there is no corresponding child.
                if (index === undefined) {
                    return undefined;
                }
                const childFlexTree = sequence.at(index);
                // No child at the given index.
                if (childFlexTree === undefined) {
                    return undefined;
                }
                return (0, index_js_1.getOrCreateNodeFromInnerUnboxedNode)(childFlexTree);
            }
            case index_js_1.NodeKind.Map:
                if (typeof propertyKey !== "string") {
                    // Map nodes only support string keys.
                    return undefined;
                }
            // Fall through
            case index_js_1.NodeKind.Object: {
                let storedKey = propertyKey;
                if ((0, index_js_1.isObjectNodeSchema)(schema)) {
                    const fieldSchema = schema.fields.get(String(propertyKey));
                    if (fieldSchema === undefined) {
                        return undefined;
                    }
                    storedKey = fieldSchema.storedKey;
                }
                const field = flexNode.tryGetField((0, index_js_2.brand)(String(storedKey)));
                if (field !== undefined) {
                    return (0, index_js_1.tryGetTreeNodeForField)(field);
                }
                return undefined;
            }
            case index_js_1.NodeKind.Leaf: {
                (0, internal_1.fail)(0xbc3 /* Leaf schema associated with non-leaf tree node. */);
            }
            default: {
                (0, internal_1.unreachableCase)(schema.kind);
            }
        }
    },
    children(node) {
        const flexNode = (0, index_js_1.getOrCreateInnerNode)(node);
        (0, internal_1.debugAssert)(() => !flexNode.context.isDisposed() || "The provided tree node has been disposed.");
        const schema = index_js_1.treeNodeApi.schema(node);
        const result = [];
        switch (schema.kind) {
            case index_js_1.NodeKind.Array: {
                const sequence = flexNode.tryGetField(index_js_4.EmptyKey);
                if (sequence === undefined) {
                    break;
                }
                for (let index = 0; index < sequence.length; index++) {
                    const childFlexTree = sequence.at(index);
                    (0, internal_1.assert)(childFlexTree !== undefined, 0xbc4 /* Sequence child was undefined. */);
                    const childTree = (0, index_js_1.getOrCreateNodeFromInnerUnboxedNode)(childFlexTree);
                    result.push([index, childTree]);
                }
                break;
            }
            case index_js_1.NodeKind.Map: {
                for (const [key, flexField] of flexNode.fields) {
                    const childTreeNode = (0, index_js_1.tryGetTreeNodeForField)(flexField);
                    if (childTreeNode !== undefined) {
                        result.push([key, childTreeNode]);
                    }
                }
                break;
            }
            case index_js_1.NodeKind.Object: {
                (0, internal_1.assert)((0, index_js_1.isObjectNodeSchema)(schema), 0xbc5 /* Expected object schema. */);
                for (const [propertyKey, fieldSchema] of schema.fields) {
                    const storedKey = fieldSchema.storedKey;
                    const flexField = flexNode.tryGetField((0, index_js_2.brand)(String(storedKey)));
                    if (flexField !== undefined) {
                        const childTreeNode = (0, index_js_1.tryGetTreeNodeForField)(flexField);
                        (0, internal_1.assert)(childTreeNode !== undefined, 0xbc6 /* Expected child tree node for field. */);
                        result.push([propertyKey, childTreeNode]);
                    }
                }
                break;
            }
            case index_js_1.NodeKind.Leaf: {
                (0, internal_1.fail)(0xbc7 /* Leaf schema associated with non-leaf tree node. */);
            }
            default: {
                (0, internal_1.unreachableCase)(schema.kind);
            }
        }
        return result;
    },
};
function exportConcise(node, options) {
    if (node === undefined) {
        return undefined;
    }
    const config = { ...options };
    const cursor = borrowCursorFromTreeNodeOrValue(node);
    return (0, index_js_1.conciseFromCursor)(cursor, (0, index_js_1.tryGetSchema)(node) ?? (0, internal_1.fail)(0xacd /* invalid input */), config);
}
/**
 * Borrow a cursor from a node.
 * @remarks
 * The cursor must be put back to its original location before the node is used again.
 */
function borrowCursorFromTreeNodeOrValue(node) {
    if ((0, index_js_5.isTreeValue)(node)) {
        return (0, index_js_1.cursorFromVerbose)(node, {});
    }
    const kernel = (0, index_js_1.getKernel)(node);
    const cursor = kernel.getOrCreateInnerNode().borrowCursor();
    return cursor;
}
/**
 * Borrow a cursor from a field.
 * @remarks
 * The cursor must be put back to its original location before the node is used again.
 */
function borrowFieldCursorFromTreeNodeOrValue(node) {
    if (node === undefined) {
        return (0, index_js_5.cursorForMapTreeField)([]);
    }
    const cursor = borrowCursorFromTreeNodeOrValue(node);
    // TODO: avoid copy: borrow cursor from field instead.
    const mapTree = (0, index_js_5.mapTreeFromCursor)(cursor);
    return (0, index_js_5.cursorForMapTreeField)([mapTree]);
}
//# sourceMappingURL=treeAlpha.js.map