/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { Lazy, assert } from "@fluidframework/core-utils";
import { EmptyKey, } from "../../core";
import { compareSets, } from "../../util";
import { FieldKinds } from "../default-field-kinds";
import { objectToMapTyped } from "./typeUtils";
{
}
/**
 * T must extend TreeSchemaSpecification.
 * This can not be enforced using TypeScript since doing so breaks recursive type support.
 * See note on SchemaBuilder.fieldRecursive.
 * @alpha
 */
export class TreeNodeSchema {
    constructor(builder, name, info) {
        this.builder = builder;
        this.info = info;
        this.name = name;
        this.objectNodeFieldsObject = normalizeStructFields(this.info.objectNodeFields);
        this.objectNodeFields = objectToMapTyped(this.objectNodeFieldsObject);
        this.mapFields = this.info.mapFields;
        this.leafValue = this.info.leafValue;
    }
}
export function schemaIsMap(schema) {
    return schema.mapFields !== undefined;
}
export function schemaIsLeaf(schema) {
    return schema.leafValue !== undefined;
}
/**
 * Checks if a {@link TreeNodeSchema} is a {@link FieldNodeSchema}.
 * @alpha
 */
export function schemaIsFieldNode(schema) {
    return schema.objectNodeFields.size === 1 && schema.objectNodeFields.has(EmptyKey);
}
export function schemaIsObjectNode(schema) {
    return !schemaIsMap(schema) && !schemaIsLeaf(schema) && !schemaIsFieldNode(schema);
}
function normalizeStructFields(fields) {
    if (fields === undefined) {
        return {};
    }
    const out = {};
    // eslint-disable-next-line no-restricted-syntax
    for (const key in fields) {
        if (Object.prototype.hasOwnProperty.call(fields, key)) {
            const element = fields[key];
            out[key] = normalizeField(element);
        }
    }
    return out;
}
function normalizeField(t) {
    if (t === undefined) {
        return TreeFieldSchema.empty;
    }
    assert(t instanceof TreeFieldSchema, 0x6ae /* invalid TreeFieldSchema */);
    return t;
}
/**
 * Allow any node (as long as it meets the schema for its own type).
 * @alpha
 */
export const Any = "Any";
/**
 * Checks if an {@link AllowedTypes} is {@link (Any:type)}.
 * @alpha
 */
export function allowedTypesIsAny(t) {
    return t.length === 1 && t[0] === Any;
}
/**
 * All policy for a specific field,
 * including functionality that does not have to be kept consistent across versions or deterministic.
 *
 * This can include policy for how to use this schema for "view" purposes, and well as how to expose editing APIs.
 *
 * @remarks
 * `Types` here must extend `AllowedTypes`, but this cannot be enforced with an "extends" clause: see {@link Unenforced} for details.
 *
 * @typeParam TKind - The kind of field.
 * @typeParam TTypes - The types allowed by the field.
 *
 * @sealed
 * @alpha
 */
export class TreeFieldSchema {
    /**
     * Constructs a TreeFieldSchema.
     * @privateRemarks
     * Alias for the constructor, but with extends clause for the `Types` parameter that {@link TreeFieldSchema} can not have (due to recursive type issues).
     */
    static create(kind, allowedTypes) {
        return new TreeFieldSchema(kind, allowedTypes);
    }
    /**
     * Constructs a TreeFieldSchema, but missing the extends clause which breaks most recursive types.
     * @remarks
     * `Types` here must extend `AllowedTypes`, but this cannot be enforced with an "extends" clause: see {@link Unenforced} for details.
     * Prefer {@link TreeFieldSchema.create} when possible.
     */
    static createUnsafe(kind, allowedTypes) {
        return new TreeFieldSchema(kind, allowedTypes);
    }
    /**
     * @param kind - The {@link https://en.wikipedia.org/wiki/Kind_(type_theory) | kind} of this field.
     * Determine the multiplicity, viewing and editing APIs as well as the merge resolution policy.
     * @param allowedTypes - What types of tree nodes are allowed in this field.
     */
    constructor(kind, allowedTypes) {
        this.kind = kind;
        this.allowedTypes = allowedTypes;
        // Since this class can't have the desired extends clause, do some extra runtime validation:
        assert(Array.isArray(allowedTypes), 0x7bc /* Invalid allowedTypes */);
        for (const allowedType of allowedTypes) {
            if (allowedType === Any) {
                assert(allowedTypes.length === 1, 0x7bd /* Invalid Any in allowedTypes */);
            }
            else if (typeof allowedType !== "function") {
                assert(allowedType instanceof TreeNodeSchema, 0x7be /* Invalid entry in allowedTypes */);
            }
        }
        this.lazyTypes = new Lazy(() => ({
            names: allowedTypesToTypeSet(this.allowedTypes),
            schema: allowedTypesSchemaSet(this.allowedTypes),
        }));
    }
    /**
     * Types which are allowed in this field (by {@link TreeNodeSchemaIdentifier}), in a format optimized for stored schema.
     * This is the same set of types in {@link TreeFieldSchema.allowedTypes}, just in a different format.
     */
    get types() {
        return this.lazyTypes.value.names;
    }
    /**
     * Types which are allowed in this field.
     * This is the same set of types in {@link TreeFieldSchema.allowedTypes}, just as a set with laziness removed.
     * @privateRemarks
     * TODO:
     * 3 ways to access the allowed types are now exposed.
     * Reducing this and/or renaming the more friendly options to take the shorter name (`types`)
     * would be a good idea.
     */
    get allowedTypeSet() {
        return this.lazyTypes.value.schema;
    }
    /**
     * Compare this schema to another.
     *
     * @returns true iff the schema are identical.
     */
    equals(other) {
        if (other.kind !== this.kind) {
            return false;
        }
        if (other.types === undefined) {
            return this.types === undefined;
        }
        if (this.types === undefined) {
            return false;
        }
        return compareSets({
            a: this.types,
            b: other.types,
            aExtra: () => false,
            bExtra: () => false,
        });
    }
}
/**
 * Schema for a field which must always be empty.
 */
TreeFieldSchema.empty = TreeFieldSchema.create(FieldKinds.forbidden, []);
/**
 * Convert {@link AllowedTypes} to {@link TreeTypeSet}.
 * @alpha
 */
export function allowedTypesSchemaSet(t) {
    if (allowedTypesIsAny(t)) {
        return Any;
    }
    const list = t.map((value) => {
        if (typeof value === "function") {
            return value();
        }
        return value;
    });
    return new Set(list);
}
/**
 * Convert {@link AllowedTypes} to {@link TreeTypeSet}.
 * @alpha
 */
export function allowedTypesToTypeSet(t) {
    const list = allowedTypesSchemaSet(t);
    if (list === Any) {
        return undefined;
    }
    const names = Array.from(list, (type) => {
        assert(type instanceof TreeNodeSchema, 0x7bf /* invalid allowed type */);
        return type.name;
    });
    return new Set(names);
}
{
}
//# sourceMappingURL=typedTreeSchema.js.map