"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeField = exports.isNeverTreeRecursive = exports.isNeverTree = exports.isNeverFieldRecursive = exports.isNeverField = exports.allowsRepoSuperset = exports.allowsTreeSchemaIdentifierSuperset = exports.allowsFieldSuperset = exports.allowsValueSuperset = exports.allowsTreeSuperset = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const util_1 = require("../../util");
const core_1 = require("../../core");
const multiplicity_1 = require("../multiplicity");
const fieldKind_1 = require("./fieldKind");
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 *
 * `undefined` TreeNodeStoredSchema means the schema is not present (and thus treated as a NeverTree).
 */
function allowsTreeSuperset(policy, originalData, original, superset) {
    if (isNeverTree(policy, originalData, original)) {
        return true;
    }
    if (isNeverTree(policy, originalData, superset)) {
        return false;
    }
    (0, core_utils_1.assert)(original !== undefined, 0x716 /* only never trees have undefined schema */);
    (0, core_utils_1.assert)(superset !== undefined, 0x717 /* only never trees have undefined schema */);
    if (original instanceof core_1.LeafNodeStoredSchema) {
        if (superset instanceof core_1.LeafNodeStoredSchema) {
            return allowsValueSuperset(original.leafValue, superset.leafValue);
        }
        return false;
    }
    (0, core_utils_1.assert)(original instanceof core_1.MapNodeStoredSchema || original instanceof core_1.ObjectNodeStoredSchema, 0x893 /* unsupported node kind */);
    (0, core_utils_1.assert)(superset instanceof core_1.MapNodeStoredSchema || superset instanceof core_1.ObjectNodeStoredSchema, 0x894 /* unsupported node kind */);
    if (original instanceof core_1.MapNodeStoredSchema) {
        if (superset instanceof core_1.MapNodeStoredSchema) {
            return allowsFieldSuperset(policy, originalData, normalizeField(original.mapFields), normalizeField(superset.mapFields));
        }
        return false;
    }
    (0, core_utils_1.assert)(original instanceof core_1.ObjectNodeStoredSchema, 0x895 /* unsupported node kind */);
    if (superset instanceof core_1.MapNodeStoredSchema) {
        for (const [_key, field] of original.objectNodeFields) {
            if (!allowsFieldSuperset(policy, originalData, normalizeField(field), normalizeField(superset.mapFields))) {
                return false;
            }
        }
        return true;
    }
    (0, core_utils_1.assert)(superset instanceof core_1.ObjectNodeStoredSchema, 0x896 /* unsupported node kind */);
    return (0, util_1.compareSets)({
        a: original.objectNodeFields,
        b: superset.objectNodeFields,
        aExtra: (originalField) => allowsFieldSuperset(policy, originalData, original.objectNodeFields.get(originalField) ?? (0, util_1.fail)("missing expected field"), normalizeField(undefined)),
        bExtra: (supersetField) => allowsFieldSuperset(policy, originalData, normalizeField(undefined), superset.objectNodeFields.get(supersetField) ?? (0, util_1.fail)("missing expected field")),
        same: (sameField) => allowsFieldSuperset(policy, originalData, original.objectNodeFields.get(sameField) ?? (0, util_1.fail)("missing expected field"), superset.objectNodeFields.get(sameField) ?? (0, util_1.fail)("missing expected field")),
    });
}
exports.allowsTreeSuperset = allowsTreeSuperset;
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 */
function allowsValueSuperset(original, superset) {
    return original === superset;
}
exports.allowsValueSuperset = allowsValueSuperset;
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 */
function allowsFieldSuperset(policy, originalData, original, superset) {
    return (0, fieldKind_1.withEditor)(policy.fieldKinds.get(original.kind.identifier) ?? (0, util_1.fail)("missing kind")).allowsFieldSuperset(policy, originalData, original.types, superset);
}
exports.allowsFieldSuperset = allowsFieldSuperset;
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 */
function allowsTreeSchemaIdentifierSuperset(original, superset) {
    if (superset === undefined) {
        return true;
    }
    if (original === undefined) {
        return false;
    }
    for (const originalType of original) {
        if (!superset.has(originalType)) {
            return false;
        }
    }
    return true;
}
exports.allowsTreeSchemaIdentifierSuperset = allowsTreeSchemaIdentifierSuperset;
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 *
 * A version of this that assumes a specific root field could be slightly more permissive in some simple cases,
 * however if any extra fields and fields with unconstrained types are reachable,
 * it would have to compare everything anyway.
 */
function allowsRepoSuperset(policy, original, superset) {
    {
        // TODO: I think its ok to use the field from superset here, but I should confirm it is, and document why.
        if (!allowsFieldSuperset(policy, original, original.rootFieldSchema, superset.rootFieldSchema)) {
            return false;
        }
    }
    for (const [key, schema] of original.nodeSchema) {
        // TODO: I think its ok to use the tree from superset here, but I should confirm it is, and document why.
        if (!allowsTreeSuperset(policy, original, schema, superset.nodeSchema.get(key))) {
            return false;
        }
    }
    return true;
}
exports.allowsRepoSuperset = allowsRepoSuperset;
/**
 * @internal
 */
function isNeverField(policy, originalData, field) {
    return isNeverFieldRecursive(policy, originalData, field, new Set());
}
exports.isNeverField = isNeverField;
function isNeverFieldRecursive(policy, originalData, field, parentTypeStack) {
    if ((policy.fieldKinds.get(field.kind.identifier) ?? (0, util_1.fail)("missing field kind"))
        .multiplicity === multiplicity_1.Multiplicity.Single &&
        field.types !== undefined) {
        for (const type of field.types) {
            if (!isNeverTreeRecursive(policy, originalData, originalData.nodeSchema.get(type), parentTypeStack)) {
                return false;
            }
        }
        // This field requires at least one child, and there are no types permitted in it that can exist,
        // so this is a never field (field which no sequence of children content could ever be in schema for)
        return true;
    }
    return false;
}
exports.isNeverFieldRecursive = isNeverFieldRecursive;
/**
 * Returns true iff there are no possible trees that could meet this schema.
 * Trees which are infinite (like endless linked lists) are considered impossible.
 *
 * `undefined` means the schema is not present and thus a NeverTree.
 */
function isNeverTree(policy, originalData, treeNode) {
    return isNeverTreeRecursive(policy, originalData, treeNode, new Set());
}
exports.isNeverTree = isNeverTree;
/**
 * Returns true iff there are no possible trees that could meet this schema.
 * Trees which are infinite (like endless linked lists) are considered impossible.
 *
 * `undefined` means the schema is not present and thus a NeverTree.
 */
function isNeverTreeRecursive(policy, originalData, treeNode, parentTypeStack) {
    if (treeNode === undefined) {
        return true;
    }
    if (parentTypeStack.has(treeNode)) {
        return true;
    }
    try {
        parentTypeStack.add(treeNode);
        if (treeNode instanceof core_1.MapNodeStoredSchema) {
            return ((policy.fieldKinds.get(treeNode.mapFields.kind.identifier) ??
                (0, util_1.fail)("missing field kind")).multiplicity === multiplicity_1.Multiplicity.Single);
        }
        else if (treeNode instanceof core_1.ObjectNodeStoredSchema) {
            for (const field of treeNode.objectNodeFields.values()) {
                // TODO: this can recurse infinitely for schema that include themselves in a value field.
                // This breaks even if there are other allowed types.
                // Such schema should either be rejected (as an error here) or considered never (and thus detected by this).
                // This can be done by passing a set/stack of current types recursively here.
                if (isNeverFieldRecursive(policy, originalData, field, parentTypeStack)) {
                    return true;
                }
            }
            return false;
        }
        else {
            (0, core_utils_1.assert)(treeNode instanceof core_1.LeafNodeStoredSchema, 0x897 /* unsupported node kind */);
            return false;
        }
    }
    finally {
        parentTypeStack.delete(treeNode);
    }
}
exports.isNeverTreeRecursive = isNeverTreeRecursive;
function normalizeField(schema) {
    return schema ?? core_1.storedEmptyFieldSchema;
}
exports.normalizeField = normalizeField;
//# sourceMappingURL=comparison.js.map