"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 core_utils_1 = require("@fluidframework/core-utils");
const util_1 = require("../../util");
const modular_schema_1 = require("../modular-schema");
const utils_1 = require("./utils");
const format_1 = require("./format");
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, nodeExistenceState = modular_schema_1.NodeExistenceState.Alive) {
    (0, core_utils_1.assert)(base.revision !== undefined, 0x69b /* Cannot rebase over changeset with no revision */);
    const baseInfo = base.revision === undefined ? undefined : revisionMetadata.getInfo(base.revision);
    const baseIntention = baseInfo?.rollbackOf ?? base.revision;
    return rebaseMarkList(change, base.change, base.revision, baseIntention, rebaseChild, genId, manager, nodeExistenceState);
}
exports.rebase = rebase;
function rebaseMarkList(currMarkList, baseMarkList, baseRevision, baseIntention, rebaseChild, genId, moveEffects, nodeExistenceState) {
    const factory = new markListFactory_1.MarkListFactory();
    const queue = new RebaseQueue(baseRevision, baseIntention, baseMarkList, currMarkList, genId, moveEffects);
    // Each mark with empty input cells in `currMarkList` should have a lineage event added for all adjacent detaches in the base changeset.
    // At the time we process an attach we don't know about detaches of later nodes,
    // so we record marks which should have their lineage updated if we encounter a detach.
    const lineageRecipients = [];
    const lineageEntries = [];
    // List of IDs of detaches encountered in the base changeset which are adjacent to the current position.
    let detachBlock = [];
    while (!queue.isEmpty()) {
        const { baseMark, newMark: currMark } = queue.pop();
        if ("revision" in baseMark) {
            // TODO support rebasing over composite changeset
            (0, core_utils_1.assert)(baseMark.revision === baseRevision, 0x4f3 /* Unable to keep track of the base input offset in composite changeset */);
        }
        const length = (0, utils_1.getInputLength)(baseMark);
        (0, core_utils_1.assert)(length === (0, utils_1.getInputLength)(currMark), 0x4f6 /* The two marks should be the same size */);
        const rebasedMark = rebaseMark(currMark, baseMark, baseRevision, baseIntention, rebaseChild, moveEffects, nodeExistenceState);
        // Note that we first add lineage for `baseMark` to `lineageRecipients`, then handle adding lineage to `rebasedMark`,
        // then add `baseMark` to `lineageEntries` so that `rebasedMark` does not get an entry for `baseMark`.
        // `rebasedMark` should already have a detach event for `baseMark`.
        if ((0, utils_1.markEmptiesCells)(baseMark)) {
            (0, core_utils_1.assert)((0, utils_1.isDetachMark)(baseMark), 0x709 /* Only detach marks should empty cells */);
            const detachId = (0, utils_1.getDetachCellId)(baseMark, baseIntention);
            (0, core_utils_1.assert)(detachId.revision !== undefined, 0x74a /* Detach ID should have a revision */);
            addLineageToRecipients(lineageRecipients, detachId.revision, detachId.localId, length);
        }
        if ((0, utils_1.areInputCellsEmpty)(rebasedMark)) {
            if ((0, utils_1.markEmptiesCells)(baseMark)) {
                (0, core_utils_1.assert)((0, utils_1.isDetachMark)(baseMark), 0x74b /* Only detaches empty cells */);
                if (baseMark.type === "MoveOut" || baseMark.detachIdOverride === undefined) {
                    setMarkAdjacentCells(rebasedMark, detachBlock);
                }
            }
            else {
                handleLineage(rebasedMark, lineageRecipients, baseIntention, detachBlock, lineageEntries);
            }
        }
        factory.push(rebasedMark);
        if ((0, utils_1.markEmptiesCells)(baseMark)) {
            (0, core_utils_1.assert)((0, utils_1.isDetachMark)(baseMark), 0x70a /* Only detach marks should empty cells */);
            const detachId = (0, utils_1.getDetachCellId)(baseMark, baseIntention);
            if (detachId.revision === baseIntention) {
                addIdRange(detachBlock, { id: baseMark.id, count: baseMark.count });
            }
            else {
                (0, core_utils_1.assert)(detachId.revision !== undefined, 0x74c /* Detach ID should have revision */);
                lineageEntries.push({
                    revision: detachId.revision,
                    id: detachId.localId,
                    count: length,
                });
            }
        }
        else if (!(0, utils_1.areOutputCellsEmpty)(baseMark)) {
            lineageRecipients.length = 0;
            detachBlock = [];
        }
    }
    return factory.list;
}
/**
 * Generates a NoOp mark that targets the same cells as the input mark.
 * @param mark - The mark the NoOp should target.
 * @param revision - The revision, if available.
 * @returns A NoOp mark that targets the same cells as the input mark.
 */
function generateNoOpWithCellId(mark, revision) {
    const length = mark.count;
    const cellId = (0, utils_1.getInputCellId)(mark, revision);
    return cellId === undefined ? { count: length } : { count: length, cellId };
}
class RebaseQueue {
    constructor(baseRevision, baseIntention, baseMarks, newMarks, genId, moveEffects) {
        this.baseIntention = baseIntention;
        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() {
        const baseMark = this.baseMarks.peek();
        const newMark = this.newMarks.peek();
        (0, core_utils_1.assert)(!(baseMark === undefined && newMark === undefined), 0x722 /* Cannot pop from empty queue */);
        if (baseMark === undefined) {
            const dequeuedNewMark = this.newMarks.dequeue();
            return {
                baseMark: generateNoOpWithCellId(dequeuedNewMark),
                newMark: dequeuedNewMark,
            };
        }
        else if (newMark === undefined) {
            const dequeuedBaseMark = this.baseMarks.dequeue();
            return {
                baseMark: dequeuedBaseMark,
                newMark: generateNoOpWithCellId(dequeuedBaseMark, this.baseIntention),
            };
        }
        else if ((0, utils_1.areInputCellsEmpty)(baseMark) && (0, utils_1.areInputCellsEmpty)(newMark)) {
            const cmp = compareCellPositions(this.baseIntention, baseMark, newMark);
            if (cmp < 0) {
                const dequeuedBaseMark = this.baseMarks.dequeueUpTo(-cmp);
                return {
                    baseMark: dequeuedBaseMark,
                    newMark: generateNoOpWithCellId(dequeuedBaseMark, this.baseIntention),
                };
            }
            else if (cmp > 0) {
                const dequeuedNewMark = this.newMarks.dequeueUpTo(cmp);
                return {
                    newMark: dequeuedNewMark,
                    baseMark: generateNoOpWithCellId(dequeuedNewMark),
                };
            }
            else {
                return this.dequeueBoth();
            }
        }
        else if ((0, utils_1.areInputCellsEmpty)(newMark)) {
            return this.dequeueNew();
        }
        else if ((0, utils_1.areInputCellsEmpty)(baseMark)) {
            return this.dequeueBase();
        }
        else {
            return this.dequeueBoth();
        }
    }
    dequeueBase() {
        const baseMark = this.baseMarks.dequeue();
        return { baseMark, newMark: generateNoOpWithCellId(baseMark, this.baseIntention) };
    }
    dequeueNew() {
        const newMark = this.newMarks.dequeue();
        return { newMark, baseMark: generateNoOpWithCellId(newMark) };
    }
    dequeueBoth() {
        const baseMark = this.baseMarks.peek();
        const newMark = this.newMarks.peek();
        (0, core_utils_1.assert)(baseMark !== undefined && newMark !== undefined, 0x69c /* Cannot dequeue both unless both mark queues are non-empty */);
        const length = Math.min(newMark.count, baseMark.count);
        return {
            baseMark: this.baseMarks.dequeueUpTo(length),
            newMark: this.newMarks.dequeueUpTo(length),
        };
    }
}
function rebaseMark(currMark, baseMark, baseRevision, baseIntention, rebaseChild, moveEffects, nodeExistenceState) {
    let rebasedMark = rebaseNodeChange((0, utils_1.cloneMark)(currMark), baseMark, rebaseChild);
    if ((0, utils_1.markEmptiesCells)(baseMark)) {
        const moveId = (0, utils_1.getMarkMoveId)(baseMark);
        if (moveId !== undefined) {
            (0, core_utils_1.assert)((0, moveEffectTable_1.isMoveMark)(baseMark), 0x6f0 /* Only move marks have move IDs */);
            if (markFollowsMoves(rebasedMark)) {
                sendMarkToDest(rebasedMark, moveEffects, baseRevision, moveId, baseMark.count);
                return { count: 0 };
            }
            const modify = rebasedMark.changes;
            if (modify !== undefined) {
                rebasedMark = (0, utils_1.withNodeChange)(rebasedMark, undefined);
                const nestedChange = {
                    count: 1,
                    changes: modify,
                };
                sendMarkToDest(nestedChange, moveEffects, baseRevision, moveId, baseMark.count);
            }
        }
        (0, core_utils_1.assert)(!(0, utils_1.isNewAttach)(rebasedMark), 0x69d /* A new attach should not be rebased over its cell being emptied */);
        if ((0, moveEffectTable_1.isMoveMark)(rebasedMark)) {
            if (rebasedMark.type === "MoveOut" || rebasedMark.type === "ReturnFrom") {
                setPairedMarkStatus(moveEffects, modular_schema_1.CrossFieldTarget.Destination, rebasedMark.revision, rebasedMark.id, rebasedMark.count, moveEffectTable_1.PairedMarkUpdate.Deactivated);
            }
            else if (rebasedMark.type === "ReturnTo") {
                setPairedMarkStatus(moveEffects, modular_schema_1.CrossFieldTarget.Source, rebasedMark.revision, rebasedMark.id, rebasedMark.count, moveEffectTable_1.PairedMarkUpdate.Reactivated);
            }
        }
        (0, core_utils_1.assert)((0, utils_1.isDetachMark)(baseMark), 0x70b /* Only detach marks should empty cells */);
        const baseMarkIntention = getMarkIntention(baseMark, baseIntention);
        const baseCellId = (0, utils_1.getDetachCellId)(baseMark, baseMarkIntention);
        rebasedMark = makeDetachedMark(rebasedMark, (0, utils_1.cloneCellId)(baseCellId));
    }
    else if ((0, utils_1.markFillsCells)(baseMark)) {
        if ((0, moveEffectTable_1.isMoveMark)(baseMark)) {
            const movedMark = getMovedMark(moveEffects, baseMark.revision ?? baseRevision, baseMark.id, baseMark.count);
            if (movedMark !== undefined) {
                return movedMark;
            }
        }
        if ((0, moveEffectTable_1.isMoveMark)(rebasedMark)) {
            if ((rebasedMark.type === "MoveOut" || rebasedMark.type === "ReturnFrom") &&
                nodeExistenceState === modular_schema_1.NodeExistenceState.Alive) {
                setPairedMarkStatus(moveEffects, modular_schema_1.CrossFieldTarget.Destination, rebasedMark.revision, rebasedMark.id, rebasedMark.count, moveEffectTable_1.PairedMarkUpdate.Reactivated);
            }
            else if (rebasedMark.type === "ReturnTo") {
                setPairedMarkStatus(moveEffects, modular_schema_1.CrossFieldTarget.Source, rebasedMark.revision, rebasedMark.id, rebasedMark.count, moveEffectTable_1.PairedMarkUpdate.Deactivated);
            }
        }
        rebasedMark = withoutCellId(rebasedMark);
    }
    else if (nodeExistenceState === modular_schema_1.NodeExistenceState.Alive &&
        (rebasedMark.type === "MoveOut" || rebasedMark.type === "ReturnFrom") &&
        rebasedMark.cellId === undefined) {
        setPairedMarkStatus(moveEffects, modular_schema_1.CrossFieldTarget.Destination, rebasedMark.revision, rebasedMark.id, rebasedMark.count, moveEffectTable_1.PairedMarkUpdate.Reactivated);
    }
    else if (nodeExistenceState === modular_schema_1.NodeExistenceState.Dead &&
        (rebasedMark.type === "MoveOut" || rebasedMark.type === "ReturnFrom")) {
        setPairedMarkStatus(moveEffects, modular_schema_1.CrossFieldTarget.Destination, rebasedMark.revision, rebasedMark.id, rebasedMark.count, moveEffectTable_1.PairedMarkUpdate.Deactivated);
    }
    return rebasedMark;
}
function markFollowsMoves(mark) {
    const type = mark.type;
    switch (type) {
        case "Delete":
        case "MoveOut":
        case "Revive":
            return true;
        case format_1.NoopMarkType:
        case "ReturnFrom":
        case "Insert":
        case "MoveIn":
        case "ReturnTo":
        case "Placeholder":
            return false;
        default:
            (0, core_utils_1.unreachableCase)(type);
    }
}
// It is expected that the range from `id` to `id + count - 1` has the same move effect.
// The call sites to this function are making queries about a mark which has already been split by a `MarkQueue`
// to match the ranges in `moveEffects`.
// TODO: Reduce the duplication between this and other MoveEffect helpers
function sendMarkToDest(mark, moveEffects, revision, id, count) {
    const effect = (0, moveEffectTable_1.getMoveEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, revision, id, count, false);
    let newEffect;
    if (effect !== undefined) {
        (0, core_utils_1.assert)(effect.start <= id && effect.start + effect.length >= id + count, 0x6f1 /* Expected effect to cover entire mark */);
        newEffect = { ...effect.value, movedMark: mark };
    }
    else {
        newEffect = { movedMark: mark };
    }
    (0, moveEffectTable_1.setMoveEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, revision, id, count, newEffect);
}
// It is expected that the range from `id` to `id + count - 1` has the same move effect.
// The call sites to this function are making queries about a mark which has already been split by a `MarkQueue`
// to match the ranges in `moveEffects`.
// TODO: Reduce the duplication between this and other MoveEffect helpers
function setPairedMarkStatus(moveEffects, target, revision, id, count, status) {
    const effect = (0, moveEffectTable_1.getMoveEffect)(moveEffects, target, revision, id, count, false);
    let newEffect;
    if (effect !== undefined) {
        (0, core_utils_1.assert)(effect.start <= id && effect.start + effect.length >= id + count, 0x6f2 /* Expected effect to cover entire mark */);
        newEffect = { ...effect.value, pairedMarkStatus: status };
    }
    else {
        newEffect = { pairedMarkStatus: status };
    }
    (0, moveEffectTable_1.setMoveEffect)(moveEffects, target, revision, id, count, newEffect);
}
function getMarkIntention(mark, intention) {
    // TODO: Use mark's revision tag when available.
    // Currently we never rebase over a composition of multiple revisions, so this isn't necessary.
    return intention;
}
function rebaseNodeChange(currMark, baseMark, nodeRebaser) {
    const baseChange = baseMark.changes;
    const currChange = currMark.changes;
    if ((0, utils_1.markEmptiesCells)(baseMark) && !(0, moveEffectTable_1.isMoveMark)(baseMark)) {
        return (0, utils_1.withNodeChange)(currMark, nodeRebaser(currChange, baseChange, modular_schema_1.NodeExistenceState.Dead));
    }
    else if ((0, utils_1.markFillsCells)(baseMark) && !(0, moveEffectTable_1.isMoveMark)(baseMark)) {
        return (0, utils_1.withNodeChange)(currMark, nodeRebaser(currChange, baseChange, modular_schema_1.NodeExistenceState.Alive));
    }
    return (0, utils_1.withNodeChange)(currMark, nodeRebaser(currChange, baseChange));
}
function makeDetachedMark(mark, cellId) {
    (0, core_utils_1.assert)(mark.cellId === undefined, 0x69f /* Expected mark to be attached */);
    return { ...mark, cellId };
}
function withoutCellId(mark) {
    const newMark = { ...mark };
    delete newMark.cellId;
    return newMark;
}
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) {
    // 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();
    while (!queue.isEmpty()) {
        const { baseMark, newMark } = queue.pop();
        if (baseMark === undefined) {
            (0, core_utils_1.assert)(newMark !== undefined, 0x70c /* Non-empty RebaseQueue should not provide two empty marks */);
            factory.push((0, utils_1.withNodeChange)(newMark, rebaseChild(newMark.changes, undefined)));
        }
        if (baseMark !== undefined &&
            (baseMark.type === "MoveIn" || baseMark.type === "ReturnTo")) {
            const movedMark = getMovedMark(moveEffects, baseMark.revision ?? baseRevision, baseMark.id, baseMark.count);
            if (movedMark !== undefined) {
                factory.push(movedMark);
                factory.pushOffset(-(0, utils_1.getInputLength)(movedMark));
            }
        }
        if (newMark !== undefined && baseMark !== undefined) {
            const rebasedMark = rebaseNodeChange((0, utils_1.cloneMark)(newMark), baseMark, rebaseChild);
            factory.push(rebasedMark);
        }
    }
    return factory.list;
}
// It is expected that the range from `id` to `id + count - 1` has the same move effect.
// The call sites to this function are making queries about a mark which has already been split by a `MarkQueue`
// to match the ranges in `moveEffects`.
// TODO: Reduce the duplication between this and other MoveEffect helpers
function getMovedMark(moveEffects, revision, id, count) {
    const effect = (0, moveEffectTable_1.getMoveEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, revision, id, count);
    if (effect?.value.movedMark !== undefined) {
        (0, core_utils_1.assert)(effect.start <= id && effect.start + effect.length >= id + count, 0x6f3 /* Expected effect to cover entire mark */);
        const newEffect = { ...effect.value };
        delete newEffect.movedMark;
        (0, moveEffectTable_1.setMoveEffect)(moveEffects, modular_schema_1.CrossFieldTarget.Destination, revision, id, count, newEffect, false);
        return effect.value.movedMark;
    }
    return undefined;
}
function handleLineage(rebasedMark, lineageRecipients, baseIntention, detachBlock, lineageEntries) {
    // 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.
    const lineageHolder = getLineageHolder(rebasedMark);
    tryRemoveLineageEvents(lineageHolder, baseIntention);
    const cellRevision = (0, utils_1.getInputCellId)(rebasedMark, undefined)?.revision;
    if (baseIntention !== cellRevision) {
        for (const entry of detachBlock) {
            addLineageEntry(lineageHolder, baseIntention, entry.id, entry.count, entry.count);
        }
    }
    for (const entry of lineageEntries) {
        if (entry.revision !== cellRevision) {
            addLineageEntry(lineageHolder, entry.revision, entry.id, entry.count, entry.count);
        }
    }
    lineageRecipients.push(rebasedMark);
}
function addLineageToRecipients(recipients, revision, id, count) {
    for (const mark of recipients) {
        if ((0, utils_1.getInputCellId)(mark, undefined)?.revision !== revision) {
            addLineageEntry(getLineageHolder(mark), revision, id, count, 0);
        }
    }
}
function addLineageEntry(lineageHolder, revision, id, count, offset) {
    if (lineageHolder.lineage === undefined) {
        lineageHolder.lineage = [];
    }
    if (lineageHolder.lineage.length > 0) {
        const lastEntry = lineageHolder.lineage[lineageHolder.lineage.length - 1];
        if (lastEntry.revision === revision && lastEntry.id + lastEntry.count === id) {
            if (lastEntry.offset === lastEntry.count) {
                lineageHolder.lineage[lineageHolder.lineage.length - 1] = {
                    ...lastEntry,
                    count: lastEntry.count + count,
                    offset: lastEntry.offset + offset,
                };
                return;
            }
            else if (offset === 0) {
                lineageHolder.lineage[lineageHolder.lineage.length - 1] = {
                    ...lastEntry,
                    count: lastEntry.count + count,
                };
                return;
            }
        }
    }
    lineageHolder.lineage.push({ revision, id, count, offset });
}
function tryRemoveLineageEvents(lineageHolder, revisionToRemove) {
    if (lineageHolder.lineage === undefined) {
        return;
    }
    lineageHolder.lineage = lineageHolder.lineage.filter((event) => event.revision !== revisionToRemove);
    if (lineageHolder.lineage.length === 0) {
        delete lineageHolder.lineage;
    }
}
function addIdRange(lineageEntries, range) {
    if (lineageEntries.length > 0) {
        const lastEntry = lineageEntries[lineageEntries.length - 1];
        if (lastEntry.id + lastEntry.count === range.id) {
            lastEntry.count += range.count;
            return;
        }
    }
    lineageEntries.push(range);
}
function getLineageHolder(mark) {
    (0, core_utils_1.assert)(mark.cellId !== undefined, 0x723 /* Attached cells cannot have lineage */);
    return mark.cellId;
}
function setMarkAdjacentCells(mark, adjacentCells) {
    (0, core_utils_1.assert)(mark.cellId !== undefined, 0x74d /* Can only set adjacent cells on a mark with cell ID */);
    (0, core_utils_1.assert)(mark.cellId.adjacentCells === undefined, 0x74e /* Should not overwrite adjacentCells */);
    mark.cellId.adjacentCells = adjacentCells;
}
/**
 * Returns a number N which encodes how the cells of the two marks are aligned.
 * - If N is zero, then the first cell of `baseMark` is the same as the first cell of `newMark`.
 * - If N is positive, then the first N cells of `newMark` (or all its cells if N is greater than its length)
 * are before the first cell of `baseMark`.
 * - If N is negative, then the first N cells of `baseMark` (or all its cells if N is greater than its length)
 * are before the first cell of `newMark`.
 */
function compareCellPositions(baseIntention, baseMark, newMark) {
    const baseId = (0, utils_1.getInputCellId)(baseMark, baseIntention);
    const baseLength = baseMark.count;
    (0, core_utils_1.assert)(baseId !== undefined, 0x6a0 /* baseMark should have cell ID */);
    const newId = (0, utils_1.getInputCellId)(newMark, undefined);
    const newLength = newMark.count;
    if (newId !== undefined && baseId.revision === newId.revision) {
        if ((0, utils_1.areOverlappingIdRanges)(baseId.localId, baseLength, newId.localId, newLength)) {
            return baseId.localId - newId.localId;
        }
        const adjacentCells = baseId.adjacentCells ?? newId.adjacentCells;
        if (adjacentCells !== undefined) {
            return (getPositionAmongAdjacentCells(adjacentCells, baseId.localId) -
                getPositionAmongAdjacentCells(adjacentCells, newId.localId));
        }
    }
    if (newId !== undefined) {
        const offset = (0, utils_1.getOffsetInCellRange)(baseId.lineage, newId.revision, newId.localId, newLength);
        if (offset !== undefined) {
            return offset > 0 ? offset : -Infinity;
        }
        const newOffset = (0, utils_1.getOffsetInCellRange)(newId.lineage, baseId.revision, baseId.localId, baseLength);
        if (newOffset !== undefined) {
            return newOffset > 0 ? -newOffset : Infinity;
        }
    }
    if (newId !== undefined) {
        const cmp = (0, utils_1.compareLineages)(baseId.lineage, newId.lineage);
        if (cmp !== 0) {
            return Math.sign(cmp) * Infinity;
        }
    }
    if ((0, utils_1.isNewAttach)(newMark)) {
        // When the marks are at the same position, we use the tiebreak of `newMark`.
        // TODO: Use specified tiebreak instead of always tiebreaking left.
        return Infinity;
    }
    (0, core_utils_1.assert)((0, utils_1.isNewAttach)(baseMark), 0x6a1 /* Lineage should determine order of marks unless one is a new attach */);
    // BUG 5351: The following assumption is incorrect as `newMark` may be targeting cells which were created on its branch,
    // which will come after `baseMark` in the final sequence order.
    // `newMark` points to cells which were emptied before `baseMark` was created.
    // We use `baseMark`'s tiebreak policy as if `newMark`'s cells were created concurrently and before `baseMark`.
    // TODO: Use specified tiebreak instead of always tiebreaking left.
    return -Infinity;
}
function getPositionAmongAdjacentCells(adjacentCells, id) {
    let priorCells = 0;
    for (const range of adjacentCells) {
        if ((0, utils_1.areOverlappingIdRanges)(range.id, range.count, id, 1)) {
            return priorCells + (id - range.id);
        }
        priorCells += range.count;
    }
    (0, util_1.fail)("Could not find id in adjacentCells");
}
//# sourceMappingURL=rebase.js.map