"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.allowedTypesToTypeSet = exports.allowedTypesSchemaSet = exports.TreeFieldSchema = exports.allowedTypesIsAny = exports.Any = exports.schemaIsObjectNode = exports.schemaIsFieldNode = exports.schemaIsLeaf = exports.schemaIsMap = exports.TreeNodeSchema = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const core_1 = require("../../core");
const util_1 = require("../../util");
const default_field_kinds_1 = require("../default-field-kinds");
const typeUtils_1 = require("./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
 */
class TreeNodeSchema {
    constructor(builder, name, info) {
        this.builder = builder;
        this.info = info;
        this.name = name;
        this.objectNodeFieldsObject = normalizeStructFields(this.info.objectNodeFields);
        this.objectNodeFields = (0, typeUtils_1.objectToMapTyped)(this.objectNodeFieldsObject);
        this.mapFields = this.info.mapFields;
        this.leafValue = this.info.leafValue;
    }
}
exports.TreeNodeSchema = TreeNodeSchema;
function schemaIsMap(schema) {
    return schema.mapFields !== undefined;
}
exports.schemaIsMap = schemaIsMap;
function schemaIsLeaf(schema) {
    return schema.leafValue !== undefined;
}
exports.schemaIsLeaf = schemaIsLeaf;
/**
 * Checks if a {@link TreeNodeSchema} is a {@link FieldNodeSchema}.
 * @alpha
 */
function schemaIsFieldNode(schema) {
    return schema.objectNodeFields.size === 1 && schema.objectNodeFields.has(core_1.EmptyKey);
}
exports.schemaIsFieldNode = schemaIsFieldNode;
function schemaIsObjectNode(schema) {
    return !schemaIsMap(schema) && !schemaIsLeaf(schema) && !schemaIsFieldNode(schema);
}
exports.schemaIsObjectNode = schemaIsObjectNode;
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;
    }
    (0, core_utils_1.assert)(t instanceof TreeFieldSchema, 0x6ae /* invalid TreeFieldSchema */);
    return t;
}
/**
 * Allow any node (as long as it meets the schema for its own type).
 * @alpha
 */
exports.Any = "Any";
/**
 * Checks if an {@link AllowedTypes} is {@link (Any:type)}.
 * @alpha
 */
function allowedTypesIsAny(t) {
    return t.length === 1 && t[0] === exports.Any;
}
exports.allowedTypesIsAny = allowedTypesIsAny;
/**
 * 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
 */
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:
        (0, core_utils_1.assert)(Array.isArray(allowedTypes), 0x7bc /* Invalid allowedTypes */);
        for (const allowedType of allowedTypes) {
            if (allowedType === exports.Any) {
                (0, core_utils_1.assert)(allowedTypes.length === 1, 0x7bd /* Invalid Any in allowedTypes */);
            }
            else if (typeof allowedType !== "function") {
                (0, core_utils_1.assert)(allowedType instanceof TreeNodeSchema, 0x7be /* Invalid entry in allowedTypes */);
            }
        }
        this.lazyTypes = new core_utils_1.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 (0, util_1.compareSets)({
            a: this.types,
            b: other.types,
            aExtra: () => false,
            bExtra: () => false,
        });
    }
}
exports.TreeFieldSchema = TreeFieldSchema;
/**
 * Schema for a field which must always be empty.
 */
TreeFieldSchema.empty = TreeFieldSchema.create(default_field_kinds_1.FieldKinds.forbidden, []);
/**
 * Convert {@link AllowedTypes} to {@link TreeTypeSet}.
 * @alpha
 */
function allowedTypesSchemaSet(t) {
    if (allowedTypesIsAny(t)) {
        return exports.Any;
    }
    const list = t.map((value) => {
        if (typeof value === "function") {
            return value();
        }
        return value;
    });
    return new Set(list);
}
exports.allowedTypesSchemaSet = allowedTypesSchemaSet;
/**
 * Convert {@link AllowedTypes} to {@link TreeTypeSet}.
 * @alpha
 */
function allowedTypesToTypeSet(t) {
    const list = allowedTypesSchemaSet(t);
    if (list === exports.Any) {
        return undefined;
    }
    const names = Array.from(list, (type) => {
        (0, core_utils_1.assert)(type instanceof TreeNodeSchema, 0x7bf /* invalid allowed type */);
        return type.name;
    });
    return new Set(names);
}
exports.allowedTypesToTypeSet = allowedTypesToTypeSet;
{
}
//# sourceMappingURL=typedTreeSchema.js.map