/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { unreachableCase } from "@fluidframework/core-utils/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { ValueSchema } from "../../core/index.js";
import { copyProperty, getOrCreate, hasSingle } from "../../util/index.js";
import { FieldKind } from "../schemaTypes.js";
import { NodeKind } from "../core/index.js";
/**
 * Generates a JSON Schema representation from a simple tree schema.
 * @internal
 */
export function toJsonSchema(schema) {
    const definitions = convertDefinitions(schema.definitions);
    const allowedTypes = [];
    for (const allowedType of schema.allowedTypes) {
        allowedTypes.push(createSchemaRef(allowedType));
    }
    return hasSingle(allowedTypes)
        ? {
            ...allowedTypes[0],
            $defs: definitions,
        }
        : {
            $defs: definitions,
            anyOf: allowedTypes,
        };
}
function convertDefinitions(definitions) {
    const result = {};
    for (const [key, value] of definitions) {
        result[key] = convertNodeSchema(value);
    }
    return result;
}
/**
 * Private symbol under which the results of {@link convertNodeSchema} are cached on an input {@link SimpleNodeSchema}.
 */
const nodeJsonSchemaCache = new WeakMap();
/**
 * Converts an input {@link SimpleNodeSchema} to a {@link JsonNodeSchema}.
 *
 * @remarks Caches the result on the input schema for future calls.
 */
function convertNodeSchema(schema) {
    return getOrCreate(nodeJsonSchemaCache, schema, () => {
        switch (schema.kind) {
            case NodeKind.Array:
                return convertArrayNodeSchema(schema);
            case NodeKind.Leaf:
                return convertLeafNodeSchema(schema);
            case NodeKind.Map:
                return convertMapNodeSchema(schema);
            case NodeKind.Object:
                return convertObjectNodeSchema(schema);
            default:
                throw new TypeError(`Unknown node schema kind: ${schema.kind}`);
        }
    });
}
function convertArrayNodeSchema(schema) {
    const allowedTypes = [];
    schema.allowedTypes.forEach((type) => {
        allowedTypes.push(createSchemaRef(type));
    });
    const items = hasSingle(allowedTypes)
        ? allowedTypes[0]
        : { anyOf: allowedTypes };
    const output = {
        type: "array",
        _treeNodeSchemaKind: NodeKind.Array,
        items,
    };
    copyProperty(schema.metadata, "description", output);
    return output;
}
function convertLeafNodeSchema(schema) {
    let type;
    switch (schema.leafKind) {
        case ValueSchema.String:
            type = "string";
            break;
        case ValueSchema.Number:
            type = "number";
            break;
        case ValueSchema.Boolean:
            type = "boolean";
            break;
        case ValueSchema.Null:
            type = "null";
            break;
        case ValueSchema.FluidHandle:
            throw new UsageError("Fluid handles are not supported via JSON Schema.");
        default:
            unreachableCase(schema.leafKind);
    }
    return {
        type,
        _treeNodeSchemaKind: NodeKind.Leaf,
    };
}
function convertObjectNodeSchema(schema) {
    const properties = {};
    const required = [];
    for (const [key, fieldSchema] of Object.entries(schema.fields)) {
        const allowedTypes = [];
        for (const allowedType of fieldSchema.allowedTypes) {
            allowedTypes.push(createSchemaRef(allowedType));
        }
        const output = hasSingle(allowedTypes)
            ? allowedTypes[0]
            : {
                anyOf: allowedTypes,
            };
        copyProperty(fieldSchema.metadata, "description", output);
        properties[key] = output;
        if (fieldSchema.kind === FieldKind.Required) {
            required.push(key);
        }
    }
    const transformedNode = {
        type: "object",
        _treeNodeSchemaKind: NodeKind.Object,
        properties,
        required,
        additionalProperties: false,
    };
    copyProperty(schema.metadata, "description", transformedNode);
    return transformedNode;
}
function convertMapNodeSchema(schema) {
    const allowedTypes = [];
    schema.allowedTypes.forEach((type) => {
        allowedTypes.push(createSchemaRef(type));
    });
    const output = {
        type: "object",
        _treeNodeSchemaKind: NodeKind.Map,
        patternProperties: {
            "^.*$": hasSingle(allowedTypes)
                ? allowedTypes[0]
                : {
                    anyOf: allowedTypes,
                },
        },
    };
    copyProperty(schema.metadata, "description", output);
    return output;
}
function createSchemaRef(schemaId) {
    return {
        "$ref": createRefPath(schemaId),
    };
}
function createRefPath(schemaId) {
    return `#/$defs/${schemaId}`;
}
//# sourceMappingURL=simpleSchemaToJsonSchema.js.map