/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import { normalizeFieldSchema, } from "../schemaTypes.js";
import { copyProperty, getOrCreate } from "../../util/index.js";
import { isObjectNodeSchema } from "../objectNodeTypes.js";
import { NodeKind } from "../core/index.js";
/**
 * Converts a "view" schema to a "simple" schema representation.
 */
export function toSimpleTreeSchema(schema) {
    const normalizedSchema = normalizeFieldSchema(schema);
    const allowedTypes = allowedTypesFromFieldSchema(normalizedSchema);
    const definitions = new Map();
    populateSchemaDefinitionsForField(normalizedSchema, definitions);
    const output = {
        kind: normalizedSchema.kind,
        allowedTypes,
        definitions,
    };
    copyProperty(normalizedSchema, "metadata", output);
    return output;
}
/**
 * Cache in which the results of {@link toSimpleNodeSchema} are saved.
 */
const simpleNodeSchemaCache = new WeakMap();
/**
 * Creates a {@link SimpleNodeSchema} from a {@link TreeNodeSchema}.
 *
 * @remarks Caches the result on the input schema for future calls.
 */
function toSimpleNodeSchema(schema) {
    return getOrCreate(simpleNodeSchemaCache, schema, () => {
        const kind = schema.kind;
        switch (kind) {
            case NodeKind.Leaf: {
                return leafSchemaToSimpleSchema(schema);
            }
            case NodeKind.Map: {
                return mapSchemaToSimpleSchema(schema);
            }
            case NodeKind.Array: {
                return arraySchemaToSimpleSchema(schema);
            }
            case NodeKind.Object: {
                assert(isObjectNodeSchema(schema), 0xa06 /* Expected object schema */);
                return objectSchemaToSimpleSchema(schema);
            }
            default: {
                unreachableCase(kind);
            }
        }
    });
}
// TODO: Use a stronger type for leaf schemas once one is available (see object schema handler for an example).
function leafSchemaToSimpleSchema(schema) {
    return {
        kind: NodeKind.Leaf,
        leafKind: schema.info,
    };
}
// TODO: Use a stronger type for array schemas once one is available (see object schema handler for an example).
function arraySchemaToSimpleSchema(schema) {
    const fieldSchema = normalizeFieldSchema(schema.info);
    const allowedTypes = allowedTypesFromFieldSchema(fieldSchema);
    const output = {
        kind: NodeKind.Array,
        allowedTypes,
    };
    copyProperty(schema, "metadata", output);
    return output;
}
// TODO: Use a stronger type for map schemas once one is available (see object schema handler for an example).
function mapSchemaToSimpleSchema(schema) {
    const fieldSchema = normalizeFieldSchema(schema.info);
    const allowedTypes = allowedTypesFromFieldSchema(fieldSchema);
    const output = {
        kind: NodeKind.Map,
        allowedTypes,
    };
    copyProperty(schema, "metadata", output);
    return output;
}
function objectSchemaToSimpleSchema(schema) {
    const fields = {};
    for (const [key, field] of schema.fields) {
        fields[key] = fieldSchemaToSimpleSchema(field);
    }
    const output = {
        kind: NodeKind.Object,
        fields,
    };
    copyProperty(schema, "metadata", output);
    return output;
}
/**
 * Private symbol under which the results of {@link toSimpleNodeSchema} are cached on an input {@link TreeNodeSchema}.
 */
const simpleFieldSchemaCacheSymbol = Symbol("simpleFieldSchemaCache");
function fieldSchemaToSimpleSchema(schema) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    if (schema[simpleFieldSchemaCacheSymbol] !== undefined) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        return schema[simpleFieldSchemaCacheSymbol];
    }
    const allowedTypes = allowedTypesFromFieldSchema(schema);
    const result = {
        kind: schema.kind,
        allowedTypes,
    };
    copyProperty(schema, "metadata", result);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    schema[simpleFieldSchemaCacheSymbol] = result;
    return result;
}
function allowedTypesFromFieldSchema(schema) {
    const allowedTypes = new Set();
    for (const type of schema.allowedTypeSet) {
        allowedTypes.add(type.identifier);
    }
    return allowedTypes;
}
/**
 * Recursively populates `definitions` by walking the input field schema tree.
 */
function populateSchemaDefinitionsForField(schema, definitions) {
    for (const child of schema.allowedTypeSet) {
        populateSchemaDefinitionsForNode(child, definitions);
    }
}
/**
 * Recursively populates `definitions` by walking the input node schema tree.
 */
function populateSchemaDefinitionsForNode(schema, definitions) {
    if (definitions.has(schema.identifier)) {
        // If the definition has already been populated, no need to recurse.
        return;
    }
    // Populate definition for this schema
    definitions.set(schema.identifier, toSimpleNodeSchema(schema));
    // Recurse into children to populate definitions for them
    const kind = schema.kind;
    switch (kind) {
        case NodeKind.Leaf: {
            // Leaf node, so no need to recurse
            break;
        }
        case NodeKind.Map: {
            // TODO: Utilize a map schema type-guard once one exists (see object case for an example).
            // Recursively populate definitions for allowed map children
            const fieldSchema = normalizeFieldSchema(schema.info);
            populateSchemaDefinitionsForField(fieldSchema, definitions);
            break;
        }
        case NodeKind.Array: {
            // TODO: Utilize an array schema type-guard once one exists (see object case for an example).
            // Recursively populate definitions for allowed map children
            const fieldSchema = normalizeFieldSchema(schema.info);
            populateSchemaDefinitionsForField(fieldSchema, definitions);
            break;
        }
        case NodeKind.Object: {
            assert(isObjectNodeSchema(schema), 0xa07 /* Expected object schema */);
            for (const [, field] of schema.fields) {
                populateSchemaDefinitionsForField(field, definitions);
            }
            break;
        }
        default: {
            unreachableCase(kind);
        }
    }
}
//# sourceMappingURL=viewSchemaToSimpleSchema.js.map