"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertNodeSchema = exports.convertAllowedTypes = exports.convertField = exports.getFlexSchema = exports.toFlexSchema = exports.toFlexConfig = void 0;
/* eslint-disable import/no-internal-modules */
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../feature-libraries/index.js");
const flexList_js_1 = require("../feature-libraries/typed-schema/flexList.js");
const index_js_2 = require("../util/index.js");
const proxies_js_1 = require("./proxies.js");
const schemaCaching_js_1 = require("./schemaCaching.js");
const schemaTypes_js_1 = require("./schemaTypes.js");
const toMapTree_js_1 = require("./toMapTree.js");
/**
 * Returns a cursor (in nodes mode) for the root node.
 *
 * @privateRemarks
 * Ideally this would work on any node, not just the root,
 * and the schema would come from the unhydrated node.
 * For now though, this is the only case that's needed, and we do have the data to make it work, so this is fine.
 */
function cursorFromUnhydratedRoot(schema, tree) {
    const data = (0, proxies_js_1.extractFactoryContent)(tree);
    const normalizedFieldSchema = (0, schemaTypes_js_1.normalizeFieldSchema)(schema);
    return ((0, toMapTree_js_1.cursorFromNodeData)(data, normalizedFieldSchema.allowedTypes) ??
        (0, index_js_2.fail)("failed to decode tree"));
}
function toFlexConfig(config) {
    const unhydrated = config.initialTree();
    const initialTree = unhydrated === undefined
        ? undefined
        : [cursorFromUnhydratedRoot(config.schema, unhydrated)];
    return {
        schema: toFlexSchema(config.schema),
        initialTree,
    };
}
exports.toFlexConfig = toFlexConfig;
/**
 * Generate a {@link FlexTreeSchema} with `root` as the root field.
 *
 * This also has the side effect of populating the cached view schema on the class-based schema.
 */
function toFlexSchema(root) {
    const schemaMap = new Map();
    const field = convertField(schemaMap, root);
    const nodeSchema = new Map((0, index_js_2.mapIterable)(schemaMap, ([key, value]) => {
        const schema = value.toFlex();
        const classSchema = (0, schemaCaching_js_1.tryGetSimpleNodeSchema)(schema);
        if (classSchema === undefined) {
            (0, internal_1.assert)((0, index_js_1.schemaIsLeaf)(schema), 0x83e /* invalid leaf */);
        }
        else {
            (0, internal_1.assert)((0, schemaCaching_js_1.cachedFlexSchemaFromClassSchema)(classSchema) === schema, 0x83f /* mismatched schema */);
        }
        return [key, schema];
    }));
    const typed = {
        nodeSchema,
        adapters: {},
        rootFieldSchema: field,
        policy: index_js_1.defaultSchemaPolicy,
    };
    return typed;
}
exports.toFlexSchema = toFlexSchema;
/**
 * Return a flex schema for the provided class schema.
 *
 * This also has the side effect of populating the cached view schema on the class based schema.
 */
function getFlexSchema(root) {
    const treeSchema = toFlexSchema(root);
    return treeSchema.rootFieldSchema.monomorphicChildType ?? (0, index_js_2.fail)("root should be monomorphic");
}
exports.getFlexSchema = getFlexSchema;
/**
 * Normalizes an {@link ImplicitFieldSchema} into a {@link TreeFieldSchema}.
 */
function convertField(schemaMap, schema) {
    let kind;
    let types;
    if (schema instanceof schemaTypes_js_1.FieldSchema) {
        kind = convertFieldKind.get(schema.kind) ?? (0, index_js_2.fail)("Invalid field kind");
        types = schema.allowedTypes;
    }
    else {
        kind = index_js_1.FieldKinds.required;
        types = schema;
    }
    const allowedTypes = convertAllowedTypes(schemaMap, types);
    return index_js_1.FlexFieldSchema.create(kind, allowedTypes);
}
exports.convertField = convertField;
const convertFieldKind = new Map([
    [schemaTypes_js_1.FieldKind.Optional, index_js_1.FieldKinds.optional],
    [schemaTypes_js_1.FieldKind.Required, index_js_1.FieldKinds.required],
]);
/**
 * Normalizes an {@link ImplicitAllowedTypes} into an {@link AllowedTypes}.
 */
function convertAllowedTypes(schemaMap, schema) {
    if ((0, index_js_2.isReadonlyArray)(schema)) {
        return (0, flexList_js_1.normalizeFlexListEager)(schema).map((item) => convertNodeSchema(schemaMap, item));
    }
    return [convertNodeSchema(schemaMap, schema)];
}
exports.convertAllowedTypes = convertAllowedTypes;
const builder = { name: "simple schema" };
/**
 * Converts a {@link TreeNodeSchema} into a {@link FlexTreeNodeSchema}.
 * Ensures all types reachable from `schema` are included in `schemaMap`.
 *
 * Return value (and entries in map) are lazy to allow recursive types to work.
 * This laziness does NOT extend to adding entries to `schemaMap`:
 * all referenced types are added to it before this function returns.
 */
function convertNodeSchema(schemaMap, schema) {
    const fromMap = schemaMap.get((0, index_js_2.brand)(schema.identifier));
    if (fromMap !== undefined) {
        if (fromMap.original !== schema) {
            // Use JSON.stringify to quote and escape string.
            throw new Error(`Multiple schema encountered with the identifier ${JSON.stringify(schema.identifier)}. Remove or rename them to avoid the collision.`);
        }
        return fromMap.toFlex;
    }
    const toFlex = () => {
        let out;
        const kind = schema.kind;
        switch (kind) {
            case schemaTypes_js_1.NodeKind.Leaf: {
                const cached = (0, schemaCaching_js_1.cachedFlexSchemaFromClassSchema)(schema) ??
                    (0, index_js_2.fail)("leaf schema should be pre-cached");
                (0, internal_1.assert)((0, index_js_1.schemaIsLeaf)(cached), 0x840 /* expected leaf */);
                return cached;
            }
            case schemaTypes_js_1.NodeKind.Map: {
                const fieldInfo = schema.info;
                const field = index_js_1.FlexFieldSchema.create(index_js_1.FieldKinds.optional, convertAllowedTypes(schemaMap, fieldInfo));
                // Lookup of cached schema is done here instead of before since walking the schema recursively to populate schemaMap is still required.
                const cached = (0, schemaCaching_js_1.cachedFlexSchemaFromClassSchema)(schema);
                out = cached ?? index_js_1.FlexMapNodeSchema.create(builder, (0, index_js_2.brand)(schema.identifier), field);
                break;
            }
            case schemaTypes_js_1.NodeKind.Array: {
                const fieldInfo = schema.info;
                const field = index_js_1.FlexFieldSchema.create(index_js_1.FieldKinds.sequence, convertAllowedTypes(schemaMap, fieldInfo));
                const cached = (0, schemaCaching_js_1.cachedFlexSchemaFromClassSchema)(schema);
                out =
                    cached ?? index_js_1.FlexFieldNodeSchema.create(builder, (0, index_js_2.brand)(schema.identifier), field);
                break;
            }
            case schemaTypes_js_1.NodeKind.Object: {
                const info = schema.info;
                const fields = Object.create(null);
                for (const [viewKey, implicitFieldSchema] of Object.entries(info)) {
                    // If a `stored key` was provided, use it as the key in the flex schema.
                    // Otherwise, use the view key.
                    const flexKey = (0, schemaTypes_js_1.getStoredKey)(viewKey, implicitFieldSchema);
                    // This code has to be careful to avoid assigning to __proto__ or similar built-in fields.
                    Object.defineProperty(fields, flexKey, {
                        enumerable: true,
                        configurable: false,
                        writable: false,
                        value: convertField(schemaMap, implicitFieldSchema),
                    });
                }
                const cached = (0, schemaCaching_js_1.cachedFlexSchemaFromClassSchema)(schema);
                out =
                    cached ??
                        index_js_1.FlexObjectNodeSchema.create(builder, (0, index_js_2.brand)(schema.identifier), fields);
                break;
            }
            default:
                (0, internal_1.unreachableCase)(kind);
        }
        (0, internal_1.assert)(out instanceof index_js_1.TreeNodeSchemaBase, 0x841 /* invalid schema produced */);
        {
            const cached = (0, schemaCaching_js_1.cachedFlexSchemaFromClassSchema)(schema);
            if (cached !== undefined) {
                (0, internal_1.assert)((0, schemaCaching_js_1.cachedFlexSchemaFromClassSchema)(schema) === out, 0x842 /* incorrect flexSchemaSymbol */);
            }
            else {
                (0, schemaCaching_js_1.setFlexSchemaFromClassSchema)(schema, out);
            }
        }
        return out;
    };
    schemaMap.set((0, index_js_2.brand)(schema.identifier), { original: schema, toFlex });
    return toFlex;
}
exports.convertNodeSchema = convertNodeSchema;
//# sourceMappingURL=toFlexSchema.js.map