"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.unknownTypeError = exports.replaceHandles = exports.tryStoredSchemaAsArray = exports.customFromCursorStored = exports.customFromCursor = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/runtime-utils/internal");
const internal_3 = require("@fluidframework/telemetry-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 index_js_4 = require("../core/index.js");
const leafNodeSchema_js_1 = require("../leafNodeSchema.js");
const index_js_5 = require("../node-kinds/index.js");
/**
 * Builds an {@link CustomTree} from a cursor in Nodes mode.
 */
function customFromCursor(reader, options, schema, childHandler) {
    const type = reader.type;
    const nodeSchema = schema.get(type) ?? (0, internal_1.fail)(0xb2e /* missing schema for type in cursor */);
    switch (type) {
        case leafNodeSchema_js_1.numberSchema.identifier:
        case leafNodeSchema_js_1.booleanSchema.identifier:
        case leafNodeSchema_js_1.nullSchema.identifier:
        case leafNodeSchema_js_1.stringSchema.identifier:
            (0, internal_1.assert)(reader.value !== undefined, 0xa50 /* out of schema: missing value */);
            (0, internal_1.assert)(!(0, internal_2.isFluidHandle)(reader.value), 0xa51 /* out of schema: unexpected FluidHandle */);
            return reader.value;
        case leafNodeSchema_js_1.handleSchema.identifier:
            (0, internal_1.assert)(reader.value !== undefined, 0xa52 /* out of schema: missing value */);
            (0, internal_1.assert)((0, internal_2.isFluidHandle)(reader.value), 0xa53 /* out of schema: expected FluidHandle */);
            return reader.value;
        default: {
            (0, internal_1.assert)(reader.value === undefined, 0xa54 /* out of schema: unexpected value */);
            if (nodeSchema.kind === index_js_4.NodeKind.Array) {
                const fields = (0, index_js_1.inCursorField)(reader, index_js_1.EmptyKey, () => (0, index_js_1.mapCursorField)(reader, () => childHandler(reader, options, schema)));
                return fields;
            }
            else {
                const fields = {};
                (0, index_js_1.forEachField)(reader, () => {
                    const children = (0, index_js_1.mapCursorField)(reader, () => childHandler(reader, options, schema));
                    if (children.length === 1) {
                        const storedKey = reader.getFieldKey();
                        const key = (0, index_js_5.isObjectNodeSchema)(nodeSchema) && !options.useStoredKeys
                            ? (nodeSchema.storedKeyToPropertyKey.get(storedKey) ??
                                (0, internal_1.fail)(0xb2f /* missing property key */))
                            : storedKey;
                        // Length is checked above.
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        fields[key] = children[0];
                    }
                    else {
                        (0, internal_1.assert)(children.length === 0, 0xa19 /* invalid children number */);
                    }
                });
                return fields;
            }
        }
    }
}
exports.customFromCursor = customFromCursor;
/**
 * Builds an {@link CustomTree} from a cursor in Nodes mode.
 * @remarks
 * Uses stored keys and stored schema.
 */
function customFromCursorStored(reader, schema, childHandler) {
    const type = reader.type;
    const nodeSchema = schema.get(type) ?? (0, internal_1.fail)(0xb30 /* missing schema for type in cursor */);
    if (nodeSchema instanceof index_js_1.LeafNodeStoredSchema) {
        (0, internal_1.assert)((0, index_js_2.valueSchemaAllows)(nodeSchema.leafValue, reader.value), 0xa9c /* invalid value */);
        return reader.value;
    }
    (0, internal_1.assert)(reader.value === undefined, 0xa9d /* out of schema: unexpected value */);
    const arrayTypes = tryStoredSchemaAsArray(nodeSchema);
    if (arrayTypes !== undefined) {
        const field = (0, index_js_1.inCursorField)(reader, index_js_1.EmptyKey, () => (0, index_js_1.mapCursorField)(reader, () => childHandler(reader, schema)));
        return field;
    }
    const fields = {};
    (0, index_js_1.forEachField)(reader, () => {
        const children = (0, index_js_1.mapCursorField)(reader, () => childHandler(reader, schema));
        if (children.length === 1) {
            const storedKey = reader.getFieldKey();
            // Length is checked above.
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            fields[storedKey] = children[0];
        }
        else {
            (0, internal_1.assert)(children.length === 0, 0xa9e /* invalid children number */);
        }
    });
    return fields;
}
exports.customFromCursorStored = customFromCursorStored;
/**
 * Assumes `schema` corresponds to a simple-tree schema.
 * If it is an array schema, returns the allowed types for the array field.
 * Otherwise returns `undefined`.
 */
function tryStoredSchemaAsArray(schema) {
    if (schema instanceof index_js_1.ObjectNodeStoredSchema) {
        const empty = schema.getFieldSchema(index_js_1.EmptyKey);
        if (empty.kind === index_js_2.FieldKinds.sequence.identifier) {
            (0, internal_1.assert)(schema.objectNodeFields.size === 1, 0xa9f /* invalid schema */);
            return empty.types;
        }
    }
}
exports.tryStoredSchemaAsArray = tryStoredSchemaAsArray;
/**
 * Clones tree, replacing any handles.
 * @remarks
 * This can be useful converting data containing handles to JSON compatible formats,
 * or just asserting that data does not contain handles.
 *
 * Reversing this replacement depends on how the replacer encodes handles, and can often be impossible if the replacer
 * does not have all the necessary context to restore the handles
 * (e.g. if the handles are something insufficiently descriptive,
 * if data referenced by the handle got garbage collected,
 * if the encoded form of the handle can't be differentiated from other data,
 * or the replacer doesn't have access to the correct Fluid container to to restore them from).
 *
 * Code attempting to reverse this replacement may want to use {@link cloneWithReplacements}.
 * @alpha
 */
function replaceHandles(tree, replacer) {
    return (0, index_js_3.cloneWithReplacements)(tree, "", (key, value) => {
        // eslint-disable-next-line unicorn/prefer-ternary
        if ((0, internal_2.isFluidHandle)(value)) {
            return { clone: false, value: replacer(value) };
        }
        else {
            return { clone: true, value };
        }
    });
}
exports.replaceHandles = replaceHandles;
/**
 * Throws a `UsageError` indicating that a type is unknown in the current context.
 */
function unknownTypeError(type) {
    throw new internal_3.UsageError(`Failed to parse tree due to occurrence of type ${JSON.stringify(type)} which is not defined in this context.`);
}
exports.unknownTypeError = unknownTypeError;
//# sourceMappingURL=customTree.js.map