"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setFlexSchemaFromClassSchema = exports.cachedFlexSchemaFromClassSchema = exports.flexSchemaSymbol = exports.convertNodeSchema = exports.convertAllowedTypes = exports.convertField = exports.getFlexSchema = exports.toFlexSchema = exports.toFlexConfig = exports.cursorFromUnhydratedRoot = void 0;
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
/* eslint-disable import/no-internal-modules */
const core_utils_1 = require("@fluidframework/core-utils");
const feature_libraries_1 = require("../feature-libraries");
const util_1 = require("../util");
const flexList_1 = require("../feature-libraries/typed-schema/flexList");
const proxies_1 = require("../simple-tree/proxies");
const core_1 = require("../core");
const toMapTree_1 = require("../simple-tree/toMapTree");
const schemaTypes_1 = require("./schemaTypes");
/**
 * 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_1.extractFactoryContent)(tree);
    return ((0, toMapTree_1.cursorFromNodeData)(data.content, { schema }, schema.rootFieldSchema.types) ??
        (0, util_1.fail)("failed to decode tree"));
}
exports.cursorFromUnhydratedRoot = cursorFromUnhydratedRoot;
function toFlexConfig(config) {
    const schema = toFlexSchema(config.schema);
    const unhydrated = config.initialTree();
    const initialTree = unhydrated === undefined ? undefined : [cursorFromUnhydratedRoot(schema, unhydrated)];
    return {
        allowedSchemaModifications: core_1.AllowedUpdateType.None,
        schema,
        initialTree,
    };
}
exports.toFlexConfig = toFlexConfig;
/**
 * Generate a {@link TreeSchema} 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, util_1.mapIterable)(schemaMap, ([key, value]) => {
        const schema = value();
        const classSchema = (0, proxies_1.getClassSchema)(schema);
        if (classSchema === undefined) {
            (0, core_utils_1.assert)((0, feature_libraries_1.schemaIsLeaf)(schema), "invalid leaf");
        }
        else {
            (0, core_utils_1.assert)(cachedFlexSchemaFromClassSchema(classSchema) === schema, "mismatched schema");
        }
        return [key, value()];
    }));
    const typed = {
        nodeSchema,
        adapters: {},
        rootFieldSchema: field,
        policy: feature_libraries_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, util_1.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_1.FieldSchema) {
        kind = convertFieldKind.get(schema.kind) ?? (0, util_1.fail)("Invalid field kind");
        types = schema.allowedTypes;
    }
    else {
        kind = feature_libraries_1.FieldKinds.required;
        types = schema;
    }
    const allowedTypes = convertAllowedTypes(schemaMap, types);
    return feature_libraries_1.TreeFieldSchema.create(kind, allowedTypes);
}
exports.convertField = convertField;
const convertFieldKind = new Map([
    [schemaTypes_1.FieldKind.Optional, feature_libraries_1.FieldKinds.optional],
    [schemaTypes_1.FieldKind.Required, feature_libraries_1.FieldKinds.required],
]);
/**
 * Normalizes an {@link ImplicitAllowedTypes} into an {@link AllowedTypes}.
 */
function convertAllowedTypes(schemaMap, schema) {
    if ((0, util_1.isReadonlyArray)(schema)) {
        return (0, flexList_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) {
    // TODO: nice error if multiple schema have the same identifier
    const final = (0, util_1.getOrCreate)(schemaMap, schema.identifier, () => () => {
        let out;
        const kind = schema.kind;
        switch (kind) {
            case schemaTypes_1.NodeKind.Leaf: {
                const cached = cachedFlexSchemaFromClassSchema(schema) ??
                    (0, util_1.fail)("leaf schema should be pre-cached");
                (0, core_utils_1.assert)((0, feature_libraries_1.schemaIsLeaf)(cached), "expected leaf");
                return cached;
            }
            case schemaTypes_1.NodeKind.Map: {
                const fieldInfo = schema.info;
                const field = feature_libraries_1.TreeFieldSchema.create(feature_libraries_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 = cachedFlexSchemaFromClassSchema(schema);
                out = cached ?? feature_libraries_1.MapNodeSchema.create(builder, (0, util_1.brand)(schema.identifier), field);
                break;
            }
            case schemaTypes_1.NodeKind.List: {
                const fieldInfo = schema.info;
                const field = feature_libraries_1.TreeFieldSchema.create(feature_libraries_1.FieldKinds.sequence, convertAllowedTypes(schemaMap, fieldInfo));
                const cached = cachedFlexSchemaFromClassSchema(schema);
                out =
                    cached ?? feature_libraries_1.FieldNodeSchema.create(builder, (0, util_1.brand)(schema.identifier), field);
                break;
            }
            case schemaTypes_1.NodeKind.Object: {
                const info = schema.info;
                const fields = Object.create(null);
                for (const [key, value] of Object.entries(info)) {
                    // This code has to be careful to avoid assigning to __proto__ or similar built-in fields.
                    Object.defineProperty(fields, key, {
                        enumerable: true,
                        configurable: false,
                        writable: false,
                        value: convertField(schemaMap, value),
                    });
                }
                const cached = cachedFlexSchemaFromClassSchema(schema);
                out =
                    cached ??
                        feature_libraries_1.ObjectNodeSchema.create(builder, (0, util_1.brand)(schema.identifier), fields);
                break;
            }
            default:
                (0, core_utils_1.unreachableCase)(kind);
        }
        (0, core_utils_1.assert)(out instanceof feature_libraries_1.TreeNodeSchemaBase, "invalid schema produced");
        {
            const cached = cachedFlexSchemaFromClassSchema(schema);
            if (cached !== undefined) {
                (0, core_utils_1.assert)(cachedFlexSchemaFromClassSchema(schema) === out, "incorrect flexSchemaSymbol");
            }
            else {
                setFlexSchemaFromClassSchema(schema, out);
            }
        }
        out[proxies_1.simpleSchemaSymbol] = schema;
        return out;
    });
    return final;
}
exports.convertNodeSchema = convertNodeSchema;
/**
 * A symbol for storing FlexTreeSchema on TreeNodeSchema.
 * Eagerly set on leaves, and lazily set for other cases.
 */
exports.flexSchemaSymbol = Symbol(`flexSchema`);
function cachedFlexSchemaFromClassSchema(schema) {
    return schema[exports.flexSchemaSymbol];
}
exports.cachedFlexSchemaFromClassSchema = cachedFlexSchemaFromClassSchema;
function setFlexSchemaFromClassSchema(simple, flex) {
    simple[exports.flexSchemaSymbol] = flex;
}
exports.setFlexSchemaFromClassSchema = setFlexSchemaFromClassSchema;
//# sourceMappingURL=toFlexSchema.js.map