/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, unreachableCase, fail } from "@fluidframework/core-utils/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { EmptyKey, LeafNodeStoredSchema, MapNodeStoredSchema, ObjectNodeStoredSchema, } from "../core/index.js";
import { FieldKinds } from "../feature-libraries/index.js";
import { brand, getOrCreate } from "../util/index.js";
import { NodeKind } from "./core/index.js";
import { LeafNodeSchema } from "./leafNodeSchema.js";
import { FieldKind, normalizeFieldSchema } from "./schemaTypes.js";
import { walkFieldSchema } from "./walkFieldSchema.js";
const viewToStoredCache = new WeakMap();
/**
 * Converts a {@link ImplicitFieldSchema} into a {@link TreeStoredSchema}.
 * @throws
 * Throws a `UsageError` if multiple schemas are encountered with the same identifier.
 */
export function toStoredSchema(root) {
    return getOrCreate(viewToStoredCache, root, () => {
        const normalized = normalizeFieldSchema(root);
        const nodeSchema = new Map();
        walkFieldSchema(normalized, {
            node(schema) {
                if (nodeSchema.has(brand(schema.identifier))) {
                    // Use JSON.stringify to quote and escape identifier string.
                    throw new UsageError(`Multiple schema encountered with the identifier ${JSON.stringify(schema.identifier)}. Remove or rename them to avoid the collision.`);
                }
                nodeSchema.set(brand(schema.identifier), getStoredSchema(schema));
            },
        });
        const result = {
            nodeSchema,
            rootFieldSchema: convertField(normalized),
        };
        return result;
    });
}
/**
 * Converts a {@link SimpleTreeSchema} into a {@link TreeStoredSchema}.
 */
export function simpleToStoredSchema(root) {
    const nodeSchema = new Map();
    for (const [identifier, schema] of root.definitions) {
        nodeSchema.set(brand(identifier), getStoredSchema(schema));
    }
    const result = {
        nodeSchema,
        rootFieldSchema: convertField(root.root),
    };
    return result;
}
/**
 * Normalizes an {@link ImplicitFieldSchema} into a {@link TreeFieldSchema}.
 */
export function convertField(schema) {
    const kind = convertFieldKind.get(schema.kind)?.identifier ?? fail(0xae3 /* Invalid field kind */);
    const types = schema.allowedTypesIdentifiers;
    return { kind, types, persistedMetadata: schema.persistedMetadata };
}
/**
 * A map that converts {@link FieldKind} to {@link FlexFieldKind}.
 */
export const convertFieldKind = new Map([
    [FieldKind.Optional, FieldKinds.optional],
    [FieldKind.Required, FieldKinds.required],
    [FieldKind.Identifier, FieldKinds.identifier],
]);
/**
 * Converts a {@link TreeNodeSchema} into a {@link TreeNodeStoredSchema}.
 * @privateRemarks
 * TODO: Persist node metadata once schema FormatV2 is supported.
 */
export function getStoredSchema(schema) {
    const kind = schema.kind;
    switch (kind) {
        case NodeKind.Leaf: {
            assert(schema instanceof LeafNodeSchema, 0xa4a /* invalid kind */);
            return new LeafNodeStoredSchema(schema.leafKind);
        }
        case NodeKind.Map: {
            const types = schema.allowedTypesIdentifiers;
            return new MapNodeStoredSchema({
                kind: FieldKinds.optional.identifier,
                types,
                persistedMetadata: schema.persistedMetadata,
            }, 
            // TODO: Find a way to avoid injecting persistedMetadata twice in these constructor calls.
            schema.persistedMetadata);
        }
        case NodeKind.Array: {
            const types = schema.allowedTypesIdentifiers;
            const field = {
                kind: FieldKinds.sequence.identifier,
                types,
                persistedMetadata: schema.persistedMetadata,
            };
            const fields = new Map([[EmptyKey, field]]);
            return new ObjectNodeStoredSchema(fields, schema.persistedMetadata);
        }
        case NodeKind.Object: {
            const fields = new Map();
            for (const fieldSchema of schema.fields.values()) {
                fields.set(brand(fieldSchema.storedKey), convertField(fieldSchema));
            }
            return new ObjectNodeStoredSchema(fields, schema.persistedMetadata);
        }
        default:
            unreachableCase(kind);
    }
}
//# sourceMappingURL=toStoredSchema.js.map