"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertObjectNodeSchema = exports.toJsonSchema = 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("../../util/index.js");
const index_js_3 = require("../core/index.js");
const fieldSchema_js_1 = require("../fieldSchema.js");
const leafNodeSchema_js_1 = require("../leafNodeSchema.js");
const index_js_4 = require("../node-kinds/index.js");
const customTree_js_1 = require("./customTree.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.
 */
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.
    // TODO: handle staged types in a controllable way.
    return (0, index_js_2.hasSingle)(allowedTypes)
        ? {
            ...allowedTypes[0],
            $defs: definitions,
        }
        : {
            $defs: definitions,
            anyOf: allowedTypes,
        };
}
exports.toJsonSchema = toJsonSchema;
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 index_js_4.ArrayNodeSchema) {
        return convertArrayNodeSchema(schema);
    }
    else if ((0, index_js_4.isMapNodeSchema)(schema) || (0, index_js_4.isRecordNodeSchema)(schema)) {
        return convertRecordLikeNodeSchema(schema);
    }
    else if (schema instanceof index_js_4.ObjectNodeSchema) {
        return convertObjectNodeSchema(schema, options);
    }
    else if (schema instanceof leafNodeSchema_js_1.LeafNodeSchema) {
        return convertLeafNodeSchema(schema);
    }
    throw new TypeError(`Unknown node schema kind: ${schema.kind}`);
}
function convertArrayNodeSchema(schema) {
    const allowedTypes = [];
    const allowedTypesIdentifiers = new Set(schema.simpleAllowedTypes.keys());
    for (const type of allowedTypesIdentifiers) {
        allowedTypes.push(createSchemaRef(type));
    }
    const items = (0, index_js_2.hasSingle)(allowedTypes)
        ? allowedTypes[0]
        : { anyOf: allowedTypes };
    const output = {
        type: "array",
        _treeNodeSchemaKind: index_js_3.NodeKind.Array,
        items,
    };
    (0, index_js_2.copyProperty)(schema.metadata, "description", output);
    return output;
}
function convertLeafNodeSchema(schema) {
    let type;
    switch (schema.leafKind) {
        case index_js_1.ValueSchema.String: {
            type = "string";
            break;
        }
        case index_js_1.ValueSchema.Number: {
            type = "number";
            break;
        }
        case index_js_1.ValueSchema.Boolean: {
            type = "boolean";
            break;
        }
        case index_js_1.ValueSchema.Null: {
            type = "null";
            break;
        }
        case index_js_1.ValueSchema.FluidHandle: {
            throw new internal_2.UsageError("Fluid handles are not supported via JSON Schema.");
        }
        default: {
            (0, internal_1.unreachableCase)(schema.leafKind);
        }
    }
    return {
        type,
        _treeNodeSchemaKind: index_js_3.NodeKind.Leaf,
    };
}
function convertObjectNodeSchema(schema, options) {
    const properties = {};
    const required = [];
    for (const [propertyKey, fieldSchema] of schema.fields) {
        const key = options.keys === customTree_js_1.KeyEncodingOptions.usePropertyKeys
            ? propertyKey
            : fieldSchema.storedKey;
        const allowedTypes = [];
        for (const allowedType of fieldSchema.allowedTypesIdentifiers) {
            allowedTypes.push(createSchemaRef(allowedType));
        }
        const output = (0, index_js_2.hasSingle)(allowedTypes)
            ? allowedTypes[0]
            : {
                anyOf: allowedTypes,
            };
        (0, index_js_2.copyProperty)(fieldSchema.metadata, "description", output);
        properties[key] = output;
        if (fieldSchema.kind !== fieldSchema_js_1.FieldKind.Optional &&
            (options.requireFieldsWithDefaults || fieldSchema.props?.defaultProvider === undefined)) {
            required.push(key);
        }
    }
    const transformedNode = {
        type: "object",
        _treeNodeSchemaKind: index_js_3.NodeKind.Object,
        properties,
        required,
        // TODO: support unknown optional fields (only when using "allStoredKeys", and constrain the content to be in schema somehow)
        additionalProperties: false,
    };
    (0, index_js_2.copyProperty)(schema.metadata, "description", transformedNode);
    return transformedNode;
}
exports.convertObjectNodeSchema = convertObjectNodeSchema;
function convertRecordLikeNodeSchema(schema) {
    const allowedTypes = [];
    const allowedTypesIdentifiers = new Set(schema.simpleAllowedTypes.keys());
    for (const type of allowedTypesIdentifiers) {
        allowedTypes.push(createSchemaRef(type));
    }
    const output = {
        type: "object",
        _treeNodeSchemaKind: schema.kind,
        patternProperties: {
            "^.*$": (0, index_js_2.hasSingle)(allowedTypes)
                ? allowedTypes[0]
                : {
                    anyOf: allowedTypes,
                },
        },
    };
    (0, index_js_2.copyProperty)(schema.metadata, "description", output);
    return output;
}
function createSchemaRef(schemaId) {
    return {
        "$ref": createRefPath(schemaId),
    };
}
function createRefPath(schemaId) {
    return `#/$defs/${schemaId}`;
}
//# sourceMappingURL=simpleSchemaToJsonSchema.js.map