/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, unreachableCase } from "@fluidframework/common-utils";
import { schemaDataIsEmpty, AllowedUpdateType, Compatibility, SimpleObservingDependent, } from "../core";
import { ViewSchema, defaultSchemaPolicy, FieldKinds, allowsRepoSuperset, } from "../feature-libraries";
import { fail } from "../util";
/**
 * See {@link ISharedTreeView.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.
 */
export function schematizeView(tree, config) {
    // Check for empty.
    // When this becomes a more proper out of schema adapter, it should be made lazy.
    {
        if (tree.context.root.length === 0 && schemaDataIsEmpty(tree.storedSchema)) {
            tree.transaction.start();
            const rootSchema = config.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 === FieldKinds.sequence.identifier ||
                rootKind === FieldKinds.optional.identifier) {
                // These kinds are known to tolerate empty, so use the schema as is:
                incrementalSchemaUpdate = config.schema;
            }
            else {
                assert(rootKind === FieldKinds.value.identifier, 0x5c8 /* Unexpected kind */);
                // Replace value kind with optional kind in root field schema:
                incrementalSchemaUpdate = {
                    ...config.schema,
                    rootFieldSchema: {
                        kind: 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",
            // );
            assert(allowsRepoSuperset(defaultSchemaPolicy, config.schema, incrementalSchemaUpdate), 0x5c9 /* Incremental Schema during update should be a allow a superset of the final schema */);
            // Update to intermediate schema
            tree.storedSchema.update(incrementalSchemaUpdate);
            // Insert initial tree
            tree.setContent(config.initialTree);
            // If intermediate schema is not final desired schema, update to the final schema:
            if (incrementalSchemaUpdate !== config.schema) {
                tree.storedSchema.update(config.schema);
            }
            tree.transaction.commit();
        }
    }
    // TODO: support adapters and include them here.
    const viewSchema = new ViewSchema(defaultSchemaPolicy, {}, config.schema);
    {
        const compatibility = viewSchema.checkCompatibility(tree.storedSchema);
        switch (config.allowedSchemaModifications) {
            case AllowedUpdateType.None: {
                if (compatibility.read !== Compatibility.Compatible) {
                    fail("Existing stored schema permits trees which are incompatible with the view schema");
                }
                if (compatibility.write !== Compatibility.Compatible) {
                    // TODO: support readonly mode in this case.
                    fail("View schema permits trees which are incompatible with the stored schema");
                }
                break;
            }
            case AllowedUpdateType.SchemaCompatible: {
                if (compatibility.read !== Compatibility.Compatible) {
                    fail("Existing stored schema permits trees which are incompatible with the view schema, so schema can not be updated");
                }
                if (compatibility.write !== Compatibility.Compatible) {
                    tree.storedSchema.update(config.schema);
                }
                break;
            }
            default: {
                unreachableCase(config.allowedSchemaModifications);
            }
        }
    }
    // 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.
    tree.storedSchema.registerDependent(new SimpleObservingDependent(() => {
        // 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:
        // This seems like the correct policy, but more clarity on how schematized views are updating during batches is needed.
        afterBatchCheck ?? (afterBatchCheck = tree.events.on("afterBatch", () => {
            assert(afterBatchCheck !== undefined, 0x728 /* unregistered event ran */);
            afterBatchCheck();
            afterBatchCheck = undefined;
            const compatibility = viewSchema.checkCompatibility(tree.storedSchema);
            if (compatibility.read !== Compatibility.Compatible) {
                fail("Stored schema changed to one that permits data incompatible with the view schema");
            }
            if (compatibility.write !== Compatibility.Compatible) {
                // TODO: support readonly mode in this case.
                fail("Stored schema changed to one that does not support all data allowed by view schema");
            }
        }));
    }));
    return tree;
}
//# sourceMappingURL=schematizedTree.js.map