/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { compareSets, fail } from "../../util";
import { ValueSchema, } from "../../core";
import { Multiplicity } from "./fieldKind";
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 */
export function allowsTreeSuperset(policy, originalData, original, superset) {
    if (isNeverTree(policy, originalData, original)) {
        return true;
    }
    if (!allowsValueSuperset(original.value, superset.value)) {
        return false;
    }
    if (!allowsFieldSuperset(policy, originalData, original.extraLocalFields, superset.extraLocalFields)) {
        return false;
    }
    if (original.extraGlobalFields && !superset.extraGlobalFields) {
        return false;
    }
    if (!compareSets({
        a: original.globalFields,
        b: superset.globalFields,
        // true iff the original field must always be empty, or superset supports extra global fields.
        aExtra: (originalField) => {
            var _a;
            return superset.extraGlobalFields ||
                allowsFieldSuperset(policy, originalData, (_a = originalData.globalFieldSchema.get(originalField)) !== null && _a !== void 0 ? _a : policy.defaultGlobalFieldSchema, policy.defaultGlobalFieldSchema);
        },
        // true iff the new field can be empty, since it may be empty in original
        bExtra: (supersetField) => {
            var _a;
            return allowsFieldSuperset(policy, originalData, policy.defaultGlobalFieldSchema, (_a = originalData.globalFieldSchema.get(supersetField)) !== null && _a !== void 0 ? _a : policy.defaultGlobalFieldSchema);
        },
    })) {
        return false;
    }
    if (!compareSets({
        a: original.localFields,
        b: superset.localFields,
        aExtra: (originalField) => {
            var _a;
            return allowsFieldSuperset(policy, originalData, (_a = original.localFields.get(originalField)) !== null && _a !== void 0 ? _a : fail("missing expected field"), superset.extraLocalFields);
        },
        bExtra: (supersetField) => {
            var _a;
            return allowsFieldSuperset(policy, originalData, original.extraLocalFields, (_a = superset.localFields.get(supersetField)) !== null && _a !== void 0 ? _a : fail("missing expected field"));
        },
        same: (sameField) => {
            var _a, _b;
            return allowsFieldSuperset(policy, originalData, (_a = original.localFields.get(sameField)) !== null && _a !== void 0 ? _a : fail("missing expected field"), (_b = superset.localFields.get(sameField)) !== null && _b !== void 0 ? _b : fail("missing expected field"));
        },
    })) {
        return false;
    }
    return true;
}
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 */
export function allowsValueSuperset(original, superset) {
    return original === superset || superset === ValueSchema.Serializable;
}
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 */
export function allowsFieldSuperset(policy, originalData, original, superset) {
    var _a;
    return ((_a = policy.fieldKinds.get(original.kind.identifier)) !== null && _a !== void 0 ? _a : fail("missing kind")).allowsFieldSuperset(policy, originalData, original.types, superset);
}
/**
 * @returns true iff `superset` is a superset of `original`.
 *
 * This does not require a strict (aka proper) superset: equivalent schema will return true.
 */
export 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;
}
/**
 * @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.
 */
export function allowsRepoSuperset(policy, original, superset) {
    var _a, _b, _c;
    const fields = new Set([
        ...original.globalFieldSchema.keys(),
        ...superset.globalFieldSchema.keys(),
    ]);
    for (const key of fields) {
        // 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, (_a = original.globalFieldSchema.get(key)) !== null && _a !== void 0 ? _a : policy.defaultGlobalFieldSchema, (_b = superset.globalFieldSchema.get(key)) !== null && _b !== void 0 ? _b : policy.defaultGlobalFieldSchema)) {
            return false;
        }
    }
    for (const [key, schema] of original.treeSchema) {
        // 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, (_c = superset.treeSchema.get(key)) !== null && _c !== void 0 ? _c : policy.defaultTreeSchema)) {
            return false;
        }
    }
    return true;
}
/**
 * @alpha
 */
export function isNeverField(policy, originalData, field) {
    return isNeverFieldRecursive(policy, originalData, field, new Set());
}
export function isNeverFieldRecursive(policy, originalData, field, parentTypeStack) {
    var _a, _b;
    if (((_a = policy.fieldKinds.get(field.kind.identifier)) !== null && _a !== void 0 ? _a : fail("missing field kind"))
        .multiplicity === Multiplicity.Value &&
        field.types !== undefined) {
        for (const type of field.types) {
            if (!isNeverTreeRecursive(policy, originalData, (_b = originalData.treeSchema.get(type)) !== null && _b !== void 0 ? _b : policy.defaultTreeSchema, 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;
}
/**
 * Returns true iff there are no possible trees that could meet this schema.
 * Trees which are infinate (like endless linked lists) are considered impossible.
 */
export function isNeverTree(policy, originalData, tree) {
    return isNeverTreeRecursive(policy, originalData, tree, new Set());
}
/**
 * Returns true iff there are no possible trees that could meet this schema.
 * Trees which are infinate (like endless linked lists) are considered impossible.
 */
export function isNeverTreeRecursive(policy, originalData, tree, parentTypeStack) {
    var _a, _b;
    if (parentTypeStack.has(tree)) {
        return true;
    }
    try {
        parentTypeStack.add(tree);
        if (((_a = policy.fieldKinds.get(tree.extraLocalFields.kind.identifier)) !== null && _a !== void 0 ? _a : fail("missing field kind")).multiplicity === Multiplicity.Value) {
            return true;
        }
        for (const field of tree.localFields.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;
            }
        }
        for (const field of tree.globalFields) {
            const schema = (_b = originalData.globalFieldSchema.get(field)) !== null && _b !== void 0 ? _b : policy.defaultGlobalFieldSchema;
            if (isNeverField(policy, originalData, schema)) {
                return true;
            }
        }
        return false;
    }
    finally {
        parentTypeStack.delete(tree);
    }
}
//# sourceMappingURL=comparison.js.map