"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 leafNodeSchema_js_1 = require("../leafNodeSchema.js");
const index_js_4 = require("../node-kinds/index.js");
/**
 * Builds an {@link CustomTree} from a cursor in Nodes mode.
 *
 * @param reader - The cursor to read from.
 * @param options - Options for how to interpret the tree.
 * @param storedSchema - Schema which the cursor must comply with.
 * Must be be possible to map to a view schema (mainly that sequences can only occur in the special ArrayNode pattern).
 * Must include even unknown optional fields.
 * @param schema - View schema used to derive the property keys for fields when `options` selects them via {@link TreeEncodingOptions.useStoredKeys}.
 * @param childHandler - Function to handle children of the cursor.
 *
 * @remarks
 * This can handle unknown optional fields only because they are included in the `storedSchema` and `schema` is only needed when using property keys, which also skips unknown optional fields.
 */
function customFromCursor(reader, options, storedSchema, schema, childHandler) {
    const type = reader.type;
    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 */);
            const nodeSchema = storedSchema.get(type) ?? (0, internal_1.fail)(0xb2e /* missing schema for type in cursor */);
            const arrayTypes = tryStoredSchemaAsArray(nodeSchema);
            if (arrayTypes !== undefined) {
                const fields = (0, index_js_1.inCursorField)(reader, index_js_1.EmptyKey, () => (0, index_js_1.mapCursorField)(reader, () => childHandler(reader, options, storedSchema, schema)));
                return fields;
            }
            else {
                const fields = {};
                (0, index_js_1.forEachField)(reader, () => {
                    (0, internal_1.assert)(reader.getFieldLength() === 1, 0xa19 /* invalid children number */);
                    const storedKey = reader.getFieldKey();
                    let key;
                    if (!options.useStoredKeys) {
                        const viewSchema = schema.get(type) ?? (0, internal_1.fail)(0xbff /* missing schema for type in cursor */);
                        if ((0, index_js_4.isObjectNodeSchema)(viewSchema)) {
                            const propertyKey = viewSchema.storedKeyToPropertyKey.get(storedKey);
                            if (propertyKey === undefined) {
                                (0, internal_1.assert)(viewSchema.allowUnknownOptionalFields, 0xc00 /* found unknown field where not allowed */);
                                // Skip unknown optional fields when using property keys.
                                return;
                            }
                            key = propertyKey;
                        }
                        else {
                            key = storedKey;
                        }
                    }
                    else {
                        key = storedKey;
                    }
                    reader.enterNode(0);
                    fields[key] = childHandler(reader, options, storedSchema, schema);
                    reader.exitNode();
                });
                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;
/**
 * Checks if `schema` could correspond to a simple-tree array node.
 * If it is an array schema, returns the allowed types for the array field.
 * Otherwise returns `undefined`.
 * @remarks
 * If the schema was defined by the public API, this will be accurate since there is no way to define an object node with a sequence field.
 */
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) {
            // This assert can only be hit by schema created not using the public API surface.
            // If at some point this case needs to be tolerated, it can be replaced by "return undefined"
            (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