"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.verboseFromCursor = exports.fieldCursorFromVerbose = exports.cursorFromVerbose = exports.applySchemaToParserOptions = void 0;
const internal_1 = require("@fluidframework/runtime-utils/internal");
const internal_2 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../../util/index.js");
const index_js_3 = require("../core/index.js");
const index_js_4 = require("../../feature-libraries/index.js");
const leafNodeSchema_js_1 = require("../leafNodeSchema.js");
const toFlexSchema_js_1 = require("../toFlexSchema.js");
const objectNodeTypes_js_1 = require("../objectNodeTypes.js");
const walkSchema_js_1 = require("../walkSchema.js");
/**
 * Use info from `schema` to convert `options` to {@link SchemalessParseOptions}.
 */
function applySchemaToParserOptions(schema, options) {
    const config = {
        useStoredKeys: false,
        ...options,
    };
    // TODO: should provide a way to look up schema by name efficiently without converting to flex tree schema and back.
    // Maybe cache identifier->schema map on simple tree schema lazily.
    const flexSchema = (0, toFlexSchema_js_1.toFlexSchema)(schema);
    return {
        valueConverter: config.valueConverter,
        keyConverter: config.useStoredKeys
            ? undefined
            : {
                encode: (type, key) => {
                    // translate stored key into property key.
                    const flexNodeSchema = flexSchema.nodeSchema.get((0, index_js_2.brand)(type)) ?? (0, index_js_2.fail)("missing schema");
                    const simpleNodeSchema = (0, index_js_3.getSimpleNodeSchema)(flexNodeSchema);
                    if ((0, objectNodeTypes_js_1.isObjectNodeSchema)(simpleNodeSchema)) {
                        const propertyKey = simpleNodeSchema.storedKeyToPropertyKey.get(key);
                        if (propertyKey !== undefined) {
                            return propertyKey;
                        }
                        // Looking up an out of schema key.
                        // This must point to a non-existent field.
                        // It's possible that the key, if we returned it unmodified, could point to some data
                        // (for example if looking up a key which is a stored key already when using property keys).
                        // Thus return an arbitrary key that was selected randomly, so should not exist on non-adversarial data:
                        const arbitrary = "arbitrary unused key: fe71614a-bf3e-43b3-b7b0-4cef39538e90";
                        (0, internal_2.assert)(!simpleNodeSchema.storedKeyToPropertyKey.has((0, index_js_2.brand)(arbitrary)), 0xa13 /* arbitrarily selected unused key was actually used */);
                        return arbitrary;
                    }
                    return key;
                },
                parse: (type, inputKey) => {
                    const flexNodeSchema = flexSchema.nodeSchema.get((0, index_js_2.brand)(type)) ?? (0, index_js_2.fail)("missing schema");
                    const simpleNodeSchema = (0, index_js_3.getSimpleNodeSchema)(flexNodeSchema);
                    if ((0, objectNodeTypes_js_1.isObjectNodeSchema)(simpleNodeSchema)) {
                        const info = simpleNodeSchema.flexKeyMap.get(inputKey) ?? (0, index_js_2.fail)("missing field info");
                        return info.storedKey;
                    }
                    return (0, index_js_2.brand)(inputKey);
                },
            },
    };
}
exports.applySchemaToParserOptions = applySchemaToParserOptions;
/**
 * Used to read a VerboseTree as a node cursor.
 *
 * @returns an {@link ITreeCursorSynchronous} for a single node in nodes mode.
 */
function cursorFromVerbose(data, options) {
    return (0, index_js_4.stackTreeNodeCursor)(verboseTreeAdapter(options), data);
}
exports.cursorFromVerbose = cursorFromVerbose;
/**
 * Used to read a VerboseTree[] as a field cursor.
 *
 * @returns an {@link ITreeCursorSynchronous} for a single field in fields mode.
 */
function fieldCursorFromVerbose(data, options) {
    return (0, index_js_4.stackTreeFieldCursor)(verboseTreeAdapter(options), { type: index_js_1.aboveRootPlaceholder, fields: data }, (0, index_js_1.keyAsDetachedField)(index_js_1.EmptyKey));
}
exports.fieldCursorFromVerbose = fieldCursorFromVerbose;
function verboseTreeAdapter(options) {
    return {
        value: (input) => {
            const node = options.valueConverter(input);
            return (0, index_js_4.isTreeValue)(node) ? node : undefined;
        },
        type: (input) => {
            const node = options.valueConverter(input);
            switch (typeof node) {
                case "number":
                    return leafNodeSchema_js_1.numberSchema.identifier;
                case "string":
                    return leafNodeSchema_js_1.stringSchema.identifier;
                case "boolean":
                    return leafNodeSchema_js_1.booleanSchema.identifier;
                default:
                    if (node === null) {
                        return leafNodeSchema_js_1.nullSchema.identifier;
                    }
                    if ((0, internal_1.isFluidHandle)(node)) {
                        return leafNodeSchema_js_1.handleSchema.identifier;
                    }
                    return node.type;
            }
        },
        keysFromNode: (input) => {
            const node = options.valueConverter(input);
            switch (typeof node) {
                case "object": {
                    if (node === null) {
                        return [];
                    }
                    if ((0, internal_1.isFluidHandle)(node)) {
                        return [];
                    }
                    if (Array.isArray(node.fields)) {
                        return node.fields.length === 0 ? [] : [index_js_1.EmptyKey];
                    }
                    const inputKeys = Object.keys(node.fields);
                    const converter = options.keyConverter;
                    if (converter === undefined) {
                        return inputKeys;
                    }
                    return inputKeys.map((k) => converter.parse(node.type, k));
                }
                default:
                    return [];
            }
        },
        getFieldFromNode: (input, key) => {
            const node = options.valueConverter(input);
            // Object.prototype.hasOwnProperty can return true for strings (ex: with key "0"), so we have to filter them out.
            // Rather than just special casing strings, we can handle them with an early return for all primitives.
            if (typeof node !== "object") {
                return [];
            }
            if (node === null) {
                return [];
            }
            if ((0, internal_1.isFluidHandle)(node)) {
                return [];
            }
            if (Array.isArray(node.fields)) {
                return key === index_js_1.EmptyKey ? node.fields : [];
            }
            const convertedKey = options.keyConverter === undefined ? key : options.keyConverter.encode(node.type, key);
            if (Object.prototype.hasOwnProperty.call(node.fields, convertedKey)) {
                const field = node.fields[convertedKey];
                return field === undefined ? [] : [field];
            }
            return [];
        },
    };
}
/**
 * Used to read a node cursor as a VerboseTree.
 */
function verboseFromCursor(reader, rootSchema, options) {
    const config = {
        useStoredKeys: false,
        ...options,
    };
    const schemaMap = new Map();
    (0, walkSchema_js_1.walkFieldSchema)(rootSchema, {
        node(schema) {
            schemaMap.set(schema.identifier, schema);
        },
    });
    return verboseFromCursorInner(reader, config, schemaMap);
}
exports.verboseFromCursor = verboseFromCursor;
function verboseFromCursorInner(reader, options, schema) {
    const type = reader.type;
    const nodeSchema = schema.get(type) ?? (0, index_js_2.fail)("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_2.assert)(reader.value !== undefined, 0xa14 /* out of schema: missing value */);
            (0, internal_2.assert)(!(0, internal_1.isFluidHandle)(reader.value), 0xa15 /* out of schema: unexpected FluidHandle */);
            return reader.value;
        case leafNodeSchema_js_1.handleSchema.identifier:
            (0, internal_2.assert)(reader.value !== undefined, 0xa16 /* out of schema: missing value */);
            (0, internal_2.assert)((0, internal_1.isFluidHandle)(reader.value), 0xa17 /* out of schema: unexpected FluidHandle */);
            return options.valueConverter(reader.value);
        default: {
            (0, internal_2.assert)(reader.value === undefined, 0xa18 /* out of schema: unexpected value */);
            if (nodeSchema.kind === index_js_3.NodeKind.Array) {
                const fields = (0, index_js_1.inCursorField)(reader, index_js_1.EmptyKey, () => (0, index_js_1.mapCursorField)(reader, () => verboseFromCursorInner(reader, options, schema)));
                return { type, fields };
            }
            else {
                const fields = {};
                (0, index_js_1.forEachField)(reader, () => {
                    const children = (0, index_js_1.mapCursorField)(reader, () => verboseFromCursorInner(reader, options, schema));
                    if (children.length === 1) {
                        const storedKey = reader.getFieldKey();
                        const key = (0, objectNodeTypes_js_1.isObjectNodeSchema)(nodeSchema) && !options.useStoredKeys
                            ? nodeSchema.storedKeyToPropertyKey.get(storedKey) ??
                                (0, index_js_2.fail)("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_2.assert)(children.length === 0, 0xa19 /* invalid children number */);
                    }
                });
                return { type, fields };
            }
        }
    }
}
//# sourceMappingURL=verboseTree.js.map