"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaBuilder = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const default_field_kinds_1 = require("./default-field-kinds");
const typed_schema_1 = require("./typed-schema");
// TODO: tests and examples for this file
/**
 * Builds schema libraries, and the schema within them.
 * @sealed @alpha
 */
class SchemaBuilder {
    /**
     * @param name - Name used to refer to this builder in error messages. Has no impact on the actual generated schema.
     * @param lint - Optional configuration for "linting". See {@link SchemaLintConfiguration}. Currently defaults to enabling all lints.
     * @param libraries - Libraries to include in this one. See `addLibraries` for details.
     */
    constructor(name, lint = {}, ...libraries) {
        this.name = name;
        this.finalized = false;
        this.treeSchema = new Map();
        this.adapters = {};
        this.lintConfiguration = { ...typed_schema_1.schemaLintDefault, ...lint };
        this.libraries = new Set();
        this.addLibraries(...libraries);
    }
    /**
     * Adds more libraries to this one.
     *
     * Unlike adding of individual schema, adding of libraries is idempotent.
     * If a single library is added multiple times (even indirectly via libraries it was added into),
     * only a single copy will be included, so they will not conflict.
     * This allows adding any library this one depends on without risk of conflicts for users of this library.
     * Contents within the added libraries can still conflict however.
     * Such errors will be reported when finalizing this builder into a library of document schema.
     */
    addLibraries(...libraries) {
        for (const libs of libraries) {
            for (const lib of libs.libraries) {
                this.libraries.add(lib);
            }
        }
    }
    addNodeSchema(schema) {
        (0, core_utils_1.assert)(!this.treeSchema.has(schema.name), 0x6ab /* Conflicting TreeSchema names */);
        this.treeSchema.set(schema.name, schema);
    }
    /**
     * Define (and add to this library) a {@link TreeSchema} for a {@link Struct} node.
     *
     * The name must be unique among all TreeSchema in the the document schema.
     */
    struct(name, t) {
        const schema = new typed_schema_1.TreeSchema(this, name, { structFields: t });
        this.addNodeSchema(schema);
        return schema;
    }
    /**
     * Same as `struct` but with less type safety and works for recursive objects.
     * Reduced type safety is a side effect of a workaround for a TypeScript limitation.
     *
     * See note on RecursiveTreeSchema for details.
     */
    structRecursive(name, t) {
        return this.struct(name, t);
    }
    /**
     * Define (and add to this library) a {@link TreeSchema} for a {@link MapNode}.
     */
    map(name, fieldSchema) {
        const schema = new typed_schema_1.TreeSchema(this, name, { mapFields: fieldSchema });
        this.addNodeSchema(schema);
        return schema;
    }
    /**
     * Same as `map` but with less type safety and works for recursive objects.
     * Reduced type safety is a side effect of a workaround for a TypeScript limitation.
     *
     * See note on RecursiveTreeSchema for details.
     */
    mapRecursive(name, t) {
        return this.map(name, t);
    }
    /**
     * Define (and add to this library) a {@link TreeSchema} for a {@link FieldNode}.
     *
     * The name must be unique among all TreeSchema in the the document schema.
     *
     * @privateRemarks
     * TODO: Write and link document outlining field vs node data model and the separation of concerns related to that.
     * TODO: Maybe find a better name for this.
     */
    fieldNode(name, t) {
        const schema = new typed_schema_1.TreeSchema(this, name, { structFields: { [""]: t } });
        this.addNodeSchema(schema);
        return schema;
    }
    /**
     * Same as `fieldNode` but with less type safety and works for recursive objects.
     * Reduced type safety is a side effect of a workaround for a TypeScript limitation.
     *
     * See note on RecursiveTreeSchema for details.
     */
    fieldNodeRecursive(name, t) {
        return this.fieldNode(name, t);
    }
    /**
     * Define (and add to this library) a {@link TreeSchema} for a node that wraps a value.
     * Such nodes will be implicitly unwrapped to the value in some APIs.
     *
     * The name must be unique among all TreeSchema in the the document schema.
     *
     * In addition to the normal properties of all nodes (having a schema for example),
     * Leaf nodes only contain a value.
     * Leaf nodes cannot have fields.
     *
     * TODO: Maybe ban undefined from allowed values here.
     * TODO: Decide and document how unwrapping works for non-primitive terminals.
     */
    leaf(name, t) {
        const schema = new typed_schema_1.TreeSchema(this, name, { leafValue: t });
        this.addNodeSchema(schema);
        return schema;
    }
    /**
     * Define a {@link FieldSchema}.
     *
     * @param kind - The [kind](https://en.wikipedia.org/wiki/Kind_(type_theory)) of this field.
     * Determine the multiplicity, viewing and editing APIs as well as the merge resolution policy.
     * @param allowedTypes - What types of children are allowed in this field.
     * @returns a {@link FieldSchema} which can be used as a struct field (see {@link SchemaBuilder.struct}),
     * a map field (see {@link SchemaBuilder.map}), a field node(see {@link SchemaBuilder.fieldNode}) or the root field (see {@link SchemaBuilder.intoDocumentSchema}).
     *
     * @privateRemarks
     * TODO: since this APi surface is using classes, maybe just have users do `new FieldSchema` instead?
     */
    static field(kind, ...allowedTypes) {
        return new typed_schema_1.FieldSchema(kind, allowedTypes);
    }
    /**
     * Define a schema for an {@link OptionalField}.
     * Shorthand or passing `FieldKinds.optional` to {@link SchemaBuilder.field}.
     */
    static fieldOptional(...allowedTypes) {
        return SchemaBuilder.field(default_field_kinds_1.FieldKinds.optional, ...allowedTypes);
    }
    /**
     * Define a schema for a {@link RequiredField}.
     * Shorthand or passing `FieldKinds.value` to {@link SchemaBuilder.field}.
     *
     * @privateRemarks
     * TODO: Consider adding even shorter syntax where:
     * - AllowedTypes can be used as a FieldSchema (Or SchemaBuilder takes a default field kind).
     * - A TreeSchema can be used as AllowedTypes in the non-polymorphic case.
     */
    static fieldValue(...allowedTypes) {
        return SchemaBuilder.field(default_field_kinds_1.FieldKinds.value, ...allowedTypes);
    }
    /**
     * Define a schema for a {@link Sequence} field.
     */
    static fieldSequence(...t) {
        return SchemaBuilder.field(default_field_kinds_1.FieldKinds.sequence, ...t);
    }
    /**
     * Define a schema for a field.
     * Same as {@link SchemaBuilder.field} but is less type safe and supports recursive types.
     * This API is less safe to work around a limitation of TypeScript.
     *
     * T must extends `AllowedTypes`: This cannot be enforced via TypeScript since such an "extends" clauses cause recursive types to error with:
     * "'theSchema' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer."
     *
     * TODO: Try and find a way to provide a more specific type without triggering the above error.
     */
    static fieldRecursive(kind, ...allowedTypes) {
        return new typed_schema_1.FieldSchema(kind, allowedTypes);
    }
    finalize() {
        (0, core_utils_1.assert)(!this.finalized, 0x6ad /* SchemaBuilder can only be finalized once. */);
        this.finalized = true;
        this.libraries.add({
            name: this.name,
            rootFieldSchema: undefined,
            treeSchema: this.treeSchema,
            adapters: this.adapters,
        });
    }
    /**
     * Produce SchemaLibraries which capture the content added to this builder, as well as any additional SchemaLibraries that were added to it.
     * May only be called once after adding content to builder is complete.
     */
    intoLibrary() {
        this.finalize();
        // Check for errors:
        const collection = (0, typed_schema_1.buildViewSchemaCollection)(this.lintConfiguration, this.libraries);
        return { ...collection, libraries: this.libraries };
    }
    /**
     * Produce a TypedSchemaCollection which captures the content added to this builder, any additional SchemaLibraries that were added to it and a root field.
     * Can be used with schematize to provide schema aware access to document content.
     *
     * May only be called once after adding content to builder is complete.
     */
    intoDocumentSchema(root) {
        this.finalize();
        const rootLibrary = {
            name: this.name,
            rootFieldSchema: root,
            treeSchema: new Map(),
            adapters: {},
        };
        const collection = (0, typed_schema_1.buildViewSchemaCollection)(this.lintConfiguration, [
            rootLibrary,
            ...this.libraries,
        ]);
        const typed = {
            ...collection,
            rootFieldSchema: root,
        };
        return typed;
    }
}
exports.SchemaBuilder = SchemaBuilder;
//# sourceMappingURL=schemaBuilder.js.map