"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPossibleTypes = exports.unhydratedFlexTreeFromInsertableNode = exports.unhydratedFlexTreeFromInsertable = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../util/index.js");
const index_js_2 = require("./core/index.js");
const createContext_js_1 = require("./createContext.js");
const fieldSchema_js_1 = require("./fieldSchema.js");
/**
 * Transforms an input {@link TypedNode} tree to an {@link UnhydratedFlexTreeNode}.
 * @param data - The input tree to be converted.
 * If the data is an unsupported value (e.g. NaN), a fallback value will be used when supported,
 * otherwise an error will be thrown.
 *
 * Fallbacks:
 *
 * * `NaN` =\> `null`
 *
 * * `+/-∞` =\> `null`
 *
 * * `-0` =\> `+0`
 *
 * For fields with a default value, the field may be omitted.
 * If `context` is not provided, defaults which require a context will be left empty which can be out of schema.
 *
 * @param allowedTypes - The set of types allowed by the parent context. Used to validate the input tree.
 * @remarks
 * The resulting tree will be populated with any defaults from {@link FieldProvider}s in the schema.
 *
 * Often throws UsageErrors for invalid data, but may miss some cases.
 *
 * Output should comply with the provided view schema, but this is not explicitly validated:
 * validation against stored schema (to guard against document corruption) is done elsewhere.
 */
function unhydratedFlexTreeFromInsertable(data, allowedTypes) {
    const normalizedFieldSchema = (0, fieldSchema_js_1.normalizeFieldSchema)(allowedTypes);
    if (data === undefined) {
        // TODO: this code-path should support defaults
        if (normalizedFieldSchema.kind !== fieldSchema_js_1.FieldKind.Optional) {
            throw new internal_2.UsageError(`Got undefined for non-optional field expecting one of ${quotedAllowedTypesWithNames(normalizedFieldSchema.allowedTypeSet)}`);
        }
        return undefined;
    }
    const flexTree = unhydratedFlexTreeFromInsertableNode(data, normalizedFieldSchema.allowedTypeSet);
    return flexTree;
}
exports.unhydratedFlexTreeFromInsertable = unhydratedFlexTreeFromInsertable;
/**
 * Throw a usage error with a helpful message about `schema` not being in `allowedTypes` for insertable content.
 */
function allowedTypesInsertableSchemaError(allowedTypes, schema) {
    (0, internal_1.debugAssert)(() => !allowedTypes.has(schema) ||
        "This function should only be called if the schema is not in the allowed types.");
    const map = new Map([...allowedTypes].map((s) => [s.identifier, s]));
    const expected = map.get(schema.identifier);
    if (expected !== undefined) {
        throw new internal_2.UsageError(`A node with schema ${quotedSchemaIdentifierWithName(schema)} was provided where a node with that identifier is allowed, but the actual schema required (${quotedSchemaIdentifierWithName(expected)}) is not the same schema object.
TreeNodeSchema have significant object identity and thus the exact same object must be used as the schema when defining what nodes are allowed and when constructing the node to use.`);
    }
    throw new internal_2.UsageError(`Expected insertable for one of ${quotedAllowedTypesWithNames(allowedTypes)}. Got node with schema ${quotedSchemaIdentifierWithName(schema)}.
Nodes are valid insertable objects, but only if their schema are in the allowed list.`);
}
/**
 * Gets a description of a schema for use in error messages.
 */
function quotedSchemaIdentifierWithName(schema) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
    return `${JSON.stringify(schema.identifier)} (name: ${JSON.stringify(schema.name)})`;
}
/**
 * Gets a description of an allowedTypes for use in error messages.
 */
function quotedAllowedTypesWithNames(allowedTypes) {
    return `[${[...allowedTypes].map(quotedSchemaIdentifierWithName).join(", ")}]`;
}
/**
 * Copy content from `data` into a UnhydratedFlexTreeNode.
 */
function unhydratedFlexTreeFromInsertableNode(data, allowedTypes) {
    if ((0, index_js_2.isTreeNode)(data)) {
        const kernel = (0, index_js_2.getKernel)(data);
        const inner = kernel.getInnerNodeIfUnhydrated();
        if (inner === undefined) {
            // The node is already hydrated, meaning that it already got inserted into the tree previously
            throw new internal_2.UsageError(`A node with schema ${quotedSchemaIdentifierWithName(kernel.schema)} was inserted into the tree more than once. This is not supported.`);
        }
        else {
            if (!allowedTypes.has(kernel.schema)) {
                allowedTypesInsertableSchemaError(allowedTypes, kernel.schema);
            }
            return inner;
        }
    }
    const schema = getType(data, allowedTypes);
    const handler = (0, index_js_2.getTreeNodeSchemaPrivateData)(schema).idempotentInitialize();
    const result = handler.toFlexContent(data, allowedTypes);
    // Might not match schema due to fallbacks, see TODO on toFlexContent
    // TODO: fix TODO in `toFlexContent`, and remove this.
    const finalSchema = (0, index_js_1.oneFromIterable)((0, index_js_1.filterIterable)(allowedTypes, (s) => s.identifier === result[0].type)) ??
        (0, internal_1.fail)(0xc9d /* missing schema */);
    return new index_js_2.UnhydratedFlexTreeNode(...result, (0, createContext_js_1.getUnhydratedContext)(finalSchema));
}
exports.unhydratedFlexTreeFromInsertableNode = unhydratedFlexTreeFromInsertableNode;
function getType(data, allowedTypes) {
    const possibleTypes = getPossibleTypes(allowedTypes, data);
    if (possibleTypes.length === 0) {
        throw new internal_2.UsageError(`The provided data is incompatible with all of the types allowed by the schema. The set of allowed types is: ${quotedAllowedTypesWithNames(allowedTypes)}.`);
    }
    if (!(0, index_js_1.hasSingle)(possibleTypes)) {
        throw new internal_2.UsageError(`The provided data is compatible with more than one type allowed by the schema.
The set of possible types is ${quotedAllowedTypesWithNames(possibleTypes)}.
Explicitly construct an unhydrated node of the desired type to disambiguate.
For class-based schema, this can be done by replacing an expression like "{foo: 1}" with "new MySchema({foo: 1})".`);
    }
    return possibleTypes[0];
}
/**
 * Returns all types for which the data is schema-compatible.
 * @remarks This will respect the {@link contentSchemaSymbol} property on data to disambiguate types - if present, only that type will be returned.
 */
function getPossibleTypes(allowedTypes, data) {
    (0, internal_1.assert)(data !== undefined, 0x889 /* undefined cannot be used as FactoryContent. */);
    let toCheck;
    if (typeof data === "object" && data !== null && index_js_2.contentSchemaSymbol in data) {
        // If the data has an explicit brand via contentSchemaSymbol, only check that type.
        const type = data[index_js_2.contentSchemaSymbol];
        toCheck = (0, index_js_1.filterIterable)(allowedTypes, (schema) => schema.identifier === type);
    }
    else {
        toCheck = allowedTypes;
    }
    // Start at the lowest level of compat we would ever accept: this discards types which are less compatible.
    let best = index_js_2.CompatibilityLevel.Low;
    const possibleTypes = [];
    for (const schema of toCheck) {
        const handler = (0, index_js_2.getTreeNodeSchemaPrivateData)(schema).idempotentInitialize();
        const level = handler.shallowCompatibilityTest(data);
        if (level > best) {
            possibleTypes.length = 0;
            best = level;
        }
        if (best === level) {
            possibleTypes.push(schema);
        }
    }
    return possibleTypes;
}
exports.getPossibleTypes = getPossibleTypes;
//# sourceMappingURL=unhydratedFlexTreeFromInsertable.js.map