/*!
 * 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, hasSingle } from "../../util/index.js";
import { FieldKind } from "../fieldSchema.js";
import { NodeKind } from "../core/index.js";
import { ArrayNodeSchema, isMapNodeSchema, isRecordNodeSchema, ObjectNodeSchema, } from "../node-kinds/index.js";
import { LeafNodeSchema } from "../leafNodeSchema.js";
/**
 * Generates a JSON Schema representation from a simple tree schema.
 * @remarks
 * This expects the data to be in the {@link ConciseTree} format.
 *
 * This cannot handle the case where the root is undefined since undefined is not a concept in JSON.
 * This also cannot handle {@link SchemaStatics.handle} since they also are not supported in JSON.
 *
 * @internal
 */
export function toJsonSchema(schema, options) {
    const definitions = convertDefinitions(schema.definitions, options);
    const allowedTypes = [];
    for (const allowedType of schema.root.allowedTypesIdentifiers) {
        allowedTypes.push(createSchemaRef(allowedType));
    }
    // TODO: deduplicate field handling logic from convertObjectNodeSchema: at least include metadata's description.
    // TODO: maybe account for consider schema.kind, or just take in ImplicitAllowedTypes
    // TODO: handle case where allowedTypes is empty.
    return hasSingle(allowedTypes)
        ? {
            ...allowedTypes[0],
            $defs: definitions,
        }
        : {
            $defs: definitions,
            anyOf: allowedTypes,
        };
}
function convertDefinitions(definitions, options) {
    const result = {};
    for (const [key, value] of definitions) {
        result[key] = convertNodeSchema(value, options);
    }
    return result;
}
/**
 * Converts an input {@link SimpleNodeSchema} to a {@link JsonNodeSchema}.
 *
 * @remarks Caches the result on the input schema for future calls.
 */
function convertNodeSchema(schema, options) {
    if (schema instanceof ArrayNodeSchema) {
        return convertArrayNodeSchema(schema);
    }
    else if (isMapNodeSchema(schema) || isRecordNodeSchema(schema)) {
        return convertRecordLikeNodeSchema(schema);
    }
    else if (schema instanceof ObjectNodeSchema) {
        return convertObjectNodeSchema(schema, options);
    }
    else if (schema instanceof LeafNodeSchema) {
        return convertLeafNodeSchema(schema);
    }
    throw new TypeError(`Unknown node schema kind: ${schema.kind}`);
}
function convertArrayNodeSchema(schema) {
    const allowedTypes = [];
    schema.allowedTypesIdentifiers.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,
    };
}
export function convertObjectNodeSchema(schema, options) {
    const properties = {};
    const required = [];
    for (const [propertyKey, fieldSchema] of schema.fields) {
        const key = options.useStoredKeys ? fieldSchema.storedKey : propertyKey;
        const allowedTypes = [];
        for (const allowedType of fieldSchema.allowedTypesIdentifiers) {
            allowedTypes.push(createSchemaRef(allowedType));
        }
        const output = hasSingle(allowedTypes)
            ? allowedTypes[0]
            : {
                anyOf: allowedTypes,
            };
        copyProperty(fieldSchema.metadata, "description", output);
        properties[key] = output;
        if (fieldSchema.kind !== FieldKind.Optional) {
            if (options.requireFieldsWithDefaults ||
                fieldSchema.props?.defaultProvider === undefined) {
                required.push(key);
            }
        }
    }
    const transformedNode = {
        type: "object",
        _treeNodeSchemaKind: NodeKind.Object,
        properties,
        required,
        additionalProperties: false,
    };
    copyProperty(schema.metadata, "description", transformedNode);
    return transformedNode;
}
function convertRecordLikeNodeSchema(schema) {
    const allowedTypes = [];
    schema.allowedTypesIdentifiers.forEach((type) => {
        allowedTypes.push(createSchemaRef(type));
    });
    const output = {
        type: "object",
        _treeNodeSchemaKind: schema.kind,
        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