"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.compliesWithMultiplicity = exports.isFieldInSchema = exports.isNodeInSchema = exports.SchemaValidationErrors = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const valueUtilities_js_1 = require("../valueUtilities.js");
const index_js_2 = require("../../util/index.js");
var SchemaValidationErrors;
(function (SchemaValidationErrors) {
    SchemaValidationErrors[SchemaValidationErrors["NoError"] = 0] = "NoError";
    SchemaValidationErrors[SchemaValidationErrors["Field_KindNotInSchemaPolicy"] = 1] = "Field_KindNotInSchemaPolicy";
    SchemaValidationErrors[SchemaValidationErrors["Field_IncorrectMultiplicity"] = 2] = "Field_IncorrectMultiplicity";
    SchemaValidationErrors[SchemaValidationErrors["Field_NodeTypeNotAllowed"] = 3] = "Field_NodeTypeNotAllowed";
    SchemaValidationErrors[SchemaValidationErrors["LeafNode_InvalidValue"] = 4] = "LeafNode_InvalidValue";
    SchemaValidationErrors[SchemaValidationErrors["LeafNode_FieldsNotAllowed"] = 5] = "LeafNode_FieldsNotAllowed";
    SchemaValidationErrors[SchemaValidationErrors["ObjectNode_FieldNotInSchema"] = 6] = "ObjectNode_FieldNotInSchema";
    SchemaValidationErrors[SchemaValidationErrors["NonLeafNode_ValueNotAllowed"] = 7] = "NonLeafNode_ValueNotAllowed";
    SchemaValidationErrors[SchemaValidationErrors["Node_MissingSchema"] = 8] = "Node_MissingSchema";
    SchemaValidationErrors[SchemaValidationErrors["UnknownError"] = 9] = "UnknownError";
})(SchemaValidationErrors || (exports.SchemaValidationErrors = SchemaValidationErrors = {}));
/**
 * Deeply checks that the provided node complies with the schema based on its identifier.
 */
function isNodeInSchema(node, schemaAndPolicy) {
    // If the stored schema is completely empty it _probably_ (in almost all cases?) means the tree is brand new and we
    // shouldn't validate the data.
    // TODO: AB#8197
    // See https://github.com/microsoft/FluidFramework/pull/21305#discussion_r1626595991 for further discussion.
    if (schemaAndPolicy.schema.nodeSchema.size === 0) {
        return 0 /* SchemaValidationErrors.NoError */;
    }
    // Validate the schema declared by the node exists
    const schema = schemaAndPolicy.schema.nodeSchema.get(node.type);
    if (schema === undefined) {
        return 8 /* SchemaValidationErrors.Node_MissingSchema */;
    }
    // Validate the node is well formed according to its schema
    if (schema instanceof index_js_1.LeafNodeStoredSchema) {
        if (node.fields.size !== 0) {
            return 5 /* SchemaValidationErrors.LeafNode_FieldsNotAllowed */;
        }
        if (!(0, valueUtilities_js_1.allowsValue)(schema.leafValue, node.value)) {
            return 4 /* SchemaValidationErrors.LeafNode_InvalidValue */;
        }
    }
    else if (schema instanceof index_js_1.ObjectNodeStoredSchema) {
        if (node.value !== undefined) {
            return 7 /* SchemaValidationErrors.NonLeafNode_ValueNotAllowed */;
        }
        const uncheckedFieldsFromNode = new Set(node.fields.keys());
        for (const [fieldKey, fieldSchema] of schema.objectNodeFields) {
            const nodeField = node.fields.get(fieldKey) ?? [];
            const fieldInSchemaResult = isFieldInSchema(nodeField, fieldSchema, schemaAndPolicy);
            if (fieldInSchemaResult !== 0 /* SchemaValidationErrors.NoError */) {
                return fieldInSchemaResult;
            }
            uncheckedFieldsFromNode.delete(fieldKey);
        }
        // The node has fields that we did not check as part of looking at every field defined in the node's schema
        if (uncheckedFieldsFromNode.size !== 0 &&
            !schemaAndPolicy.policy.allowUnknownOptionalFields(node.type)) {
            return 6 /* SchemaValidationErrors.ObjectNode_FieldNotInSchema */;
        }
    }
    else if (schema instanceof index_js_1.MapNodeStoredSchema) {
        if (node.value !== undefined) {
            return 7 /* SchemaValidationErrors.NonLeafNode_ValueNotAllowed */;
        }
        for (const field of node.fields.values()) {
            const fieldInSchemaResult = isFieldInSchema(field, schema.mapFields, schemaAndPolicy);
            if (fieldInSchemaResult !== 0 /* SchemaValidationErrors.NoError */) {
                return fieldInSchemaResult;
            }
        }
    }
    else {
        (0, index_js_2.fail)("Unknown TreeNodeStoredSchema type");
    }
    return 0 /* SchemaValidationErrors.NoError */;
}
exports.isNodeInSchema = isNodeInSchema;
/**
 * Deeply checks that the nodes comply with the field schema and included schema.
 */
function isFieldInSchema(childNodes, schema, schemaAndPolicy) {
    // Validate that the field kind is handled by the schema policy
    const kind = schemaAndPolicy.policy.fieldKinds.get(schema.kind);
    if (kind === undefined) {
        return 1 /* SchemaValidationErrors.Field_KindNotInSchemaPolicy */;
    }
    // Validate that the field doesn't contain more nodes than its type supports
    if (!compliesWithMultiplicity(childNodes.length, kind.multiplicity)) {
        return 2 /* SchemaValidationErrors.Field_IncorrectMultiplicity */;
    }
    for (const node of childNodes) {
        // Validate the type declared by the node is allowed in this field
        if (schema.types !== undefined && !schema.types.has(node.type)) {
            return 3 /* SchemaValidationErrors.Field_NodeTypeNotAllowed */;
        }
        // Validate the node complies with the type it declares to be.
        const nodeInSchemaResult = isNodeInSchema(node, schemaAndPolicy);
        if (nodeInSchemaResult !== 0 /* SchemaValidationErrors.NoError */) {
            return nodeInSchemaResult;
        }
    }
    return 0 /* SchemaValidationErrors.NoError */;
}
exports.isFieldInSchema = isFieldInSchema;
/**
 * Validates that a given number of items complies with the specified {@link Multiplicity | multiplicity}.
 * @param numberOfItems - Number of items.
 * @param multiplicity - Kind of multiplicity to validate against.
 * @returns `true` if the specified number of items complies with the specified multiplicity; otherwise, `false`.
 */
function compliesWithMultiplicity(numberOfItems, multiplicity) {
    switch (multiplicity) {
        case index_js_1.Multiplicity.Single:
            return numberOfItems === 1;
        case index_js_1.Multiplicity.Optional:
            return numberOfItems <= 1;
        case index_js_1.Multiplicity.Sequence:
            return true;
        case index_js_1.Multiplicity.Forbidden:
            return numberOfItems === 0;
        default:
            (0, internal_1.unreachableCase)(multiplicity);
    }
}
exports.compliesWithMultiplicity = compliesWithMultiplicity;
//# sourceMappingURL=schemaChecker.js.map