/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { transformObjectMap } from "../util/index.js";
import { defaultSchemaPolicy } from "./default-schema/index.js";
import { Any, FlexFieldNodeSchema, FlexFieldSchema, FlexMapNodeSchema, FlexObjectNodeSchema, TreeNodeSchemaBase, aggregateSchemaLibraries, schemaLintDefault, } from "./typed-schema/index.js";
/**
 * Builds schema libraries, and the schema within them.
 * @internal
 *
 * @privateRemarks
 * This class does not directly depend on any specific field kinds,
 * or bake in any defaults that might have compatibility implications.
 * All use of such implicit defaults is done by subclasses, which thus get versioning implications.
 */
export class SchemaBuilderBase {
    /**
     * @param defaultKind - The default field kind to use when inferring a {@link FlexFieldSchema} from {@link FlexImplicitAllowedTypes}.
     */
    constructor(defaultKind, options) {
        this.defaultKind = defaultKind;
        this.finalized = false;
        this.treeNodeSchema = new Map();
        this.adapters = {};
        this.name = options.name ?? options.scope;
        this.lintConfiguration = { ...schemaLintDefault, ...options.lint };
        this.libraries = new Set();
        this.addLibraries(...(options.libraries ?? []));
        this.scope = options.scope;
    }
    scoped(name) {
        return `${this.scope}.${name}`;
    }
    /**
     * 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) {
        assert(!this.treeNodeSchema.has(schema.name), 0x799 /* Conflicting TreeNodeSchema names */);
        this.treeNodeSchema.set(schema.name, schema);
    }
    finalizeCommon(field) {
        assert(!this.finalized, 0x79a /* SchemaBuilder can only be finalized once. */);
        this.finalized = true;
        this.libraries.add({
            name: this.name,
            nodeSchema: this.treeNodeSchema,
            adapters: this.adapters,
        });
        // Check for errors and aggregate data
        return aggregateSchemaLibraries(this.name, this.lintConfiguration, this.libraries, field);
    }
    /**
     * 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() {
        const aggregated = this.finalizeCommon();
        // Full library set (instead of just aggregated) is kept since it is required to handle deduplication of libraries included through different paths.
        return { nodeSchema: aggregated.nodeSchema, libraries: this.libraries };
    }
    /**
     * Produce a TreeSchema 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.
     *
     * @remarks
     * May only be called once after adding content to builder is complete.
     */
    intoSchema(root) {
        // return this.toDocumentSchemaInternal(normalizeField(root, DefaultFieldKind));
        const field = this.normalizeField(root);
        const library = this.finalizeCommon(field);
        const typed = {
            nodeSchema: library.nodeSchema,
            adapters: library.adapters,
            rootFieldSchema: field,
            policy: defaultSchemaPolicy,
        };
        return typed;
    }
    /**
     * Define (and add to this library) a {@link FlexObjectNodeSchema}.
     *
     * The name must be unique among all TreeNodeSchema in the the document schema.
     */
    object(name, t) {
        const schema = FlexObjectNodeSchema.create(this, this.scoped(name), transformObjectMap(t, (field) => this.normalizeField(field)));
        this.addNodeSchema(schema);
        return schema;
    }
    /**
     * Same as `object` 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 {@link Unenforced} for details.
     *
     * TODO: Make this work with ImplicitFieldSchema.
     */
    objectRecursive(name, t) {
        return this.object(name, t);
    }
    /**
     * Define (and add to this library) a {@link FlexMapNodeSchema}.
     */
    map(name, fieldSchema) {
        const schema = FlexMapNodeSchema.create(this, this.scoped(name), 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 {@link Unenforced} for details.
     *
     * TODO: Make this work with ImplicitFieldSchema.
     */
    mapRecursive(name, t) {
        return this.map(name, t);
    }
    /**
     * Define (and add to this library) a {@link FlexFieldNodeSchema}.
     *
     * The name must be unique among all TreeNodeSchema 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, fieldSchema) {
        const schema = FlexFieldNodeSchema.create(this, this.scoped(name), this.normalizeField(fieldSchema));
        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 {@link Unenforced} for details.
     *
     * TODO: Make this work with ImplicitFieldSchema.
     */
    fieldNodeRecursive(name, t) {
        return this.fieldNode(name, t);
    }
    /**
     * Define a {@link FlexFieldSchema}.
     *
     * @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 FlexFieldSchema} which can be used as a object field (see {@link SchemaBuilderBase.object}),
     * a map field (see {@link SchemaBuilderBase.map}), a field node (see {@link SchemaBuilderBase.fieldNode}) or the root field (see {@link SchemaBuilderBase.intoSchema}).
     *
     * @privateRemarks
     * TODO:
     * If a solution to TreeFieldSchema not being able to have extends clauses gets found,
     * consider just having users do `new TreeFieldSchema` instead?
     */
    static field(kind, allowedTypes) {
        return FlexFieldSchema.create(kind, normalizeAllowedTypes(allowedTypes));
    }
    /**
     * Define a schema for a field.
     * Same as {@link SchemaBuilderBase.field} but is less type safe and supports recursive types.
     * This API is less safe to work around a [limitation of TypeScript](https://github.com/microsoft/TypeScript/issues/55758).
     *
     * 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 FlexFieldSchema.createUnsafe(kind, allowedTypes);
    }
    /**
     * Normalizes an {@link FlexImplicitFieldSchema} into a {@link FlexFieldSchema} using this schema builder's `defaultKind`.
     */
    normalizeField(schema) {
        return normalizeField(schema, this.defaultKind);
    }
}
/**
 * Normalizes an {@link FlexImplicitAllowedTypes} into  {@link FlexAllowedTypes}.
 */
export function normalizeAllowedTypes(schema) {
    if (schema === Any) {
        return [Any];
    }
    if (schema instanceof TreeNodeSchemaBase) {
        return [schema];
    }
    assert(Array.isArray(schema), 0x7c6 /* invalid ImplicitAllowedTypes */);
    return schema;
}
/**
 * Normalizes an {@link FlexImplicitFieldSchema} into a {@link FlexFieldSchema}.
 */
export function normalizeField(schema, defaultKind) {
    if (schema instanceof FlexFieldSchema) {
        return schema;
    }
    const allowedTypes = normalizeAllowedTypes(schema);
    return FlexFieldSchema.create(defaultKind, allowedTypes);
}
//# sourceMappingURL=schemaBuilderBase.js.map