"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.schemaIsObjectNode = exports.schemaIsFieldNode = exports.schemaIsLeaf = exports.schemaIsMap = exports.intoStoredSchemaCollection = exports.intoStoredSchema = exports.allowedTypesToTypeSet = exports.allowedTypesSchemaSet = exports.FlexFieldSchema = exports.allowedTypesIsAny = exports.Any = exports.FlexFieldNodeSchema = exports.FlexObjectNodeSchema = exports.LeafNodeSchema = exports.FlexMapNodeSchema = exports.TreeNodeSchemaBase = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../../util/index.js");
const index_js_3 = require("../default-schema/index.js");
const typeUtils_js_1 = require("./typeUtils.js");
{
}
/**
 * T must extend TreeSchemaSpecification.
 * This can not be enforced using TypeScript since doing so breaks recursive type support.
 * See note on SchemaBuilder.fieldRecursive.
 * @internal
 */
class TreeNodeSchemaBase {
    constructor(builder, name, info, stored) {
        this.builder = builder;
        this.name = name;
        this.info = info;
        this.stored = stored;
    }
}
exports.TreeNodeSchemaBase = TreeNodeSchemaBase;
/**
 * @internal
 */
class FlexMapNodeSchema extends TreeNodeSchemaBase {
    get mapFields() {
        return this.info;
    }
    static create(builder, name, specification) {
        return new FlexMapNodeSchema(builder, name, specification, new index_js_1.MapNodeStoredSchema(specification.stored));
    }
    getFieldSchema(field) {
        return this.info;
    }
}
exports.FlexMapNodeSchema = FlexMapNodeSchema;
/**
 * @internal
 */
class LeafNodeSchema extends TreeNodeSchemaBase {
    get leafValue() {
        return this.info;
    }
    static create(builder, name, specification) {
        return new LeafNodeSchema(builder, name, specification, new index_js_1.LeafNodeStoredSchema(specification));
    }
    getFieldSchema(field) {
        return FlexFieldSchema.empty;
    }
}
exports.LeafNodeSchema = LeafNodeSchema;
/**
 * @internal
 */
class FlexObjectNodeSchema extends TreeNodeSchemaBase {
    static create(builder, name, specification) {
        const objectNodeFieldsObject = normalizeStructFields(specification);
        const objectNodeFields = (0, typeUtils_js_1.objectToMapTyped)(objectNodeFieldsObject);
        return new FlexObjectNodeSchema(builder, name, specification, objectNodeFieldsObject, objectNodeFields);
    }
    constructor(builder, name, info, objectNodeFieldsObject, 
    // Allows reading fields through the normal map.
    // Stricter typing caused Specification to no longer be covariant, so has been removed.
    objectNodeFields) {
        const fields = (0, index_js_2.mapIterable)(objectNodeFields, ([k, v]) => [k, v.stored]);
        super(builder, name, info, new index_js_1.ObjectNodeStoredSchema(new Map(fields)));
        this.objectNodeFieldsObject = objectNodeFieldsObject;
        this.objectNodeFields = objectNodeFields;
    }
    getFieldSchema(field) {
        return this.objectNodeFields.get(field) ?? FlexFieldSchema.empty;
    }
}
exports.FlexObjectNodeSchema = FlexObjectNodeSchema;
/**
 * TODO: remove or replace (or subclass) this with more specific types, like "List".
 * @internal
 */
class FlexFieldNodeSchema extends TreeNodeSchemaBase {
    static create(builder, name, specification) {
        return new FlexFieldNodeSchema(builder, name, specification);
    }
    constructor(builder, name, info) {
        const objectNodeFields = new Map([[index_js_1.EmptyKey, info.stored]]);
        super(builder, name, info, new index_js_1.ObjectNodeStoredSchema(objectNodeFields));
    }
    getFieldSchema(field) {
        return (field ?? index_js_1.EmptyKey) === index_js_1.EmptyKey
            ? this.info
            : FlexFieldSchema.empty;
    }
}
exports.FlexFieldNodeSchema = FlexFieldNodeSchema;
function normalizeStructFields(fields) {
    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 FlexFieldSchema.empty;
    }
    (0, internal_1.assert)(t instanceof FlexFieldSchema, 0x6ae /* invalid TreeFieldSchema */);
    return t;
}
/**
 * Allow any node (as long as it meets the schema for its own type).
 * @internal
 */
exports.Any = "Any";
/**
 * Checks if an {@link FlexAllowedTypes} is {@link (Any:type)}.
 * @internal
 */
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
 * @internal
 */
class FlexFieldSchema {
    /**
     * Constructs a TreeFieldSchema.
     * @privateRemarks
     * Alias for the constructor, but with extends clause for the `Types` parameter that {@link FlexFieldSchema} can not have (due to recursive type issues).
     */
    static create(kind, allowedTypes) {
        return new FlexFieldSchema(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 FlexFieldSchema.create} when possible.
     */
    static createUnsafe(kind, allowedTypes) {
        return new FlexFieldSchema(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, internal_1.assert)(Array.isArray(allowedTypes), 0x7bc /* Invalid allowedTypes */);
        for (const allowedType of allowedTypes) {
            if (allowedType === exports.Any) {
                (0, internal_1.assert)(allowedTypes.length === 1, 0x7bd /* Invalid Any in allowedTypes */);
            }
            else if (typeof allowedType !== "function") {
                (0, internal_1.assert)(allowedType instanceof TreeNodeSchemaBase, 0x7be /* Invalid entry in allowedTypes */);
            }
        }
        const lazy = new internal_1.Lazy(() => {
            const input = this.allowedTypes;
            const schema = allowedTypesSchemaSet(input);
            return {
                names: allowedTypesToTypeSet(input),
                schema,
                monomorphicChildType: schema !== exports.Any ? (0, index_js_2.oneFromSet)(schema) : undefined,
            };
        });
        this.lazyTypes = lazy;
        this.stored = {
            kind: this.kind.identifier,
            get types() {
                return lazy.value.names;
            },
        };
    }
    /**
     * 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 FlexFieldSchema.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 FlexFieldSchema.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;
    }
    /**
     * If exactly one type of child is allowed in this field, it is provided here.
     * @remarks
     * Some code paths (like unboxing and compressed tree encoding) special case schema with exactly one allowed type.
     * This field allows for simple and optimized handling of this case.
     */
    get monomorphicChildType() {
        return this.lazyTypes.value.monomorphicChildType;
    }
    /**
     * 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, index_js_2.compareSets)({
            a: this.types,
            b: other.types,
            aExtra: () => false,
            bExtra: () => false,
        });
    }
}
exports.FlexFieldSchema = FlexFieldSchema;
/**
 * Schema for a field which must always be empty.
 */
FlexFieldSchema.empty = FlexFieldSchema.create(index_js_3.FieldKinds.forbidden, []);
/**
 * Convert {@link FlexAllowedTypes} to {@link TreeTypeSet}.
 * @internal
 */
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 FlexAllowedTypes} to {@link TreeTypeSet}.
 * @internal
 */
function allowedTypesToTypeSet(t) {
    const list = allowedTypesSchemaSet(t);
    if (list === exports.Any) {
        return undefined;
    }
    const names = Array.from(list, (type) => {
        (0, internal_1.assert)(type instanceof TreeNodeSchemaBase, 0x7bf /* invalid allowed type */);
        return type.name;
    });
    return new Set(names);
}
exports.allowedTypesToTypeSet = allowedTypesToTypeSet;
/**
 * Converts a {@link FlexTreeSchema} into a {@link TreeStoredSchema}.
 */
function intoStoredSchema(treeSchema) {
    return {
        rootFieldSchema: treeSchema.rootFieldSchema.stored,
        ...intoStoredSchemaCollection(treeSchema),
    };
}
exports.intoStoredSchema = intoStoredSchema;
/**
 * Converts a {@link SchemaCollection} into a {@link StoredSchemaCollection}.
 */
function intoStoredSchemaCollection(treeSchema) {
    return {
        nodeSchema: new Map((0, index_js_2.mapIterable)(treeSchema.nodeSchema.entries(), ([k, v]) => [k, v.stored])),
    };
}
exports.intoStoredSchemaCollection = intoStoredSchemaCollection;
// These schema type narrowing functions are preferred over `instanceof` due to being easier to migrate to class based schema.
/**
 * Checks if a {@link FlexTreeNodeSchema} is a {@link FlexMapNodeSchema}.
 * @internal
 */
function schemaIsMap(schema) {
    return schema instanceof FlexMapNodeSchema;
}
exports.schemaIsMap = schemaIsMap;
/**
 * Checks if a {@link FlexTreeNodeSchema} is a {@link LeafNodeSchema}.
 * @internal
 */
function schemaIsLeaf(schema) {
    return schema instanceof LeafNodeSchema;
}
exports.schemaIsLeaf = schemaIsLeaf;
/**
 * Checks if a {@link FlexTreeNodeSchema} is a {@link FlexFieldNodeSchema}.
 * @internal
 */
function schemaIsFieldNode(schema) {
    return schema instanceof FlexFieldNodeSchema;
}
exports.schemaIsFieldNode = schemaIsFieldNode;
/**
 * Checks if a {@link FlexTreeNodeSchema} is a {@link FlexObjectNodeSchema}.
 * @internal
 */
function schemaIsObjectNode(schema) {
    return schema instanceof FlexObjectNodeSchema;
}
exports.schemaIsObjectNode = schemaIsObjectNode;
//# sourceMappingURL=typedTreeSchema.js.map