"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.replaceVerboseTreeHandles = 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 internal_3 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../../util/index.js");
const index_js_3 = require("../../feature-libraries/index.js");
const leafNodeSchema_js_1 = require("../leafNodeSchema.js");
const index_js_4 = require("../node-kinds/index.js");
const customTree_js_1 = require("./customTree.js");
const createContext_js_1 = require("../createContext.js");
/**
 * Use info from `schema` to convert `options` to {@link SchemalessParseOptions}.
 */
function applySchemaToParserOptions(schema, options) {
    const config = {
        useStoredKeys: false,
        ...options,
    };
    const context = (0, createContext_js_1.getUnhydratedContext)(schema);
    return {
        keyConverter: config.useStoredKeys
            ? undefined
            : {
                encode: (type, key) => {
                    // translate stored key into property key.
                    const simpleNodeSchema = context.schema.get((0, index_js_2.brand)(type)) ?? (0, internal_2.fail)(0xb39 /* missing schema */);
                    if ((0, index_js_4.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 simpleNodeSchema = context.schema.get((0, index_js_2.brand)(type)) ?? (0, customTree_js_1.unknownTypeError)(type);
                    if ((0, index_js_4.isObjectNodeSchema)(simpleNodeSchema)) {
                        const info = simpleNodeSchema.flexKeyMap.get(inputKey);
                        if (info === undefined) {
                            throw new internal_3.UsageError(`Failed to parse VerboseTree due to unexpected key ${JSON.stringify(inputKey)} on type ${JSON.stringify(type)}.`);
                        }
                        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_3.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_3.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: (node) => {
            return (0, index_js_3.isTreeValue)(node) ? node : undefined;
        },
        type: (node) => {
            switch (typeof node) {
                case "number":
                    return (0, index_js_2.brand)(leafNodeSchema_js_1.numberSchema.identifier);
                case "string":
                    return (0, index_js_2.brand)(leafNodeSchema_js_1.stringSchema.identifier);
                case "boolean":
                    return (0, index_js_2.brand)(leafNodeSchema_js_1.booleanSchema.identifier);
                default:
                    if (node === null) {
                        return (0, index_js_2.brand)(leafNodeSchema_js_1.nullSchema.identifier);
                    }
                    if ((0, internal_1.isFluidHandle)(node)) {
                        return (0, index_js_2.brand)(leafNodeSchema_js_1.handleSchema.identifier);
                    }
                    return (0, index_js_2.brand)(node.type);
            }
        },
        keysFromNode: (node) => {
            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: (node, key) => {
            // 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, context, options) {
    const config = {
        useStoredKeys: false,
        ...options,
    };
    const storedSchemaMap = context.flexContext.schema.nodeSchema;
    const schemaMap = context.schema;
    return verboseFromCursorInner(reader, config, storedSchemaMap, schemaMap);
}
exports.verboseFromCursor = verboseFromCursor;
function verboseFromCursorInner(reader, options, storedSchema, schema) {
    const fields = (0, customTree_js_1.customFromCursor)(reader, options, storedSchema, schema, verboseFromCursorInner);
    const nodeSchema = storedSchema.get(reader.type) ?? (0, internal_2.fail)(0xb3c /* missing schema for type in cursor */);
    if (nodeSchema instanceof index_js_1.LeafNodeStoredSchema) {
        return fields;
    }
    return {
        type: reader.type,
        fields: fields,
    };
}
/**
 * Clones tree, replacing any handles.
 * @remarks
 * A strongly types version of {@link replaceHandles}.
 * @alpha
 */
function replaceVerboseTreeHandles(tree, replacer) {
    return (0, customTree_js_1.replaceHandles)(tree, replacer);
}
exports.replaceVerboseTreeHandles = replaceVerboseTreeHandles;
//# sourceMappingURL=verboseTree.js.map