"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAllowedContentIncompatibilities = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
/**
 * @remarks
 *
 * The workflow for finding schema incompatibilities:
 * 1. Compare the two root schemas to identify any `FieldIncompatibility`.
 *
 * 2. For each node schema in the `view`:
 * - Verify if the node schema exists in the stored. If it does, ensure that the `SchemaFactoryNodeKind` are
 * consistent. Otherwise this difference is treated as `NodeKindIncompatibility`
 * - If a node schema with the same identifier exists in both view and stored, and their `SchemaFactoryNodeKind`
 * are consistent, perform a exhaustive validation to identify all `FieldIncompatibility`.
 *
 * 3. For each node schema in the stored, verify if it exists in the view. The overlapping parts were already
 * addressed in the previous step.
 *
 * @returns the discrepancies between two TreeStoredSchema objects
 */
function getAllowedContentIncompatibilities(view, stored) {
    const incompatibilities = [];
    // check root schema discrepancies
    incompatibilities.push(...trackFieldDiscrepancies(view.rootFieldSchema, stored.rootFieldSchema));
    // Verify the existence and type of a node schema given its identifier (key), then determine if
    // an exhaustive search is necessary.
    const viewNodeKeys = new Set();
    for (const [key, viewNodeSchema] of view.nodeSchema) {
        viewNodeKeys.add(key);
        if (viewNodeSchema instanceof index_js_1.ObjectNodeStoredSchema) {
            if (!stored.nodeSchema.has(key)) {
                incompatibilities.push({
                    identifier: key,
                    mismatch: "nodeKind",
                    view: "object",
                    stored: undefined,
                });
            }
            else {
                const storedNodeSchema = stored.nodeSchema.get(key);
                (0, internal_1.assert)(storedNodeSchema !== undefined, 0x9be /* The storedNodeSchema in stored.nodeSchema should not be undefined */);
                if (storedNodeSchema instanceof index_js_1.MapNodeStoredSchema) {
                    incompatibilities.push({
                        identifier: key,
                        mismatch: "nodeKind",
                        view: "object",
                        stored: "map",
                    });
                }
                else if (storedNodeSchema instanceof index_js_1.LeafNodeStoredSchema) {
                    incompatibilities.push({
                        identifier: key,
                        mismatch: "nodeKind",
                        view: "object",
                        stored: "leaf",
                    });
                }
                else if (storedNodeSchema instanceof index_js_1.ObjectNodeStoredSchema) {
                    const differences = trackObjectNodeDiscrepancies(viewNodeSchema, storedNodeSchema);
                    if (differences.length > 0) {
                        incompatibilities.push({
                            identifier: key,
                            mismatch: "fields",
                            differences,
                        });
                    }
                }
                else {
                    throwUnsupportedNodeType(storedNodeSchema.constructor.name);
                }
            }
        }
        else if (viewNodeSchema instanceof index_js_1.MapNodeStoredSchema) {
            if (!stored.nodeSchema.has(key)) {
                incompatibilities.push({
                    identifier: key,
                    mismatch: "nodeKind",
                    view: "map",
                    stored: undefined,
                });
            }
            else {
                const storedNodeSchema = stored.nodeSchema.get(key);
                (0, internal_1.assert)(storedNodeSchema !== undefined, 0x9bf /* The storedNodeSchema in stored.nodeSchema should not be undefined */);
                if (storedNodeSchema instanceof index_js_1.ObjectNodeStoredSchema) {
                    incompatibilities.push({
                        identifier: key,
                        mismatch: "nodeKind",
                        view: "map",
                        stored: "object",
                    });
                }
                else if (storedNodeSchema instanceof index_js_1.LeafNodeStoredSchema) {
                    incompatibilities.push({
                        identifier: key,
                        mismatch: "nodeKind",
                        view: "map",
                        stored: "leaf",
                    });
                }
                else if (storedNodeSchema instanceof index_js_1.MapNodeStoredSchema) {
                    incompatibilities.push(...trackFieldDiscrepancies(viewNodeSchema.mapFields, storedNodeSchema.mapFields, key));
                }
                else {
                    throwUnsupportedNodeType(storedNodeSchema.constructor.name);
                }
            }
        }
        else if (viewNodeSchema instanceof index_js_1.LeafNodeStoredSchema) {
            if (!stored.nodeSchema.has(key)) {
                incompatibilities.push({
                    identifier: key,
                    mismatch: "nodeKind",
                    view: "leaf",
                    stored: undefined,
                });
            }
            else {
                const storedNodeSchema = stored.nodeSchema.get(key);
                (0, internal_1.assert)(storedNodeSchema !== undefined, 0x9c0 /* The storedNodeSchema in stored.nodeSchema should not be undefined */);
                if (storedNodeSchema instanceof index_js_1.MapNodeStoredSchema) {
                    incompatibilities.push({
                        identifier: key,
                        mismatch: "nodeKind",
                        view: "leaf",
                        stored: "map",
                    });
                }
                else if (storedNodeSchema instanceof index_js_1.ObjectNodeStoredSchema) {
                    incompatibilities.push({
                        identifier: key,
                        mismatch: "nodeKind",
                        view: "leaf",
                        stored: "object",
                    });
                }
                else if (storedNodeSchema instanceof index_js_1.LeafNodeStoredSchema) {
                    if (viewNodeSchema.leafValue !== storedNodeSchema.leafValue) {
                        incompatibilities.push({
                            identifier: key,
                            mismatch: "valueSchema",
                            view: viewNodeSchema.leafValue,
                            stored: storedNodeSchema.leafValue,
                        });
                    }
                }
                else {
                    throwUnsupportedNodeType(storedNodeSchema.constructor.name);
                }
            }
        }
        else {
            throwUnsupportedNodeType(viewNodeSchema.constructor.name);
        }
    }
    for (const [key, storedNodeSchema] of stored.nodeSchema) {
        if (!viewNodeKeys.has(key)) {
            incompatibilities.push({
                identifier: key,
                mismatch: "nodeKind",
                view: undefined,
                stored: storedNodeSchema instanceof index_js_1.MapNodeStoredSchema
                    ? "map"
                    : storedNodeSchema instanceof index_js_1.ObjectNodeStoredSchema
                        ? "object"
                        : "leaf",
            });
        }
    }
    return incompatibilities;
}
exports.getAllowedContentIncompatibilities = getAllowedContentIncompatibilities;
/**
 * The function to track the discrepancies between two field stored schemas.
 *
 * @param keyOrRoot - If the key is missing, it indicates that this is the root field schema.
 */
function trackFieldDiscrepancies(view, stored, keyOrRoot) {
    const differences = [];
    // Only track the intersection of the two sets.
    const findSetDiscrepancies = (a, b) => {
        if (a === undefined && b === undefined) {
            return [[], []];
        }
        if (a !== undefined && b !== undefined) {
            const aDiff = [...a].filter((value) => !b.has(value));
            const bDiff = [...b].filter((value) => !a.has(value));
            return [aDiff, bDiff];
        }
        if (a !== undefined) {
            return [[...a], []];
        }
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        return [[], [...b]];
    };
    const allowedTypesDiscrepancies = findSetDiscrepancies(view.types, stored.types);
    if (allowedTypesDiscrepancies[0].length > 0 || allowedTypesDiscrepancies[1].length > 0) {
        differences.push({
            identifier: keyOrRoot,
            mismatch: "allowedTypes",
            view: allowedTypesDiscrepancies[0],
            stored: allowedTypesDiscrepancies[1],
        });
    }
    if (view.kind !== stored.kind) {
        differences.push({
            identifier: keyOrRoot,
            mismatch: "fieldKind",
            view: view.kind,
            stored: stored.kind,
        });
    }
    return differences;
}
function trackObjectNodeDiscrepancies(view, stored) {
    const differences = [];
    const viewFieldKeys = new Set();
    /**
     * 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.
     */
    for (const [fieldKey, fieldStoredSchema] of view.objectNodeFields) {
        viewFieldKeys.add(fieldKey);
        if (!stored.objectNodeFields.has(fieldKey)) {
            differences.push({
                identifier: fieldKey,
                mismatch: "fieldKind",
                view: fieldStoredSchema.kind,
                stored: undefined,
            });
        }
        else {
            differences.push(...trackFieldDiscrepancies(view.objectNodeFields.get(fieldKey), stored.objectNodeFields.get(fieldKey), fieldKey));
        }
    }
    for (const [fieldKey, fieldStoredSchema] of stored.objectNodeFields) {
        if (viewFieldKeys.has(fieldKey)) {
            continue;
        }
        differences.push({
            identifier: fieldKey,
            mismatch: "fieldKind",
            view: undefined,
            stored: fieldStoredSchema.kind,
        });
    }
    return differences;
}
function throwUnsupportedNodeType(type) {
    throw new TypeError(`Unsupported node stored schema type: ${type}`);
}
//# sourceMappingURL=discrepancies.js.map