"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateField = exports.validateGlobalField = exports.validateViewSchemaCollection = exports.buildViewSchemaCollection = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const util_1 = require("../../../util");
const defaultSchema_1 = require("../../defaultSchema");
const defaultFieldKinds_1 = require("../../defaultFieldKinds");
const typedTreeSchema_1 = require("./typedTreeSchema");
const flexList_1 = require("./flexList");
// TODO: tests for this file
/**
 * Build and validate a SchemaCollection.
 *
 * As much as possible tries to detect anything that might be a mistake made by the schema author.
 * This will error on some valid but probably never intended to be used patterns
 * (like libraries with the same name, nodes which are impossible to create etc).
 */
function buildViewSchemaCollection(libraries) {
    var _a, _b;
    const globalFieldSchema = new Map();
    const treeSchema = new Map();
    const adapters = { tree: [], fieldAdapters: new Map() };
    const errors = [];
    const librarySet = new Set();
    const libraryNames = new Set();
    for (const library of libraries) {
        if (librarySet.has(library)) {
            // SchemaBuilder should ensure this doesn't happen, but include it here for completeness.
            errors.push(`Duplicate library named "${library.name}"`);
            continue;
        }
        librarySet.add(library);
        if (libraryNames.has(library.name)) {
            // This wouldn't break anything, but could make error messages confusing, so its better to avoid duplicates.
            errors.push(`Found another library with name "${library.name}"`);
        }
        for (const [key, field] of library.globalFieldSchema) {
            // This check is an assert since if it fails, the other error messages would be incorrect.
            (0, common_utils_1.assert)(field.builder.name === library.name, 0x6a8 /* field must be part by the library its in */);
            const existing = globalFieldSchema.get(key);
            if (existing !== undefined) {
                errors.push(`Multiple global field schema for key "${key}". One from library "${existing.builder.name}" and one from "${field.builder.name}"`);
            }
            else {
                globalFieldSchema.set(key, field);
            }
        }
        for (const [key, tree] of library.treeSchema) {
            // This check is an assert since if it fails, the other error messages would be incorrect.
            (0, common_utils_1.assert)(tree.builder.name === library.name, 0x6a9 /* tree must be part by the library its in */);
            const existing = treeSchema.get(key);
            if (existing !== undefined) {
                errors.push(`Multiple tree schema for identifier "${key}". One from library "${existing.builder.name}" and one from "${tree.builder.name}"`);
            }
            else {
                treeSchema.set(key, tree);
            }
        }
        for (const [_key, _adapter] of (_a = library.adapters.fieldAdapters) !== null && _a !== void 0 ? _a : []) {
            (0, util_1.fail)("Adapters not yet supported");
        }
        for (const _adapter of (_b = library.adapters.tree) !== null && _b !== void 0 ? _b : []) {
            (0, util_1.fail)("Adapters not yet supported");
        }
    }
    if (errors.length !== 0) {
        (0, util_1.fail)(errors.join("\n"));
    }
    const result = { globalFieldSchema, treeSchema, adapters, policy: defaultSchema_1.defaultSchemaPolicy };
    const errors2 = validateViewSchemaCollection(result);
    if (errors2.length !== 0) {
        (0, util_1.fail)(errors2.join("\n"));
    }
    return result;
}
exports.buildViewSchemaCollection = buildViewSchemaCollection;
/**
 * Returns an array of descriptions of errors in the collection.
 *
 * As much as possible tries to detect anything that might be a mistake made by the schema author.
 * This will error on some valid but probably never intended to be used patterns (like never nodes).
 */
function validateViewSchemaCollection(collection) {
    const errors = [];
    // TODO: make this check specific to document schema. Replace check here for no tre or field schema (empty library).
    if (collection.treeSchema.size === 0) {
        errors.push("No tree schema are included, meaning no data can possibly be stored.");
    }
    if (collection.policy !== defaultSchema_1.defaultSchemaPolicy) {
        errors.push("Unexpected policy.");
    }
    // Validate that all schema referenced are included, and none are "never".
    for (const [key, field] of collection.globalFieldSchema) {
        (0, common_utils_1.assert)(key === field.key, 0x6aa /* field key should match map key */);
        validateGlobalField(collection, field, errors);
    }
    for (const [identifier, tree] of collection.treeSchema) {
        for (const [key, field] of tree.localFields) {
            validateField(collection, field, () => `Local field "${key}" of "${identifier}" schema from library "${tree.builder.name}"`, errors);
        }
        if (tree.extraLocalFields !== typedTreeSchema_1.FieldSchema.empty) {
            validateField(collection, tree.extraLocalFields, () => `Extra local fields of "${identifier}" schema from library "${tree.builder.name}"`, errors);
            if (tree.extraLocalFields.kind === defaultFieldKinds_1.value) {
                errors.push(`Extra local fields of "${identifier}" schema from library "${tree.builder.name}" has kind "value". This is invalid since it requires all possible local field keys to have a value under them.`);
            }
        }
        for (const key of tree.globalFields) {
            if (!collection.globalFieldSchema.has(key)) {
                errors.push(`Tree schema "${identifier}" from library "${tree.builder.name}" references undefined global field "${key}".`);
            }
        }
    }
    // TODO: validate adapters
    return errors;
}
exports.validateViewSchemaCollection = validateViewSchemaCollection;
function validateGlobalField(collection, field, errors) {
    const describeField = () => `Global field schema "${field.key}" from library "${field.builder.name}"`;
    validateField(collection, field.schema, describeField, errors);
}
exports.validateGlobalField = validateGlobalField;
function validateField(collection, field, describeField, errors) {
    const types = field.allowedTypes;
    if (!(0, typedTreeSchema_1.allowedTypesIsAny)(types)) {
        const normalizedTypes = (0, flexList_1.normalizeFlexListEager)(types);
        for (const type of normalizedTypes) {
            const referenced = collection.treeSchema.get(type.name);
            if (referenced === undefined) {
                errors.push(`${describeField()} references type "${type.name}" from library "${type.builder.name}" which is not defined. Perhaps another type was intended, or that library needs to be added.`);
            }
        }
        if (types.length === 0) {
            errors.push(`${describeField()} requires children to have a type from a set of zero types. This means the field must always be empty.`);
        }
    }
    const kind = field.kind;
    const kindFromPolicy = defaultSchema_1.defaultSchemaPolicy.fieldKinds.get(kind.identifier);
    if (kindFromPolicy === undefined) {
        errors.push(`"${describeField()}" has unknown field kind "${kind.identifier}".`);
    }
    else if (kindFromPolicy !== kind) {
        errors.push(`${describeField()} has field kind "${kind.identifier}" which isn't a reference to the default kind with that identifier.`);
    }
    else if (kind === defaultFieldKinds_1.forbidden) {
        errors.push(`${describeField()} explicitly uses "forbidden" kind, which is not recommended.`);
    } // else if (kind !== counter) {
    // 	errors.push(
    // 		`${describeField()} explicitly uses "counter" kind, which is finished.`,
    // 	);
    // }
}
exports.validateField = validateField;
//# sourceMappingURL=buildViewSchemaCollection.js.map