"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.amendRebase = exports.rebase = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const util_1 = require("../../util");
const modular_schema_1 = require("../modular-schema");
const utils_1 = require("./utils");
const markListFactory_1 = require("./markListFactory");
const compose_1 = require("./compose");
const moveEffectTable_1 = require("./moveEffectTable");
const markQueue_1 = require("./markQueue");
/**
 * Rebases `change` over `base` assuming they both apply to the same initial state.
 * @param change - The changeset to rebase.
 * @param base - The changeset to rebase over.
 * @returns A changeset that performs the changes in `change` but does so assuming `base` has been applied first.
 *
 * WARNING! This implementation is incomplete:
 * - Some marks that affect existing content are removed instead of marked as conflicted when rebased over the deletion
 * of that content. This prevents us from then reinstating the mark when rebasing over the revive.
 * - Tombs are not added when rebasing an insert over a gap that is immediately left of deleted content.
 * This prevents us from being able to accurately track the position of the insert.
 * - Tiebreak ordering is not respected.
 * - Support for moves is not implemented.
 * - Support for slices is not implemented.
 */
function rebase(change, base, rebaseChild, genId, manager, revisionMetadata) {
    var _a;
    const baseInfo = base.revision === undefined ? undefined : revisionMetadata.getInfo(base.revision);
    const baseIntention = (_a = baseInfo === null || baseInfo === void 0 ? void 0 : baseInfo.rollbackOf) !== null && _a !== void 0 ? _a : base.revision;
    return rebaseMarkList(change, base.change, base.revision, baseIntention, rebaseChild, genId, manager);
}
exports.rebase = rebase;
function rebaseMarkList(currMarkList, baseMarkList, baseRevision, baseIntention, rebaseChild, genId, moveEffects) {
    var _a;
    const factory = new markListFactory_1.MarkListFactory(undefined, moveEffects, true);
    const queue = new RebaseQueue(baseRevision, baseIntention, baseMarkList, currMarkList, genId, moveEffects);
    // Each attach mark in `currMarkList` should have a lineage event added for `baseRevision` if a node adjacent to
    // the attach position was detached by `baseMarkList`.
    // At the time we process an attach we don't know whether the following node will be detached, so we record attach
    // marks which should have their lineage updated if we encounter a detach.
    const lineageRequests = [];
    let baseDetachOffset = 0;
    // The index of (i.e., number of nodes to the left of) the base mark in the input context of the base change.
    // This assumes the base changeset is not composite (and asserts if it is).
    let baseInputIndex = 0;
    while (!queue.isEmpty()) {
        const { baseMark, newMark: currMark } = queue.pop();
        if ((0, utils_1.isObjMark)(baseMark) && baseMark.type !== "Modify" && baseMark.revision !== undefined) {
            // TODO support rebasing over composite changeset
            (0, common_utils_1.assert)(baseMark.revision === baseRevision, 0x4f3 /* Unable to keep track of the base input offset in composite changeset */);
        }
        if (baseMark === undefined) {
            (0, common_utils_1.assert)(currMark !== undefined, 0x4f4 /* Non-empty queue should return at least one mark */);
            if ((0, utils_1.isAttach)(currMark)) {
                handleCurrAttach(currMark, factory, lineageRequests, baseDetachOffset, baseIntention);
            }
            else {
                if (baseDetachOffset > 0 && baseIntention !== undefined) {
                    updateLineage(lineageRequests, baseIntention);
                    baseDetachOffset = 0;
                }
                factory.push((0, utils_1.cloneMark)(currMark));
            }
        }
        else if (currMark === undefined) {
            // TODO: Do we need to handle rebasing over baseMark's changes in this case?
            if ((0, utils_1.isDetachMark)(baseMark)) {
                const detachLength = (0, utils_1.getInputLength)(baseMark);
                baseDetachOffset += detachLength;
                baseInputIndex += detachLength;
            }
            else if ((0, utils_1.isAttach)(baseMark)) {
                if (baseMark.type === "MoveIn" || baseMark.type === "ReturnTo") {
                    const effect = (0, moveEffectTable_1.getMoveEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, (_a = baseMark.revision) !== null && _a !== void 0 ? _a : baseRevision, baseMark.id);
                    if (effect.movedMark !== undefined) {
                        factory.push(effect.movedMark);
                        delete effect.movedMark;
                    }
                    else {
                        factory.pushOffset((0, utils_1.getOutputLength)(baseMark));
                    }
                }
                else {
                    factory.pushOffset((0, utils_1.getOutputLength)(baseMark));
                }
            }
        }
        else {
            (0, common_utils_1.assert)(!(0, utils_1.isNewAttach)(baseMark) && !(0, utils_1.isNewAttach)(currMark), 0x4f5 /* A new attach cannot be at the same position as another mark */);
            (0, common_utils_1.assert)((0, utils_1.getInputLength)(baseMark) === (0, utils_1.getInputLength)(currMark), 0x4f6 /* The two marks should be the same size */);
            const rebasedMark = rebaseMark(currMark, baseMark, baseRevision, baseIntention, baseInputIndex, rebaseChild, moveEffects);
            factory.push(rebasedMark);
            const detachLength = (0, utils_1.getInputLength)(baseMark);
            baseInputIndex += detachLength;
            if ((0, utils_1.isDetachMark)(baseMark)) {
                baseDetachOffset += detachLength;
            }
            else {
                if (baseDetachOffset > 0 && baseIntention !== undefined) {
                    updateLineage(lineageRequests, baseIntention);
                }
                lineageRequests.length = 0;
                baseDetachOffset = 0;
            }
        }
    }
    if (baseDetachOffset > 0 && baseIntention !== undefined) {
        updateLineage(lineageRequests, baseIntention);
    }
    return factory.list;
}
class RebaseQueue {
    constructor(baseRevision, baseIntention, baseMarks, newMarks, genId, moveEffects) {
        this.baseIntention = baseIntention;
        this.reattachOffset = 0;
        this.baseMarks = new markQueue_1.MarkQueue(baseMarks, baseRevision, moveEffects, false, genId);
        this.newMarks = new markQueue_1.MarkQueue(newMarks, undefined, moveEffects, true, genId);
    }
    isEmpty() {
        return this.baseMarks.isEmpty() && this.newMarks.isEmpty();
    }
    pop() {
        var _a, _b;
        const baseMark = this.baseMarks.peek();
        const newMark = this.newMarks.peek();
        if (baseMark === undefined && newMark === undefined) {
            return {};
        }
        else if (baseMark === undefined) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const length = (0, utils_1.getInputLength)(newMark);
            return {
                baseMark: length > 0 ? length : undefined,
                newMark: this.newMarks.tryDequeue(),
            };
        }
        else if (newMark === undefined) {
            const length = (0, utils_1.getInputLength)(baseMark);
            return {
                baseMark: this.baseMarks.tryDequeue(),
                newMark: length > 0 ? length : undefined,
            };
        }
        else if ((0, utils_1.isAttach)(baseMark) && (0, utils_1.isAttach)(newMark)) {
            if ((0, utils_1.isReattach)(baseMark) &&
                (0, utils_1.isReattach)(newMark) &&
                areRelatedReattaches(baseMark, newMark)) {
                return (0, utils_1.dequeueRelatedReattaches)(this.newMarks, this.baseMarks);
            }
            if ((0, utils_1.isReattach)(baseMark)) {
                const offset = (0, utils_1.getOffsetAtRevision)(newMark.lineage, (_a = baseMark.lastDetachedBy) !== null && _a !== void 0 ? _a : baseMark.detachedBy);
                if (offset !== undefined) {
                    // WARNING: the offset is based on the first node detached whereas the detachIndex is based on the
                    // first node in the field.
                    // The comparison below is the only valid one we can make at the moment.
                    // TODO: find a way to make the lineage and detachIndex info more comparable so we can correctly
                    // handle scenarios where either all or some fraction of newMark should come first.
                    if (offset >= baseMark.detachIndex + baseMark.count) {
                        return this.dequeueBase();
                    }
                }
            }
            if ((0, utils_1.isReattach)(newMark)) {
                const offset = (0, utils_1.getOffsetAtRevision)(baseMark.lineage, (_b = newMark.lastDetachedBy) !== null && _b !== void 0 ? _b : newMark.detachedBy);
                if (offset !== undefined) {
                    // WARNING: the offset is based on the first node detached whereas the detachIndex is based on the
                    // first node in the field.
                    // The comparison below is the only valid one we can make at the moment.
                    // TODO: find a way to make the lineage and detachIndex info more comparable so we can correctly
                    // handle scenarios where either all or some fraction of baseMark should come first.
                    if (offset >= newMark.detachIndex + newMark.count) {
                        return this.dequeueNew();
                    }
                }
            }
            const reattachOffset = (0, utils_1.getOffsetAtRevision)(newMark.lineage, this.baseIntention);
            if (reattachOffset !== undefined) {
                const offset = reattachOffset - this.reattachOffset;
                if (offset === 0) {
                    return this.dequeueNew();
                }
                else if (offset >= (0, utils_1.getOutputLength)(baseMark)) {
                    this.reattachOffset += (0, utils_1.getOutputLength)(baseMark);
                    return this.dequeueBase();
                }
                else {
                    const splitBaseMark = this.baseMarks.dequeueOutput(offset);
                    this.reattachOffset += offset;
                    return { baseMark: splitBaseMark };
                }
            }
            else if (isAttachAfterBaseAttach(newMark, baseMark) ||
                (0, utils_1.isConflictedReattach)(newMark)) {
                return this.dequeueBase();
            }
            else {
                return this.dequeueNew();
            }
        }
        else if ((0, utils_1.isAttachInGap)(newMark)) {
            return this.dequeueNew();
        }
        else if (
        // The `isNewAttach(baseMark)` bit is needed because of the way sandwich rebasing makes
        // the rebased local new attaches relevant to later local changes.
        ((0, utils_1.isNewAttach)(baseMark) || (0, utils_1.isActiveReattach)(baseMark)) &&
            (0, utils_1.isConflictedDetach)(newMark) &&
            // TODO: support muting/unmuting other detach mark types
            newMark.type === "ReturnFrom" &&
            isBaseAttachRelatedToConflictedDetach(baseMark, newMark, this.baseMarks.revision)) {
            (0, common_utils_1.assert)(newMark.detachIndex !== undefined, 0x4f7 /* A conflicted ReturnFrom should have a detachIndex */);
            const newMarkLength = newMark.count;
            const baseMarkLength = (0, utils_1.getOutputLength)(baseMark);
            if ((0, utils_1.isNewAttach)(baseMark) || newMark.detachIndex === baseMark.detachIndex) {
                if (newMarkLength < baseMarkLength) {
                    return {
                        baseMark: this.baseMarks.dequeueOutput(newMarkLength),
                        newMark: this.newMarks.dequeue(),
                    };
                }
                else if (newMarkLength > baseMarkLength) {
                    return {
                        baseMark: this.baseMarks.dequeue(),
                        newMark: this.newMarks.dequeueInput(baseMarkLength),
                    };
                }
                else {
                    return { baseMark: this.baseMarks.dequeue(), newMark: this.newMarks.dequeue() };
                }
            }
            else if (newMark.detachIndex < baseMark.detachIndex) {
                if (newMark.detachIndex + newMarkLength <= baseMark.detachIndex) {
                    return this.dequeueNew();
                }
                return {
                    newMark: this.newMarks.dequeueInput(baseMark.detachIndex - newMark.detachIndex),
                };
            }
            else {
                if (baseMark.detachIndex + baseMarkLength <= newMark.detachIndex) {
                    return this.dequeueBase();
                }
                return {
                    baseMark: this.baseMarks.dequeueOutput(newMark.detachIndex - baseMark.detachIndex),
                };
            }
        }
        // TODO: Handle case where `baseMarks` has adjacent or nested inverse reattaches from multiple revisions
        this.reattachOffset = 0;
        if ((0, utils_1.isAttachInGap)(baseMark)) {
            return this.dequeueBase();
        }
        else {
            this.reattachOffset = 0;
            const newMarkLength = (0, utils_1.getInputLength)(newMark);
            const baseMarkLength = (0, utils_1.getInputLength)(baseMark);
            if (newMarkLength < baseMarkLength) {
                return {
                    baseMark: this.baseMarks.dequeueInput(newMarkLength),
                    newMark: this.newMarks.dequeue(),
                };
            }
            else if (newMarkLength > baseMarkLength) {
                return {
                    baseMark: this.baseMarks.dequeue(),
                    newMark: this.newMarks.dequeueInput(baseMarkLength),
                };
            }
            else {
                return {
                    baseMark: this.baseMarks.dequeue(),
                    newMark: this.newMarks.dequeue(),
                };
            }
        }
    }
    dequeueBase() {
        return { baseMark: this.baseMarks.dequeue() };
    }
    dequeueNew() {
        return { newMark: this.newMarks.dequeue() };
    }
}
function rebaseMark(currMark, baseMark, baseRevision, baseIntention, baseInputOffset, rebaseChild, moveEffects) {
    var _a;
    if ((0, utils_1.isSkipMark)(baseMark) || (0, utils_1.isSkipLikeReattach)(baseMark) || (0, utils_1.isSkipLikeDetach)(baseMark)) {
        // TODO: Rebase currMark's changes over baseMark's changes.
        if ((0, utils_1.isObjMark)(currMark) && currMark.type === "Modify") {
            const childChange = rebaseChild(currMark.changes, undefined);
            return childChange !== undefined ? Object.assign(Object.assign({}, currMark), { changes: childChange }) : 1;
        }
        return (0, utils_1.cloneMark)(currMark);
    }
    const baseType = baseMark.type;
    switch (baseType) {
        case "Delete": {
            if ((0, utils_1.isReattach)(currMark)) {
                // TODO: add `addedBy: RevisionTag` to inverses of attaches so we can detect when
                // baseMark.addedBy === currMark.conflictsWith, which indicates the deletion is the undo of the
                // reattach that conflicts with currMark. When that's the case, the mark should no longer be
                // marked as conflicted.
                // See skipped test: Revive ↷ [Revive, undo(Revive)] => Revive
                if (currMark.isIntention || currMark.conflictsWith === baseIntention) {
                    const reattach = Object.assign(Object.assign({}, (0, utils_1.cloneMark)(currMark)), { 
                        // Update the characterization of the deleted content
                        detachIndex: baseInputOffset });
                    if (currMark.isIntention) {
                        reattach.detachedBy = baseIntention;
                    }
                    else if (baseIntention !== reattach.detachedBy) {
                        reattach.lastDetachedBy = baseIntention;
                    }
                    delete reattach.conflictsWith;
                    // TODO: Rebase reattach's changes over delete's changes.
                    return reattach;
                }
                // The reattach mark remains conflicted because the deletion was performed by a different change.
                // After this, the only way for the reattach to recover from the conflict is for the nodes to be
                // revived and for the original deletion (currMark.detachedBy) to be re-applied.
                // TODO: Rebase reattach's changes over delete's changes.
                return Object.assign(Object.assign({}, (0, utils_1.cloneMark)(currMark)), { lastDetachedBy: baseIntention, detachIndex: baseInputOffset });
            }
            if ((0, utils_1.isObjMark)(currMark) &&
                (currMark.type === "MoveOut" || currMark.type === "ReturnFrom")) {
                (0, moveEffectTable_1.getOrAddEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, currMark.revision, currMark.id).shouldRemove = true;
            }
            // TODO: Represent muted change, and rebase currMark's changes over baseMark's changes.
            return 0;
        }
        case "Revive":
        case "ReturnTo": {
            (0, common_utils_1.assert)((0, utils_1.isDetachMark)(currMark) || (0, utils_1.isReattach)(currMark), 0x4f8 /* Only a detach or a reattach can overlap with a non-inert reattach */);
            const currMarkType = currMark.type;
            switch (currMarkType) {
                case "Delete":
                case "MoveOut":
                case "ReturnFrom": {
                    (0, common_utils_1.assert)(currMarkType === "ReturnFrom", 0x4f9 /* TODO: support conflict management for other detach marks */);
                    (0, common_utils_1.assert)((0, utils_1.isConflicted)(currMark) && currMark.conflictsWith === baseIntention, 0x4fa /* Invalid reattach mark overlap */);
                    // The nodes that currMark aims to detach are being reattached by baseMark
                    const newCurrMark = (0, utils_1.cloneMark)(currMark);
                    delete newCurrMark.conflictsWith;
                    delete newCurrMark.detachIndex;
                    (0, moveEffectTable_1.getOrAddEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, newCurrMark.revision, newCurrMark.id).pairedMarkStatus = moveEffectTable_1.PairedMarkUpdate.Reactivated;
                    // TODO: Rebase currMark's changes over baseMark's changes.
                    return newCurrMark;
                }
                case "Revive":
                case "ReturnTo": {
                    if (currMark.isIntention) {
                        // Past this point, currMark must be a reattach.
                        (0, common_utils_1.assert)((0, utils_1.isActiveReattach)(currMark), 0x4fb /* Invalid reattach mark overlap */);
                        // The nodes that currMark aims to reattach are being reattached by baseMark
                        // TODO: Rebase currMark's changes over baseMark's changes.
                        return Object.assign(Object.assign({}, (0, utils_1.cloneMark)(currMark)), { conflictsWith: baseIntention });
                    }
                    if ((0, utils_1.isActiveReattach)(currMark)) {
                        // The nodes that currMark aims to reattach are being reattached by baseMark
                        if (currMarkType === "ReturnTo") {
                            (0, moveEffectTable_1.getOrAddEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Source, currMark.revision, currMark.id).pairedMarkStatus = moveEffectTable_1.PairedMarkUpdate.Deactivated;
                        }
                        // TODO: Rebase currMark's changes over baseMark's changes.
                        return Object.assign(Object.assign({}, (0, utils_1.cloneMark)(currMark)), { conflictsWith: baseIntention });
                    }
                    (0, common_utils_1.assert)(!(0, utils_1.isSkipLikeReattach)(currMark), 0x4fc /* Unsupported reattach mark overlap */);
                    // The nodes that currMark aims to reattach and were detached by `currMark.lastDetachedBy`
                    // are being reattached by baseMark.
                    (0, common_utils_1.assert)(currMark.lastDetachedBy === baseMark.detachedBy, 0x4fd /* Invalid revive mark overlap */);
                    const revive = (0, utils_1.cloneMark)(currMark);
                    delete revive.lastDetachedBy;
                    // TODO: Rebase currMark's changes over baseMark's changes.
                    return revive;
                }
                default:
                    (0, common_utils_1.unreachableCase)(currMarkType);
            }
        }
        case "Modify": {
            if ((0, utils_1.isSkipMark)(currMark)) {
                const child = rebaseChild(undefined, baseMark.changes);
                return child !== undefined
                    ? {
                        type: "Modify",
                        changes: child,
                    }
                    : 1;
            }
            if ((0, utils_1.isModify)(currMark)) {
                const child = rebaseChild(currMark.changes, baseMark.changes);
                return child !== undefined
                    ? Object.assign(Object.assign({}, (0, utils_1.cloneMark)(currMark)), { changes: child }) : 1;
            }
            // TODO: Rebase currMark's changes over baseMark's changes.
            return (0, utils_1.cloneMark)(currMark);
        }
        case "MoveOut":
        case "ReturnFrom": {
            if (!(0, utils_1.isSkipMark)(currMark)) {
                const newCurrMark = (0, utils_1.cloneMark)(currMark);
                if (newCurrMark.type === "ReturnFrom") {
                    // The nodes that currMark aims to detach are being detached by baseMark
                    newCurrMark.conflictsWith = baseIntention;
                    newCurrMark.detachIndex = baseInputOffset;
                    (0, moveEffectTable_1.getOrAddEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, newCurrMark.revision, newCurrMark.id).pairedMarkStatus = moveEffectTable_1.PairedMarkUpdate.Deactivated;
                    // TODO: Rebase newCurrMark's changes over baseMark's changes.
                    return newCurrMark;
                }
                else if (newCurrMark.type === "ReturnTo") {
                    (0, common_utils_1.assert)((0, utils_1.isSkipLikeReattach)(newCurrMark), 0x4fe /* Only a skip-like reattach can overlap with a ReturnFrom */);
                    // The already populated cells that currMark aimed to reattach content into
                    // are having their contents detached by baseMark.
                    // This makes it possible for currMark to be active again.
                    if (newCurrMark.isIntention) {
                        newCurrMark.detachedBy = baseIntention;
                    }
                    else if (baseIntention !== newCurrMark.detachedBy) {
                        newCurrMark.lastDetachedBy = baseIntention;
                    }
                    newCurrMark.detachIndex = baseInputOffset;
                    delete newCurrMark.conflictsWith;
                    const effect = (0, moveEffectTable_1.getOrAddEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Source, newCurrMark.revision, newCurrMark.id);
                    effect.detacher = baseIntention;
                    effect.pairedMarkStatus = moveEffectTable_1.PairedMarkUpdate.Reactivated;
                    // TODO: Rebase newCurrMark's changes over baseMark's changes.
                    return newCurrMark;
                }
                else if (newCurrMark.type === "Revive" && !newCurrMark.isIntention) {
                    (0, common_utils_1.assert)((0, utils_1.isSkipLikeReattach)(newCurrMark), 0x4ff /* Only a skip-like reattach can overlap with a ReturnFrom */);
                    // The already populated cells that currMark aimed to revive content into
                    // are having their contents detached by baseMark.
                    // The revive mark remains conflicted because the detach was performed by a different change than
                    // the change the revive aims to revert.
                    // After this, the only way for the reattach to to recover from the conflict is for the nodes to be
                    // returned and for the original deletion (currMark.detachedBy) to be re-applied.
                    // Update the characterization of the deleted content
                    newCurrMark.lastDetachedBy = baseIntention;
                    newCurrMark.detachIndex = baseInputOffset;
                    // TODO: Rebase newCurrMark's changes over baseMark's changes.
                    return newCurrMark;
                }
            }
            // TODO: Rebase currMark's changes over baseMark's changes.
            // Note that even if currMark is a skip mark,
            // it could become a modify mark after rebasing its (empty) changes over baseMark's changes.
            if (!(0, utils_1.isSkipMark)(currMark)) {
                (0, moveEffectTable_1.getOrAddEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, (_a = baseMark.revision) !== null && _a !== void 0 ? _a : baseRevision, baseMark.id).movedMark = (0, utils_1.cloneMark)(currMark);
            }
            return 0;
        }
        default:
            (0, util_1.fail)(`Unsupported mark type: ${baseType}`);
    }
}
function amendRebase(rebasedMarks, baseMarks, rebaseChild, genId, crossFieldManager, revisionMetadata) {
    return amendRebaseI(baseMarks.revision, baseMarks.change, rebasedMarks, rebaseChild, crossFieldManager, revisionMetadata);
}
exports.amendRebase = amendRebase;
function amendRebaseI(baseRevision, baseMarks, rebasedMarks, rebaseChild, moveEffects, revisionMetadata) {
    var _a;
    // Is it correct to use ComposeQueue here?
    // If we used a special AmendRebaseQueue, we could ignore any base marks which don't have associated move-ins
    const queue = new compose_1.ComposeQueue(baseRevision, baseMarks, undefined, rebasedMarks, () => (0, util_1.fail)("Should not generate new IDs when applying move effects"), moveEffects, revisionMetadata);
    const factory = new markListFactory_1.MarkListFactory(undefined, moveEffects);
    while (!queue.isEmpty()) {
        const { baseMark, newMark } = queue.pop();
        if ((0, utils_1.isObjMark)(baseMark) && (baseMark.type === "MoveIn" || baseMark.type === "ReturnTo")) {
            const effect = (0, moveEffectTable_1.getMoveEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, (_a = baseMark.revision) !== null && _a !== void 0 ? _a : baseRevision, baseMark.id);
            if (effect.movedMark !== undefined) {
                factory.push(effect.movedMark);
                factory.pushOffset(-(0, utils_1.getInputLength)(effect.movedMark));
                delete effect.movedMark;
            }
        }
        if (newMark !== undefined) {
            let rebasedMark = newMark;
            // TODO: Handle all pairings of base and new mark types.
            if (baseMark !== undefined && (0, utils_1.isModify)(baseMark)) {
                if ((0, utils_1.isSkipMark)(newMark)) {
                    const childChange = rebaseChild(undefined, baseMark.changes);
                    if (childChange !== undefined) {
                        rebasedMark = { type: "Modify", changes: childChange };
                    }
                }
                else {
                    switch (newMark.type) {
                        case "Modify": {
                            const childChange = rebaseChild(newMark.changes, baseMark.changes);
                            if (childChange === undefined) {
                                rebasedMark = 1;
                            }
                            else {
                                newMark.changes = childChange;
                            }
                        }
                        default:
                            break;
                    }
                }
            }
            factory.push(rebasedMark);
        }
    }
    // We may have discovered new mergeable marks while applying move effects, as we may have moved a MoveOut next to another MoveOut.
    // A second pass through MarkListFactory will handle any remaining merges.
    const factory2 = new markListFactory_1.MarkListFactory(undefined, moveEffects);
    for (const mark of factory.list) {
        factory2.push(mark);
    }
    return factory2.list;
}
function handleCurrAttach(currMark, factory, lineageRequests, offset, baseIntention) {
    const rebasedMark = (0, utils_1.cloneMark)(currMark);
    // If the changeset we are rebasing over has the same intention as an event in rebasedMark's lineage,
    // we assume that the base changeset is the inverse of the changeset in the lineage, so we remove the lineage event.
    // TODO: Handle cases where the base changeset is a composition of multiple revisions.
    // TODO: Don't remove the lineage event in cases where the event isn't actually inverted by the base changeset,
    // e.g., if the inverse of the lineage event is muted after rebasing.
    if (baseIntention !== undefined) {
        tryRemoveLineageEvent(rebasedMark, baseIntention);
    }
    factory.pushContent(rebasedMark);
    lineageRequests.push({ mark: rebasedMark, offset });
}
function isAttachAfterBaseAttach(currMark, baseMark) {
    const lineageCmp = compareLineages(currMark.lineage, baseMark.lineage);
    if (lineageCmp < 0) {
        return false;
    }
    else if (lineageCmp > 0) {
        return true;
    }
    // TODO: Handle tiebreaking, including support for the following scenario
    // Staring state: a b
    // A1) Delete a b
    // A2) Insert c
    // B) Insert x between a and b
    // Instead of using B's tiebreak policy, we should first consider the relative positions of a, b, and c if A1 were undone.
    // The best outcome seems to be that c is positioned relative to ab according to A2's tiebreak policy.
    return false;
}
function compareLineages(lineage1, lineage2) {
    if (lineage1 === undefined || lineage2 === undefined) {
        return 0;
    }
    const lineage1Offsets = new Map();
    for (const event of lineage1) {
        lineage1Offsets.set(event.revision, event.offset);
    }
    for (let i = lineage2.length - 1; i >= 0; i--) {
        const event2 = lineage2[i];
        const offset1 = lineage1Offsets.get(event2.revision);
        if (offset1 !== undefined) {
            const offset2 = event2.offset;
            if (offset1 < offset2) {
                return -1;
            }
            else if (offset1 > offset2) {
                return 1;
            }
        }
    }
    return 0;
}
function updateLineage(requests, revision) {
    for (const request of requests) {
        const mark = request.mark;
        if (mark.lineage === undefined) {
            mark.lineage = [];
        }
        mark.lineage.push({ revision, offset: request.offset });
    }
}
function tryRemoveLineageEvent(mark, revisionToRemove) {
    if (mark.lineage === undefined) {
        return;
    }
    const index = mark.lineage.findIndex((event) => event.revision === revisionToRemove);
    if (index >= 0) {
        mark.lineage.splice(index, 1);
        if (mark.lineage.length === 0) {
            delete mark.lineage;
        }
    }
}
/**
 * @returns true iff both reattaches target cells that were affected by the same detach.
 * The target cells may or may not overlap depending on detach index information.
 *
 * Only valid in the context of a rebase (i.e., both marks have the same input context).
 */
function areRelatedReattaches(baseMark, newMark) {
    var _a, _b;
    const baseReference = (_a = baseMark.lastDetachedBy) !== null && _a !== void 0 ? _a : baseMark.detachedBy;
    const newReference = (_b = newMark.lastDetachedBy) !== null && _b !== void 0 ? _b : newMark.detachedBy;
    return baseReference === newReference;
}
/**
 * @returns true iff `baseMark` attaches nodes in cells whose contents were detached by the same change
 * that conflicts with `newMark`.
 * The target cells may or may not overlap depending on detach index information.
 */
function isBaseAttachRelatedToConflictedDetach(baseMark, newMark, baseRevision) {
    var _a;
    return (((0, utils_1.isActiveReattach)(baseMark) && baseMark.detachedBy === newMark.conflictsWith) ||
        ((_a = baseMark.revision) !== null && _a !== void 0 ? _a : baseRevision) === newMark.conflictsWith);
}
//# sourceMappingURL=rebase.js.map