/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, debugAssert, fail, unreachableCase, } from "@fluidframework/core-utils/internal";
import { createIdCompressor } from "@fluidframework/id-compressor/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { asIndex, getKernel, TreeBeta, tryGetSchema, createFromCursor, FieldKind, normalizeFieldSchema, conciseFromCursor, applySchemaToParserOptions, cursorFromVerbose, verboseFromCursor, toStoredSchema, extractPersistedSchema, TreeViewConfigurationAlpha, getStoredKey, getPropertyKeyFromStoredKey, treeNodeApi, getIdentifierFromNode, unhydratedFlexTreeFromInsertable, getOrCreateNodeFromInnerNode, getOrCreateNodeFromInnerUnboxedNode, getOrCreateInnerNode, NodeKind, tryGetTreeNodeForField, isObjectNodeSchema, } from "../simple-tree/index.js";
import { brand, extractFromOpaque } from "../util/index.js";
import { FluidClientVersion, noopValidator, } from "../codec/index.js";
import { EmptyKey } from "../core/index.js";
import { cursorForMapTreeField, defaultSchemaPolicy, isTreeValue, makeFieldBatchCodec, mapTreeFromCursor, TreeCompressionStrategy, fluidVersionToFieldBatchCodecWriteVersion, } from "../feature-libraries/index.js";
import { independentInitializedView } from "./independentView.js";
import { SchematizingSimpleTreeView, ViewSlot } from "./schematizingTreeView.js";
const identifier = (node) => {
    const nodeIdentifier = 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 ? extractFromOpaque(localNodeKey) : undefined;
};
identifier.lengthen = (branch, nodeIdentifier) => {
    const nodeKeyManager = branch
        .nodeKeyManager;
    return nodeKeyManager.stabilizeNodeIdentifier(nodeIdentifier);
};
identifier.getShort = (node) => {
    const shortIdentifier = 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
 */
export const TreeAlpha = {
    branch(node) {
        const kernel = getKernel(node);
        if (!kernel.isHydrated()) {
            return undefined;
        }
        const view = kernel.anchorNode.anchorSet.slots.get(ViewSlot);
        assert(view instanceof SchematizingSimpleTreeView, 0xa5c /* Unexpected view implementation */);
        return view;
    },
    create(schema, data) {
        const mapTree = unhydratedFlexTreeFromInsertable(data, schema);
        const result = mapTree === undefined ? undefined : 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 = applySchemaToParserOptions(schema, config);
        if (data === undefined) {
            const field = normalizeFieldSchema(schema);
            if (field.kind !== FieldKind.Optional) {
                throw new UsageError("undefined provided for non-optional field.");
            }
            return undefined;
        }
        const cursor = cursorFromVerbose(data, schemalessConfig);
        return createFromCursor(schema, cursor);
    },
    exportConcise,
    exportVerbose(node, options) {
        const config = { ...options };
        const cursor = borrowCursorFromTreeNodeOrValue(node);
        return verboseFromCursor(cursor, tryGetSchema(node) ?? fail(0xace /* invalid input */), config);
    },
    exportCompressed(node, options) {
        const schema = tryGetSchema(node) ?? fail(0xacf /* invalid input */);
        const format = fluidVersionToFieldBatchCodecWriteVersion(options.oldestCompatibleClient);
        const codec = makeFieldBatchCodec({ jsonValidator: noopValidator }, format);
        const cursor = borrowFieldCursorFromTreeNodeOrValue(node);
        const batch = [cursor];
        // If none provided, create a compressor which will not compress anything.
        const idCompressor = options.idCompressor ?? createIdCompressor();
        const context = {
            encodeType: TreeCompressionStrategy.Compressed,
            idCompressor,
            originatorId: idCompressor.localSessionId, // TODO: Why is this needed?
            schema: { schema: toStoredSchema(schema), policy: defaultSchemaPolicy },
        };
        const result = codec.encode(batch, context);
        return result;
    },
    importCompressed(schema, compressedData, options) {
        const config = new TreeViewConfigurationAlpha({ schema });
        const content = {
            // Always use a v1 schema codec for consistency.
            schema: extractPersistedSchema(config, FluidClientVersion.v2_0),
            tree: compressedData,
            idCompressor: options.idCompressor ?? createIdCompressor(),
        };
        const view = independentInitializedView(config, options, content);
        return TreeBeta.clone(view.root);
    },
    identifier,
    key2(node) {
        // If the parent is undefined, then this node is under the root field,
        const parent = 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 = getStoredKey(node);
        const parentSchema = treeNodeApi.schema(parent);
        return getPropertyKeyFromStoredKey(parentSchema, storedKey);
    },
    child: (node, propertyKey) => {
        const flexNode = getOrCreateInnerNode(node);
        debugAssert(() => !flexNode.context.isDisposed() || "The provided tree node has been disposed.");
        const schema = treeNodeApi.schema(node);
        switch (schema.kind) {
            case NodeKind.Array: {
                const sequence = flexNode.tryGetField(EmptyKey);
                // Empty sequence - cannot have children.
                if (sequence === undefined) {
                    return undefined;
                }
                const index = typeof propertyKey === "number"
                    ? propertyKey
                    : 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 getOrCreateNodeFromInnerUnboxedNode(childFlexTree);
            }
            case NodeKind.Map:
                if (typeof propertyKey !== "string") {
                    // Map nodes only support string keys.
                    return undefined;
                }
            // Fall through
            case NodeKind.Object: {
                let storedKey = propertyKey;
                if (isObjectNodeSchema(schema)) {
                    const fieldSchema = schema.fields.get(String(propertyKey));
                    if (fieldSchema === undefined) {
                        return undefined;
                    }
                    storedKey = fieldSchema.storedKey;
                }
                const field = flexNode.tryGetField(brand(String(storedKey)));
                if (field !== undefined) {
                    return tryGetTreeNodeForField(field);
                }
                return undefined;
            }
            case NodeKind.Leaf: {
                fail(0xbc3 /* Leaf schema associated with non-leaf tree node. */);
            }
            default: {
                unreachableCase(schema.kind);
            }
        }
    },
    children(node) {
        const flexNode = getOrCreateInnerNode(node);
        debugAssert(() => !flexNode.context.isDisposed() || "The provided tree node has been disposed.");
        const schema = treeNodeApi.schema(node);
        const result = [];
        switch (schema.kind) {
            case NodeKind.Array: {
                const sequence = flexNode.tryGetField(EmptyKey);
                if (sequence === undefined) {
                    break;
                }
                for (let index = 0; index < sequence.length; index++) {
                    const childFlexTree = sequence.at(index);
                    assert(childFlexTree !== undefined, 0xbc4 /* Sequence child was undefined. */);
                    const childTree = getOrCreateNodeFromInnerUnboxedNode(childFlexTree);
                    result.push([index, childTree]);
                }
                break;
            }
            case NodeKind.Map: {
                for (const [key, flexField] of flexNode.fields) {
                    const childTreeNode = tryGetTreeNodeForField(flexField);
                    if (childTreeNode !== undefined) {
                        result.push([key, childTreeNode]);
                    }
                }
                break;
            }
            case NodeKind.Object: {
                assert(isObjectNodeSchema(schema), 0xbc5 /* Expected object schema. */);
                for (const [propertyKey, fieldSchema] of schema.fields) {
                    const storedKey = fieldSchema.storedKey;
                    const flexField = flexNode.tryGetField(brand(String(storedKey)));
                    if (flexField !== undefined) {
                        const childTreeNode = tryGetTreeNodeForField(flexField);
                        assert(childTreeNode !== undefined, 0xbc6 /* Expected child tree node for field. */);
                        result.push([propertyKey, childTreeNode]);
                    }
                }
                break;
            }
            case NodeKind.Leaf: {
                fail(0xbc7 /* Leaf schema associated with non-leaf tree node. */);
            }
            default: {
                unreachableCase(schema.kind);
            }
        }
        return result;
    },
};
function exportConcise(node, options) {
    if (node === undefined) {
        return undefined;
    }
    const config = { ...options };
    const cursor = borrowCursorFromTreeNodeOrValue(node);
    return conciseFromCursor(cursor, tryGetSchema(node) ?? 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 (isTreeValue(node)) {
        return cursorFromVerbose(node, {});
    }
    const kernel = 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 cursorForMapTreeField([]);
    }
    const cursor = borrowCursorFromTreeNodeOrValue(node);
    // TODO: avoid copy: borrow cursor from field instead.
    const mapTree = mapTreeFromCursor(cursor);
    return cursorForMapTreeField([mapTree]);
}
//# sourceMappingURL=treeAlpha.js.map