"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SharedTreeChangeFamily = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const index_js_1 = require("../core/index.js");
const index_js_2 = require("../feature-libraries/index.js");
const index_js_3 = require("../util/index.js");
const sharedTreeChangeCodecs_js_1 = require("./sharedTreeChangeCodecs.js");
const sharedTreeEditBuilder_js_1 = require("./sharedTreeEditBuilder.js");
/**
 * Implementation of {@link ChangeFamily} that combines edits to fields and schema changes.
 *
 * @sealed
 */
class SharedTreeChangeFamily {
    constructor(revisionTagCodec, fieldBatchCodec, codecOptions, chunkCompressionStrategy) {
        this.modularChangeFamily = new index_js_2.ModularChangeFamily(index_js_2.fieldKinds, revisionTagCodec, fieldBatchCodec, codecOptions, chunkCompressionStrategy);
        this.codecs = (0, sharedTreeChangeCodecs_js_1.makeSharedTreeChangeCodecFamily)(this.modularChangeFamily.latestCodec, codecOptions);
    }
    buildEditor(changeReceiver) {
        return new sharedTreeEditBuilder_js_1.SharedTreeEditBuilder(this.modularChangeFamily, changeReceiver);
    }
    compose(changes) {
        const newChanges = [];
        const dataChangeRun = [];
        const flushDataChangeRun = () => {
            if (dataChangeRun.length > 0) {
                newChanges.push({
                    type: "data",
                    innerChange: this.modularChangeFamily.compose(dataChangeRun),
                });
                dataChangeRun.length = 0;
            }
        };
        for (const topChange of changes) {
            for (const change of topChange.change.changes) {
                if (change.type === "schema") {
                    flushDataChangeRun();
                    newChanges.push(change);
                }
                else {
                    dataChangeRun.push((0, index_js_1.mapTaggedChange)(topChange, change.innerChange));
                }
            }
        }
        flushDataChangeRun();
        return { changes: newChanges };
    }
    invert(change, isRollback) {
        const invertInnerChange = (innerChange) => {
            switch (innerChange.type) {
                case "data":
                    return {
                        type: "data",
                        innerChange: this.modularChangeFamily.invert((0, index_js_1.mapTaggedChange)(change, innerChange.innerChange), isRollback),
                    };
                case "schema": {
                    return {
                        type: "schema",
                        innerChange: {
                            schema: {
                                new: innerChange.innerChange.schema.old,
                                old: innerChange.innerChange.schema.new,
                            },
                        },
                    };
                }
                default:
                    (0, index_js_3.fail)("Unknown SharedTree change type.");
            }
        };
        return {
            changes: change.change.changes.map(invertInnerChange).reverse(),
        };
    }
    rebase(change, over, revisionMetadata) {
        if (change.changes.length === 0 || over.change.changes.length === 0) {
            return change;
        }
        if (hasSchemaChange(change) || hasSchemaChange(over.change)) {
            // Any SharedTreeChange (a list of sub-changes) that contains a schema change will cause ANY change that rebases over it to conflict.
            // Similarly, any SharedTreeChange containing a schema change will fail to rebase over ANY change.
            // Those two combine to mean: no concurrency with schema changes is supported.
            // This is fine because it's an open problem. Example: a tree with an A at the root and a schema that allows an A | B at the root will
            // become out of schema if a schema changes to restrict root types to just A is concurrent with a data change that sets it to a B.
            // We don't have an efficient way to detect this document-wide and there are varying opinions on restricting schema changes to prevent this.
            // A SharedTreeChange containing a schema change will NOT conflict in a non-concurrency case, as the "meatless sandwich" optimization
            // will result in rebase never being called.
            return SharedTreeChangeFamily.emptyChange;
        }
        (0, core_utils_1.assert)(change.changes.length === 1 && over.change.changes.length === 1, 0x884 /* SharedTreeChange should have exactly one inner change if no schema change is present. */);
        const dataChangeIntention = change.changes[0];
        const dataChangeOver = over.change.changes[0];
        (0, core_utils_1.assert)(dataChangeIntention.type === "data" && dataChangeOver.type === "data", 0x885 /* Data change should be present. */);
        return {
            changes: [
                {
                    type: "data",
                    innerChange: this.modularChangeFamily.rebase(dataChangeIntention.innerChange, (0, index_js_1.mapTaggedChange)(over, dataChangeOver.innerChange), revisionMetadata),
                },
            ],
        };
    }
    get rebaser() {
        return this;
    }
}
exports.SharedTreeChangeFamily = SharedTreeChangeFamily;
SharedTreeChangeFamily.emptyChange = {
    changes: [],
};
function hasSchemaChange(change) {
    return change.changes.some((innerChange) => innerChange.type === "schema");
}
//# sourceMappingURL=sharedTreeChangeFamily.js.map