"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.arrayNodeStoredSchema = exports.getStoredSchema = exports.convertFieldKind = exports.convertField = exports.toStoredSchema = exports.toUnhydratedSchema = exports.toInitialSchema = exports.toUpgradeSchema = exports.permissiveStoredSchemaGenerationOptions = exports.restrictiveStoredSchemaGenerationOptions = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = 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 index_js_4 = require("./core/index.js");
const fieldSchema_js_1 = require("./fieldSchema.js");
const walkFieldSchema_js_1 = require("./walkFieldSchema.js");
const viewToStoredCache = new WeakMap();
exports.restrictiveStoredSchemaGenerationOptions = {
    includeStaged: () => false,
};
exports.permissiveStoredSchemaGenerationOptions = {
    includeStaged: () => true,
};
/**
 * Converts a {@link ImplicitAnnotatedFieldSchema} into a {@link TreeStoredSchema} for use in schema upgrades.
 *
 * TODO: once upgrades are more flexible, this should take in more options, including the old schema and specific upgrades to enable.
 */
function toUpgradeSchema(root) {
    return toStoredSchema(root, exports.restrictiveStoredSchemaGenerationOptions);
}
exports.toUpgradeSchema = toUpgradeSchema;
/**
 * Converts a {@link ImplicitAnnotatedFieldSchema} into a {@link TreeStoredSchema} for use as initial document schema.
 */
function toInitialSchema(root) {
    return toStoredSchema(root, exports.restrictiveStoredSchemaGenerationOptions);
}
exports.toInitialSchema = toInitialSchema;
/**
 * Converts a {@link ImplicitAnnotatedFieldSchema} into a {@link TreeStoredSchema} to used for unhydrated nodes.
 * @remarks
 * This allows as much as possible, relying on further validation when inserting the content.
 *
 * TODO: this should get additional options to enable support for unknown optional fields.
 */
exports.toUnhydratedSchema = exports.permissiveStoredSchemaGenerationOptions;
/**
 * Converts a {@link ImplicitAnnotatedFieldSchema} into a {@link TreeStoredSchema}.
 *
 * @privateRemarks
 * TODO:#38722 When runtime schema upgrades are implemented, this will need to be updated to check if
 * a staged allowed type has been upgraded and if so, include it in the conversion.
 *
 * @throws
 * Throws a `UsageError` if multiple schemas are encountered with the same identifier.
 */
function toStoredSchema(root, options) {
    const cache = (0, index_js_3.getOrCreate)(viewToStoredCache, options, () => new WeakMap());
    return (0, index_js_3.getOrCreate)(cache, root, () => {
        const normalized = (0, fieldSchema_js_1.normalizeFieldSchema)(root);
        const nodeSchema = new Map();
        (0, walkFieldSchema_js_1.walkFieldSchema)(normalized, {
            node(schema) {
                if (nodeSchema.has((0, index_js_3.brand)(schema.identifier))) {
                    // Use JSON.stringify to quote and escape identifier string.
                    throw new internal_2.UsageError(`Multiple schema encountered with the identifier ${JSON.stringify(schema.identifier)}. Remove or rename them to avoid the collision.`);
                }
                nodeSchema.set((0, index_js_3.brand)(schema.identifier), getStoredSchema(schema, options));
            },
            allowedTypeFilter: (allowedType) => (0, index_js_4.allowedTypeFilter)(allowedType, options),
        });
        const result = {
            nodeSchema,
            rootFieldSchema: convertField(normalized, options),
        };
        return result;
    });
}
exports.toStoredSchema = toStoredSchema;
/**
 * Normalizes an {@link ImplicitFieldSchema} into a {@link TreeFieldSchema}.
 */
function convertField(schema, options) {
    const kind = exports.convertFieldKind.get(schema.kind)?.identifier ?? (0, internal_1.fail)(0xae3 /* Invalid field kind */);
    let types;
    // eslint-disable-next-line unicorn/prefer-ternary
    if (schema instanceof fieldSchema_js_1.FieldSchemaAlpha) {
        types = (0, index_js_4.convertAllowedTypes)(schema.annotatedAllowedTypesNormalized, options);
    }
    else {
        types = schema.allowedTypesIdentifiers;
    }
    return { kind, types, persistedMetadata: schema.persistedMetadata };
}
exports.convertField = convertField;
/**
 * A map that converts {@link FieldKind} to {@link FlexFieldKind}.
 */
exports.convertFieldKind = new Map([
    [fieldSchema_js_1.FieldKind.Optional, index_js_2.FieldKinds.optional],
    [fieldSchema_js_1.FieldKind.Required, index_js_2.FieldKinds.required],
    [fieldSchema_js_1.FieldKind.Identifier, index_js_2.FieldKinds.identifier],
]);
/**
 * Converts a {@link TreeNodeSchema} into a {@link TreeNodeStoredSchema}.
 * @privateRemarks
 * TODO: Persist node metadata once schema FormatV2 is supported.
 *
 * TODO: AB#43548: Using a stored schema for unhydrated flex trees does not handle schema evolution features like "allowUnknownOptionalFields".
 * Usage of this and the conversion which wrap it should be audited and reduced.
 */
function getStoredSchema(schema, options) {
    if ((0, index_js_4.isClassBasedSchema)(schema)) {
        return (0, index_js_4.getTreeNodeSchemaPrivateData)(schema).toStored(options);
    }
    const kind = schema.kind;
    switch (kind) {
        case index_js_4.NodeKind.Leaf: {
            return new index_js_1.LeafNodeStoredSchema(schema.leafKind);
        }
        case index_js_4.NodeKind.Map:
        case index_js_4.NodeKind.Record: {
            const types = schema.allowedTypesIdentifiers;
            return new index_js_1.MapNodeStoredSchema({
                kind: index_js_2.FieldKinds.optional.identifier,
                types,
                persistedMetadata: schema.persistedMetadata,
            }, 
            // TODO: Find a way to avoid injecting persistedMetadata twice in these constructor calls.
            schema.persistedMetadata);
        }
        case index_js_4.NodeKind.Array: {
            const types = schema.allowedTypesIdentifiers;
            return arrayNodeStoredSchema(types, schema.persistedMetadata);
        }
        case index_js_4.NodeKind.Object: {
            const fields = new Map();
            for (const fieldSchema of schema.fields.values()) {
                fields.set((0, index_js_3.brand)(fieldSchema.storedKey), convertField(fieldSchema, options));
            }
            return new index_js_1.ObjectNodeStoredSchema(fields, schema.persistedMetadata);
        }
        default: {
            (0, internal_1.unreachableCase)(kind);
        }
    }
}
exports.getStoredSchema = getStoredSchema;
function arrayNodeStoredSchema(types, persistedMetadata) {
    const field = {
        kind: index_js_2.FieldKinds.sequence.identifier,
        types,
        persistedMetadata,
    };
    const fields = new Map([[index_js_1.EmptyKey, field]]);
    return new index_js_1.ObjectNodeStoredSchema(fields, persistedMetadata);
}
exports.arrayNodeStoredSchema = arrayNodeStoredSchema;
//# sourceMappingURL=toStoredSchema.js.map