"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.findExtraAllowedTypes = exports.getDiscrepanciesInAllowedContent = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../../util/index.js");
const index_js_3 = require("../core/index.js");
const index_js_4 = require("../node-kinds/index.js");
const toStoredSchema_js_1 = require("../toStoredSchema.js");
const fieldSchema_js_1 = require("../fieldSchema.js");
const leafNodeSchema_js_1 = require("../leafNodeSchema.js");
const customTree_js_1 = require("./customTree.js");
const index_js_5 = require("../../feature-libraries/index.js");
function getStoredNodeSchemaType(nodeSchema) {
    if (nodeSchema instanceof index_js_1.ObjectNodeStoredSchema) {
        return index_js_1.ObjectNodeStoredSchema;
    }
    if (nodeSchema instanceof index_js_1.MapNodeStoredSchema) {
        return index_js_1.MapNodeStoredSchema;
    }
    if (nodeSchema instanceof index_js_1.LeafNodeStoredSchema) {
        return index_js_1.LeafNodeStoredSchema;
    }
    (0, internal_1.fail)(0xbe8 /* Invalid stored node schema type */);
}
function doesNodeKindMatchStoredNodeKind(viewKind, storedType) {
    switch (viewKind) {
        case index_js_3.NodeKind.Leaf:
            return storedType === index_js_1.LeafNodeStoredSchema;
        case index_js_3.NodeKind.Array:
        case index_js_3.NodeKind.Object:
            return storedType === index_js_1.ObjectNodeStoredSchema;
        case index_js_3.NodeKind.Map:
        case index_js_3.NodeKind.Record:
            return storedType === index_js_1.MapNodeStoredSchema;
        default:
            (0, internal_1.unreachableCase)(viewKind);
    }
}
/**
 * Finds and reports discrepancies between a view schema and a stored schema which make "canView" false.
 * @remarks
 * See documentation on {@link Discrepancy} and its subtypes for details of possible discrepancies.
 */
function* getDiscrepanciesInAllowedContent(view, stored) {
    // check root field discrepancies
    yield* getFieldDiscrepancies(view.root, stored.rootFieldSchema, undefined, undefined);
    // Check all of the stored nodes, including their fields for discrepancies.
    for (const [identifier, storedSchema] of stored.nodeSchema) {
        const viewSchema = view.definitions.get(identifier);
        // if the view schema has a node that's also in the stored schema, check it.
        if (viewSchema !== undefined) {
            yield* getNodeDiscrepancies(identifier, viewSchema, storedSchema);
        }
        // Note that nodes that are missing in the view schema are only a problem if other stored schema nodes actually reference them which will produce its own discrepancy, so we can rely on that to produce any needed discrepancies.
    }
}
exports.getDiscrepanciesInAllowedContent = getDiscrepanciesInAllowedContent;
function* getNodeDiscrepancies(identifier, view, stored) {
    if (!doesNodeKindMatchStoredNodeKind(view.kind, getStoredNodeSchemaType(stored))) {
        yield {
            identifier,
            mismatch: "nodeKind",
            view: view.kind,
            stored: getStoredNodeSchemaType(stored),
        };
        return;
    }
    switch (view.kind) {
        case index_js_3.NodeKind.Object: {
            (0, internal_1.assert)((0, index_js_4.isObjectNodeSchema)(view), 0xbe9 /* schema with node kind of object must implement ObjectNodeSchema */);
            yield* computeObjectNodeDiscrepancies(identifier, view, stored);
            break;
        }
        case index_js_3.NodeKind.Array: {
            (0, internal_1.assert)((0, index_js_4.isArrayNodeSchema)(view), 0xbea /* schema with node kind of array must implement ArrayNodeSchema */);
            const arrayStoredSchema = (0, customTree_js_1.tryStoredSchemaAsArray)(stored);
            if (arrayStoredSchema === undefined) {
                yield {
                    identifier,
                    mismatch: "nodeKind",
                    view: index_js_3.NodeKind.Array,
                    stored: getStoredNodeSchemaType(stored),
                };
                return;
            }
            yield* getAllowedTypeDiscrepancies((0, index_js_3.normalizeAnnotatedAllowedTypes)(view.info).types, arrayStoredSchema, (0, index_js_2.brand)(view.identifier), index_js_1.EmptyKey);
            break;
        }
        case index_js_3.NodeKind.Map: {
            (0, internal_1.assert)((0, index_js_4.isMapNodeSchema)(view), 0xbeb /* schema with node kind of map must implement MapNodeSchema */);
            yield* getFieldDiscrepancies((0, fieldSchema_js_1.createFieldSchema)(fieldSchema_js_1.FieldKind.Optional, view.info), stored.mapFields, identifier, undefined);
            break;
        }
        case index_js_3.NodeKind.Record: {
            (0, internal_1.assert)((0, index_js_4.isRecordNodeSchema)(view), 0xbec /* schema with node kind of record must implement RecordNodeSchema */);
            yield* getFieldDiscrepancies((0, fieldSchema_js_1.createFieldSchema)(fieldSchema_js_1.FieldKind.Optional, view.info), stored.mapFields, identifier, undefined);
            break;
        }
        case index_js_3.NodeKind.Leaf: {
            (0, internal_1.assert)(view instanceof leafNodeSchema_js_1.LeafNodeSchema, 0xbed /* schema with node kind of leaf must implement LeafNodeSchema */);
            // TODO: leafKind seems like a bad name
            const viewValue = view.leafKind;
            const storedValue = stored.leafValue;
            if (viewValue !== storedValue) {
                yield {
                    identifier,
                    mismatch: "valueSchema",
                    view: viewValue,
                    stored: storedValue,
                };
            }
            break;
        }
        default:
            break;
    }
}
/**
 * Returns the allowed types that are not present in both the given view and stored schemas.
 */
function findExtraAllowedTypes(viewAllowedTypes, storedAllowedTypes) {
    const viewNodeSchemaIdentifiers = new Set(viewAllowedTypes.map((value) => value.type.identifier));
    // TODO:
    // Add detecting which SchemaUpgrades have been made.
    // When doing so, each will fall into one of three states: staged, partially applied, or fully applied.
    // This would allow for nicer compatibility errors and more detailed compatibility status in the future.
    const view = viewAllowedTypes.filter((value) => !storedAllowedTypes.has((0, index_js_2.brand)(value.type.identifier)) &&
        value.metadata.stagedSchemaUpgrade === undefined);
    const stored = [...storedAllowedTypes].filter((value) => !viewNodeSchemaIdentifiers.has(value));
    return { view, stored };
}
exports.findExtraAllowedTypes = findExtraAllowedTypes;
/**
 * The function to track the discrepancies between a field view schema and a stored schema.
 *
 * @remarks
 * This function yields discrepancies in the following cases:
 * 1. If the view schema has allowed types that are not present in the stored schema.
 * 2. If the stored schema has allowed types that are not present in the view schema.
 * 3. If the field kind in the view schema is not compatible with the stored schema.
 *
 * This function does not recurse into the nodes of the view schema and only makes comparisons at the field level.
 *
 * @param fieldKey - If the key is missing, it indicates that this is the root field schema.
 */
function* getFieldDiscrepancies(view, stored, identifier, fieldKey) {
    (0, internal_1.assert)(view instanceof fieldSchema_js_1.FieldSchemaAlpha, 0xbee /* all field schema should be FieldSchemaAlpha */);
    yield* getAllowedTypeDiscrepancies(view.annotatedAllowedTypesNormalized.types, stored.types, identifier, fieldKey);
    const viewKind = toStoredSchema_js_1.convertFieldKind.get(view.kind) ??
        (0, internal_1.fail)(0xbef /* A conversion from a FieldKind to a FlexFieldKind should exist */);
    // This checks if the field kind in the view schema is not compatible with the stored schema.
    if (viewKind.identifier !== stored.kind) {
        yield {
            identifier,
            fieldKey,
            mismatch: "fieldKind",
            view: viewKind.identifier,
            stored: stored.kind,
        };
    }
}
/**
 * The function to track the discrepancies between a field view schema and a stored schema.
 *
 * @remarks
 * This function yields discrepancies in the following cases:
 * 1. If the view schema has allowed types that are not present in the stored schema.
 * 2. If the stored schema has allowed types that are not present in the view schema.
 *
 * This function does not recurse into the nodes of the view schema and only makes comparisons at the field level.
 *
 * @param fieldKey - If the key is missing, it indicates that this is the root field schema.
 */
function* getAllowedTypeDiscrepancies(view, stored, identifier, fieldKey) {
    const discrepancies = findExtraAllowedTypes(view, stored);
    if (discrepancies.view.length > 0 || discrepancies.stored.length > 0) {
        yield {
            ...discrepancies,
            identifier,
            fieldKey,
            mismatch: "allowedTypes",
        };
    }
}
/**
 * Computes discrepancies between a view schema and a stored schema for nodes that are treated as object nodes in the stored schema.
 * This includes both view object nodes and view array nodes.
 *
 * This function yields discrepancies in the following cases:
 *
 * 1. If the view schema has fields that are not present in the stored schema.
 * 2. If the stored schema has fields that are not present in the view schema.
 * 3. If the field kind or allowed types of a field in the view schema is not compatible with the stored schema.
 *
 * This function includes discrepancies within the common fields and their allowed types, but does NOT recurse to report and discrepancies within the node types referenced by those fields.
 */
function* computeObjectNodeDiscrepancies(identifier, view, stored) {
    /**
     * Similar to the logic used for tracking discrepancies between two node schemas, we will identify
     * three types of differences:
     * 1. Fields that exist in the view schema but not in the stored schema.
     * 2. Fields that exist in both schemas but have different contents.
     * 3. Fields that exist in the stored schema but not in the view schema.
     *
     * First, the view schema is iterated to track the first two types of differences.
     * Then, the stored schema is iterated to find the third type.
     */
    const viewKeys = new Set();
    for (const [_, { storedKey: fieldKey, schema: fieldSchema }] of view.flexKeyMap) {
        const storedSchema = stored.objectNodeFields.get(fieldKey);
        viewKeys.add(fieldKey);
        // If the view schema has a field that's not in the stored schema
        if (storedSchema === undefined) {
            const viewKind = toStoredSchema_js_1.convertFieldKind.get(fieldSchema.kind) ??
                (0, internal_1.fail)(0xbf0 /* A conversion from a FieldKind to a FlexFieldKind should exist */);
            yield {
                identifier,
                fieldKey,
                mismatch: "fieldKind",
                view: viewKind.identifier,
                stored: index_js_1.storedEmptyFieldSchema.kind,
            };
        }
        else {
            yield* getFieldDiscrepancies(fieldSchema, storedSchema, identifier, fieldKey);
        }
    }
    for (const [fieldKey, schema] of stored.objectNodeFields) {
        if (schema.kind === index_js_1.forbiddenFieldKindIdentifier) {
            // In the stored schema the field is explicitly forbidden.
            // This has the same semantics of the field not being mentioned in the stored schema,
            // and thus can be skipped.
            continue;
        }
        // If the stored schema has a field that's not in the view schema
        if (!viewKeys.has(fieldKey)) {
            // When the application has opted into it, we allow viewing documents which have additional
            // optional fields in the stored schema that are not present in the view schema.
            if (!view.allowUnknownOptionalFields || schema.kind !== index_js_5.FieldKinds.optional.identifier) {
                yield {
                    identifier,
                    fieldKey,
                    mismatch: "fieldKind",
                    view: index_js_1.storedEmptyFieldSchema.kind,
                    stored: schema.kind,
                };
            }
        }
    }
}
//# sourceMappingURL=discrepancies.js.map