"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildTreeConfiguration = exports.afterSchemaChanges = exports.schematize = exports.initializeContent = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const core_1 = require("../core");
const feature_libraries_1 = require("../feature-libraries");
const util_1 = require("../util");
/**
 * Modify `storedSchema` and invoke `setInitialTree` when it's time to set the tree content.
 *
 * Requires `storedSchema` to be in its default/empty state.
 *
 * This is done in such a way that if the content (implicitly assumed to start empty)
 * is never out of schema.
 * This means that if the root field of the new schema requires content (like a value field),
 * a temporary intermediate schema is used so the initial empty state is not out of schema.
 *
 * Since this makes multiple changes, callers may want to wrap it in a transaction.
 */
function initializeContent(schemaRepository, newSchema, setInitialTree) {
    (0, core_utils_1.assert)((0, core_1.schemaDataIsEmpty)(schemaRepository.storedSchema), 0x743 /* cannot initialize after a schema is set */);
    const schema = (0, feature_libraries_1.intoStoredSchema)(newSchema);
    const rootSchema = schema.rootFieldSchema;
    const rootKind = rootSchema.kind.identifier;
    // To keep the data in schema during the update, first define a schema that tolerates the current (empty) tree as well as the final (initial) tree.
    let incrementalSchemaUpdate;
    if (rootKind === feature_libraries_1.FieldKinds.sequence.identifier ||
        rootKind === feature_libraries_1.FieldKinds.optional.identifier) {
        // These kinds are known to tolerate empty, so use the schema as is:
        incrementalSchemaUpdate = schema;
    }
    else {
        (0, core_utils_1.assert)(rootKind === feature_libraries_1.FieldKinds.required.identifier, 0x5c8 /* Unexpected kind */);
        // Replace value kind with optional kind in root field schema:
        incrementalSchemaUpdate = {
            nodeSchema: schema.nodeSchema,
            rootFieldSchema: {
                kind: feature_libraries_1.FieldKinds.optional,
                types: rootSchema.types,
            },
        };
    }
    // TODO: fix issues with schema comparison and enable this.
    // assert(
    // 	allowsRepoSuperset(defaultSchemaPolicy, tree.storedSchema, incrementalSchemaUpdate),
    // 	"Incremental Schema update should support the existing empty tree",
    // );
    (0, core_utils_1.assert)((0, feature_libraries_1.allowsRepoSuperset)(feature_libraries_1.defaultSchemaPolicy, schema, incrementalSchemaUpdate), 0x5c9 /* Incremental Schema during update should be a allow a superset of the final schema */);
    // Update to intermediate schema
    schemaRepository.updateSchema(incrementalSchemaUpdate);
    // Insert initial tree
    setInitialTree();
    // If intermediate schema is not final desired schema, update to the final schema:
    if (incrementalSchemaUpdate !== schema) {
        schemaRepository.updateSchema(schema);
    }
}
exports.initializeContent = initializeContent;
/**
 * See {@link ISharedTree.schematize} for more details.
 *
 * TODO:
 * - Support adapters for handling out of schema data.
 * - Handle initialization via an adapter.
 * - Support per adapter update policy.
 * - Support lazy schema updates.
 * - Better error for change to invalid schema approach than throwing on later event.
 */
function schematize(events, schemaRepository, config) {
    // TODO: support adapters and include them here.
    const viewSchema = new feature_libraries_1.ViewSchema(feature_libraries_1.defaultSchemaPolicy, {}, config.schema);
    {
        const compatibility = viewSchema.checkCompatibility(schemaRepository.storedSchema);
        switch (config.allowedSchemaModifications) {
            case core_1.AllowedUpdateType.None: {
                if (compatibility.read !== core_1.Compatibility.Compatible) {
                    (0, util_1.fail)("Existing stored schema permits trees which are incompatible with the view schema");
                }
                if (compatibility.write !== core_1.Compatibility.Compatible) {
                    // TODO: support readonly mode in this case.
                    (0, util_1.fail)("View schema permits trees which are incompatible with the stored schema");
                }
                break;
            }
            case core_1.AllowedUpdateType.SchemaCompatible: {
                if (compatibility.read !== core_1.Compatibility.Compatible) {
                    (0, util_1.fail)("Existing stored schema permits trees which are incompatible with the view schema, so schema can not be updated");
                }
                if (compatibility.write !== core_1.Compatibility.Compatible) {
                    schemaRepository.updateSchema((0, feature_libraries_1.intoStoredSchema)(config.schema));
                }
                break;
            }
            default: {
                (0, core_utils_1.unreachableCase)(config.allowedSchemaModifications);
            }
        }
    }
    afterSchemaChanges(events, schemaRepository, () => {
        const compatibility = viewSchema.checkCompatibility(schemaRepository.storedSchema);
        if (compatibility.read !== core_1.Compatibility.Compatible) {
            (0, util_1.fail)("Stored schema changed to one that permits data incompatible with the view schema");
        }
        if (compatibility.write !== core_1.Compatibility.Compatible) {
            // TODO: support readonly mode in this case.
            (0, util_1.fail)("Stored schema changed to one that does not support all data allowed by view schema");
        }
        return true;
    });
}
exports.schematize = schematize;
/**
 * @param callback - run after a schema change. If returns true, will run after next change as well.
 */
function afterSchemaChanges(events, schemaRepository, callback) {
    // Callback to cleanup afterBatch schema checking.
    // Set only when such a callback is pending.
    let afterBatchCheck;
    // TODO: errors thrown by this will usually be in response to remote edits, and thus may not surface to the app.
    // Two fixes should be done related to this:
    // 1. Ensure errors in response to edits like this crash app and report telemetry.
    // 2. Replace these (and the above) exception based errors with
    // out of schema handlers which update the schematized view of the tree instead of throwing.
    const unregister = schemaRepository.storedSchema.on("afterSchemaChange", () => {
        // On schema change, setup a callback (deduplicated so its only run once) after a batch of changes.
        // This avoids erroring about invalid schema in the middle of a batch of changes.
        // TODO:
        // Ideally this would run at the end of the batch containing the schema change, but currently schema changes don't trigger afterBatch.
        // Fortunately this works out ok, since the tree can't actually become out of schema until its actually edited, which should trigger after batch.
        // When batching properly handles schema edits, this documentation and related tests should be updated.
        // TODO:
        // With the the updated incremental batch application policy, this behavior is not valid.
        afterBatchCheck ?? (afterBatchCheck = events.on("afterBatch", () => {
            (0, core_utils_1.assert)(afterBatchCheck !== undefined, 0x728 /* unregistered event ran */);
            afterBatchCheck();
            afterBatchCheck = undefined;
            if (!callback()) {
                unregister();
            }
        }));
    });
}
exports.afterSchemaChanges = afterSchemaChanges;
/**
 * Options used to initialize (if needed) and schematize a `SharedTree`.
 * @remarks
 * Using this builder improves type safety and error quality over just constructing the configuration as a object.
 * @internal
 */
function buildTreeConfiguration(config) {
    return config;
}
exports.buildTreeConfiguration = buildTreeConfiguration;
//# sourceMappingURL=schematizedTree.js.map