"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.markSchemaMostDerived = exports.checkForUninitializedSchema = exports.evaluateLazySchema = exports.unannotateAllowedType = exports.unannotateImplicitAllowedTypes = exports.normalizeAnnotatedAllowedTypes = exports.normalizeToAnnotatedAllowedType = exports.normalizeAllowedTypes = exports.SchemaUpgrade = exports.createSchemaUpgrade = exports.isAnnotatedAllowedType = exports.isAnnotatedAllowedTypes = void 0;
const internal_1 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../../util/index.js");
const flexList_js_1 = require("./flexList.js");
const treeNodeSchema_js_1 = require("./treeNodeSchema.js");
const treeNodeValid_js_1 = require("./treeNodeValid.js");
/**
 * Checks if the input is an {@link AnnotatedAllowedTypes}.
 */
function isAnnotatedAllowedTypes(allowedTypes) {
    checkForUninitializedSchema(allowedTypes);
    return (
    // Class based schema, and lazy schema references report type "function": filtering them out with typeof makes narrowing based on members mostly safe
    typeof allowedTypes === "object" && "metadata" in allowedTypes && "types" in allowedTypes);
}
exports.isAnnotatedAllowedTypes = isAnnotatedAllowedTypes;
/**
 * Checks if the given allowed type is annotated with {@link AllowedTypeMetadata}.
 */
function isAnnotatedAllowedType(allowedType) {
    checkForUninitializedSchema(allowedType);
    // Class based schema, and lazy schema references report type "function": filtering them out with typeof makes narrowing based on members mostly safe
    return typeof allowedType === "object" && "metadata" in allowedType && "type" in allowedType;
}
exports.isAnnotatedAllowedType = isAnnotatedAllowedType;
/**
 * Unique token used to upgrade schemas and determine if a particular upgrade has been completed.
 * @remarks
 * Create using {@link SchemaStaticsAlpha.staged}.
 * @privateRemarks
 * TODO:#38722 implement runtime schema upgrades.
 * Until then, the class purely behaves mostly as a placeholder.
 * TODO: Consider allowing users to store a name for the upgrade to use in error messages.
 * @sealed @alpha
 */
class SchemaUpgrade {
    constructor() { }
}
exports.SchemaUpgrade = SchemaUpgrade;
(() => {
    exports.createSchemaUpgrade = () => new SchemaUpgrade();
})();
/**
 * Normalizes a {@link ImplicitAllowedTypes} to a set of {@link TreeNodeSchema}s, by eagerly evaluating any
 * lazy schema declarations.
 *
 * @remarks Note: this must only be called after all required schemas have been declared, otherwise evaluation of
 * recursive schemas may fail.
 *
 * @internal
 */
function normalizeAllowedTypes(types) {
    // remove annotations before normalizing
    const unannotated = unannotateImplicitAllowedTypes(types);
    const normalized = new Set();
    if ((0, index_js_1.isReadonlyArray)(unannotated)) {
        // Types array must not be modified after it is normalized since that would result in the user of the normalized data having wrong (out of date) content.
        Object.freeze(unannotated);
        for (const lazyType of unannotated) {
            normalized.add(evaluateLazySchema(lazyType));
        }
    }
    else {
        normalized.add(evaluateLazySchema(unannotated));
    }
    return normalized;
}
exports.normalizeAllowedTypes = normalizeAllowedTypes;
/**
 * Normalizes an allowed type to an {@link AnnotatedAllowedType}, by adding empty annotations if they don't already exist.
 */
function normalizeToAnnotatedAllowedType(type) {
    return isAnnotatedAllowedType(type)
        ? type
        : {
            metadata: {},
            type,
        };
}
exports.normalizeToAnnotatedAllowedType = normalizeToAnnotatedAllowedType;
/**
 * Normalizes a {@link ImplicitAnnotatedAllowedTypes} to a set of {@link AnnotatedAllowedSchema}s, by eagerly evaluating any
 * lazy schema declarations and adding empty metadata if it doesn't already exist.
 *
 * @remarks Note: this must only be called after all required schemas have been declared, otherwise evaluation of
 * recursive schemas may fail.
 */
function normalizeAnnotatedAllowedTypes(types) {
    const typesWithoutAnnotation = isAnnotatedAllowedTypes(types) ? types.types : types;
    const annotatedTypes = [];
    if ((0, index_js_1.isReadonlyArray)(typesWithoutAnnotation)) {
        for (const annotatedType of typesWithoutAnnotation) {
            if (isAnnotatedAllowedType(annotatedType)) {
                annotatedTypes.push({
                    type: evaluateLazySchema(annotatedType.type),
                    metadata: annotatedType.metadata,
                });
            }
            else {
                annotatedTypes.push({ type: evaluateLazySchema(annotatedType), metadata: {} });
            }
        }
    }
    else {
        if (isAnnotatedAllowedType(typesWithoutAnnotation)) {
            annotatedTypes.push({
                type: evaluateLazySchema(typesWithoutAnnotation.type),
                metadata: typesWithoutAnnotation.metadata,
            });
        }
        else {
            annotatedTypes.push({ type: evaluateLazySchema(typesWithoutAnnotation), metadata: {} });
        }
    }
    return {
        metadata: isAnnotatedAllowedTypes(types) ? types.metadata : {},
        types: annotatedTypes,
    };
}
exports.normalizeAnnotatedAllowedTypes = normalizeAnnotatedAllowedTypes;
/**
 * Converts an {@link ImplicitAnnotatedAllowedTypes} to an {@link ImplicitAllowedTypes}s, by removing
 * any annotations.
 * @remarks
 * This does not evaluate any lazy schemas.
 */
function unannotateImplicitAllowedTypes(types) {
    return (isAnnotatedAllowedTypes(types)
        ? types.types.map(unannotateAllowedType)
        : (0, index_js_1.isReadonlyArray)(types)
            ? types.map(unannotateAllowedType)
            : isAnnotatedAllowedType(types)
                ? types.type
                : types);
}
exports.unannotateImplicitAllowedTypes = unannotateImplicitAllowedTypes;
/**
 * Converts an {@link AnnotatedAllowedType} to an {@link LazyItem} by removing any annotations.
 * @remarks
 * This does not evaluate any lazy schemas.
 */
function unannotateAllowedType(allowedType) {
    return isAnnotatedAllowedType(allowedType)
        ? allowedType.type
        : allowedType;
}
exports.unannotateAllowedType = unannotateAllowedType;
const cachedLazyItem = new WeakMap();
/**
 * Returns the schema referenced by the {@link LazyItem}.
 * @remarks
 * Caches results to handle {@link LazyItem}s which compute their resulting schema.
 * @alpha
 */
function evaluateLazySchema(value) {
    const evaluatedSchema = (0, flexList_js_1.isLazy)(value)
        ? (0, index_js_1.getOrCreate)(cachedLazyItem, value, value)
        : value;
    checkForUninitializedSchema(evaluatedSchema);
    markSchemaMostDerived(evaluatedSchema);
    return evaluatedSchema;
}
exports.evaluateLazySchema = evaluateLazySchema;
/**
 * Throws a UsageError if the provided schema is undefined, most likely due to being used before it was initialized.
 */
function checkForUninitializedSchema(schema) {
    if (schema === undefined) {
        throw new internal_1.UsageError(`Encountered an undefined schema. This could indicate that some referenced schema has not yet been instantiated. Consider using a lazy schema reference (like "() => schema") or delaying the evaluation of the lazy reference if one is already being used.`);
    }
}
exports.checkForUninitializedSchema = checkForUninitializedSchema;
/**
 * Indicates that the provided schema is the "most derived" version in its class hierarchy.
 *
 * @param oneTimeInitialize - If true this runs {@link TreeNodeValid.oneTimeInitialize} which does even more initialization and validation.
 * `oneTimeInitialize` can't safely be run until all transitively referenced schema are defined, so which cases can safely use it are more limited.
 * When legal for the caller to set this to true, it is preferred, but it is often not safe due to possible forward references.
 * @remarks
 * See {@link MostDerivedData} and {@link SchemaFactory} for details on what a "most derived" schema is and why it matters.
 *
 * This is a helper for invoking {@link TreeNodeValid.markMostDerived} for {@link TreeNodeSchema}.
 *
 * Calling this helps with error messages about invalid schema usage (See {@link SchemaFactory} for the rules, some of which this helps validate).
 * Typically this should be called for each schema as early as practical to improve error reporting for invalid usages of schema
 * (using two different schema derived from the same {@link SchemaFactory} produced base class).
 *
 * Note that construction of actual {@link TreeNode} instances or use of a schema transitively in a {@link TreeViewConfiguration} already do this,
 * so any calls to this that is unconditionally after that point for the given schema is not needed.
 * Instead most usages of this should be from those cases, and from miscellaneous cases where a schema is passed into an public API where theoretically someone could accidentally
 * pass in a base class of a schema instead of the most derived one.
 */
function markSchemaMostDerived(schema, oneTimeInitialize = false) {
    // Leaf schema are not classes, and thus do not need to be marked as most derived.
    if (schema.kind === treeNodeSchema_js_1.NodeKind.Leaf) {
        return;
    }
    const schemaValid = (0, treeNodeValid_js_1.schemaAsTreeNodeValid)(schema);
    if (oneTimeInitialize) {
        schemaValid.oneTimeInitialize();
    }
    else {
        schemaValid.markMostDerived();
    }
}
exports.markSchemaMostDerived = markSchemaMostDerived;
//# sourceMappingURL=allowedTypes.js.map