"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComposeQueue = exports.compose = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../modular-schema/index.js");
const markListFactory_js_1 = require("./markListFactory.js");
const markQueue_js_1 = require("./markQueue.js");
const moveEffectTable_js_1 = require("./moveEffectTable.js");
const types_js_1 = require("./types.js");
const utils_js_1 = require("./utils.js");
/**
 * Composes a sequence of changesets into a single changeset.
 * @param changes - The changesets to be applied.
 * Parts of the input may be reused in the output, but the input is not mutated.
 * Each changeset in the list is assumed to be applicable after the previous one.
 * @returns A changeset that is equivalent to applying each of the given `changes` in order.
 *
 * WARNING! This implementation is incomplete:
 * - Tombstone information is ignored.
 * - Support for moves is not implemented.
 * - Support for slices is not implemented.
 */
function compose(change1, change2, composeChild, _genId, manager, revisionMetadata) {
    return composeMarkLists(change1, change2, composeChild, manager, revisionMetadata);
}
exports.compose = compose;
function composeMarkLists(baseMarkList, newMarkList, composeChild, moveEffects, revisionMetadata) {
    const factory = new markListFactory_js_1.MarkListFactory();
    const queue = new ComposeQueue(baseMarkList, newMarkList, moveEffects, revisionMetadata);
    while (!queue.isEmpty()) {
        const { baseMark, newMark } = queue.pop();
        if (newMark === undefined) {
            (0, internal_1.assert)(baseMark !== undefined, 0x4db /* Non-empty queue should not return two undefined marks */);
            factory.push(composeMark(baseMark, moveEffects, (node) => composeChildChanges(node, undefined, composeChild)));
        }
        else {
            // We only compose changesets that will not be further rebased.
            // It is therefore safe to remove any intentions that have no impact in the context they apply to.
            const settledNewMark = (0, utils_js_1.settleMark)(newMark);
            if (baseMark === undefined) {
                factory.push(composeMark(settledNewMark, moveEffects, (node) => composeChildChanges(undefined, node, composeChild)));
            }
            else {
                // Past this point, we are guaranteed that `settledNewMark` and `baseMark` have the same length and
                // start at the same location in the revision after the base changes.
                // They therefore refer to the same range for that revision.
                const settledBaseMark = (0, utils_js_1.settleMark)(baseMark);
                const composedMark = composeMarks(settledBaseMark, settledNewMark, composeChild, moveEffects);
                factory.push(composedMark);
            }
        }
    }
    return factory.list;
}
/**
 * Composes two marks where `newMark` is based on the state produced by `baseMark`.
 * @param baseMark - The mark to compose with `newMark`.
 * Its output range should be the same as `newMark`'s input range.
 * @param newRev - The revision the new mark is part of.
 * @param newMark - The mark to compose with `baseMark`.
 * Its input range should be the same as `baseMark`'s output range.
 * @returns A mark that is equivalent to applying both `baseMark` and `newMark` successively.
 */
function composeMarks(baseMark, newMark, composeChild, moveEffects) {
    const nodeChange = handleNodeChanges(baseMark, newMark, composeChild, moveEffects);
    return withUpdatedEndpoint((0, utils_js_1.withNodeChange)(composeMarksIgnoreChild(baseMark, newMark, moveEffects), nodeChange), baseMark.count, moveEffects);
}
function composeMarksIgnoreChild(baseMark, newMark, moveEffects) {
    if ((0, utils_js_1.isNoopMark)(baseMark)) {
        return newMark;
    }
    else if ((0, utils_js_1.isNoopMark)(newMark)) {
        return baseMark;
    }
    if ((0, utils_js_1.isRename)(baseMark) && (0, utils_js_1.isRename)(newMark)) {
        return { ...baseMark, idOverride: newMark.idOverride };
    }
    else if ((0, utils_js_1.isRename)(baseMark)) {
        (0, internal_1.assert)((0, utils_js_1.isAttach)(newMark) || (0, utils_js_1.isAttachAndDetachEffect)(newMark), 0x9f1 /* Unexpected mark type */);
        return { ...newMark, cellId: baseMark.cellId };
    }
    else if ((0, utils_js_1.isRename)(newMark)) {
        (0, internal_1.assert)((0, utils_js_1.isDetach)(baseMark) || (0, utils_js_1.isAttachAndDetachEffect)(baseMark), 0x9f2 /* Unexpected mark type */);
        return (0, utils_js_1.isDetach)(baseMark)
            ? { ...baseMark, idOverride: newMark.idOverride }
            : { ...baseMark, detach: { ...baseMark.detach, idOverride: newMark.idOverride } };
    }
    if ((0, utils_js_1.isImpactfulCellRename)(newMark)) {
        const newAttachAndDetach = (0, utils_js_1.asAttachAndDetach)(newMark);
        (0, internal_1.assert)(newAttachAndDetach.cellId !== undefined, 0x9f3 /* Impactful cell rename must target empty cell */);
        const newDetachRevision = newAttachAndDetach.detach.revision;
        if ((0, utils_js_1.markEmptiesCells)(baseMark)) {
            // baseMark is a detach which cancels with the attach portion of the AttachAndDetach,
            // so we are just left with the detach portion of the AttachAndDetach.
            const newDetach = {
                ...newAttachAndDetach.detach,
                count: baseMark.count,
            };
            if ((0, moveEffectTable_js_1.isMoveIn)(newAttachAndDetach.attach) && (0, moveEffectTable_js_1.isMoveOut)(newAttachAndDetach.detach)) {
                (0, internal_1.assert)((0, moveEffectTable_js_1.isMoveOut)(baseMark), 0x808 /* Unexpected mark type */);
                // The base changeset and new changeset both move these nodes.
                // Call the original position of the nodes A, the position after the base changeset is applied B,
                // and the position after the new changeset is applied C.
                // The new changeset moves the nodes from B, temporarily returns them to A, and then moves them to C.
                // The composition of the base and new changesets will be a move directly from A to C,
                // since the move from A to B cancels out with the return from B to A.
                // This if-block is handling marks at A.
                // When we compose the marks at B we will link the start of the base move (A to B)
                // with the end of the new move (B to C).
                // Because we are replacing the mark representing the start of the move with the new changeset's
                // move-out from A, we update the base move-in at B to consider that its start point.
                const newDetachId = {
                    revision: newDetachRevision,
                    localId: newAttachAndDetach.detach.id,
                };
                setTruncatedEndpointForInner(moveEffects, index_js_2.CrossFieldTarget.Destination, (0, utils_js_1.getEndpoint)(baseMark), baseMark.count, newDetachId);
                const newEndpoint = getComposedEndpoint(moveEffects, index_js_2.CrossFieldTarget.Source, baseMark.revision, baseMark.id, baseMark.count);
                if (newEndpoint !== undefined) {
                    changeFinalEndpoint(newDetach, newEndpoint);
                    setTruncatedEndpoint(moveEffects, index_js_2.CrossFieldTarget.Destination, newEndpoint, baseMark.count, newDetachId);
                }
            }
            return newDetach;
        }
        if ((0, utils_js_1.isImpactfulCellRename)(baseMark)) {
            (0, internal_1.assert)(baseMark.cellId !== undefined, 0x9f4 /* Impactful cell rename must target empty cell */);
            const baseAttachAndDetach = (0, utils_js_1.asAttachAndDetach)(baseMark);
            const newOutputId = (0, utils_js_1.getOutputCellId)(newAttachAndDetach);
            const originalAttach = { ...baseAttachAndDetach.attach };
            const finalDetach = { ...newAttachAndDetach.detach };
            handleMovePivot(baseMark.count, originalAttach, finalDetach, moveEffects);
            if ((0, utils_js_1.areEqualCellIds)(newOutputId, baseAttachAndDetach.cellId)) {
                return { count: baseAttachAndDetach.count, cellId: baseAttachAndDetach.cellId };
            }
            // `newMark`'s attach portion cancels with `baseMark`'s detach portion.
            const detachRevision = finalDetach.revision;
            if (detachRevision !== undefined) {
                finalDetach.revision = detachRevision;
            }
            return (0, utils_js_1.normalizeCellRename)(baseMark.cellId, baseMark.count, originalAttach, finalDetach);
        }
        return (0, utils_js_1.normalizeCellRename)(newAttachAndDetach.cellId, newAttachAndDetach.count, newAttachAndDetach.attach, newAttachAndDetach.detach);
    }
    if ((0, utils_js_1.isImpactfulCellRename)(baseMark)) {
        const baseAttachAndDetach = (0, utils_js_1.asAttachAndDetach)(baseMark);
        if ((0, utils_js_1.markFillsCells)(newMark)) {
            const originalAttach = (0, utils_js_1.withRevision)({
                ...baseAttachAndDetach.attach,
                cellId: baseAttachAndDetach.cellId,
                count: baseAttachAndDetach.count,
            }, baseAttachAndDetach.attach.revision);
            if ((0, moveEffectTable_js_1.isMoveIn)(baseAttachAndDetach.attach) && (0, moveEffectTable_js_1.isMoveOut)(baseAttachAndDetach.detach)) {
                (0, internal_1.assert)((0, moveEffectTable_js_1.isMoveIn)(newMark), 0x809 /* Unexpected mark type */);
                const originalAttachId = {
                    revision: baseAttachAndDetach.attach.revision,
                    localId: baseAttachAndDetach.attach.id,
                };
                setTruncatedEndpointForInner(moveEffects, index_js_2.CrossFieldTarget.Source, (0, utils_js_1.getEndpoint)(newMark), baseAttachAndDetach.count, originalAttachId);
                const newEndpoint = getComposedEndpoint(moveEffects, index_js_2.CrossFieldTarget.Destination, newMark.revision, newMark.id, newMark.count);
                if (newEndpoint !== undefined) {
                    changeFinalEndpoint(originalAttach, newEndpoint);
                    setTruncatedEndpoint(moveEffects, index_js_2.CrossFieldTarget.Source, newEndpoint, baseMark.count, originalAttachId);
                }
            }
            return originalAttach;
        }
        else {
            // Other mark types have been handled by previous conditional branches.
            (0, internal_1.assert)(newMark.type === types_js_1.NoopMarkType, 0x80a /* Unexpected mark type */);
            return baseMark;
        }
    }
    if (!(0, utils_js_1.markHasCellEffect)(baseMark) && !(0, utils_js_1.markHasCellEffect)(newMark)) {
        return createNoopMark(newMark.count, undefined, (0, utils_js_1.getInputCellId)(baseMark));
    }
    else if (!(0, utils_js_1.markHasCellEffect)(baseMark)) {
        return newMark;
    }
    else if (!(0, utils_js_1.markHasCellEffect)(newMark)) {
        return baseMark;
    }
    else if ((0, utils_js_1.areInputCellsEmpty)(baseMark)) {
        (0, internal_1.assert)((0, utils_js_1.isDetach)(newMark), 0x71c /* Unexpected mark type */);
        (0, internal_1.assert)((0, utils_js_1.isAttach)(baseMark), 0x71d /* Expected generative mark */);
        const attach = (0, utils_js_1.extractMarkEffect)(baseMark);
        const detach = (0, utils_js_1.extractMarkEffect)(newMark);
        handleMovePivot(baseMark.count, attach, detach, moveEffects);
        if ((0, utils_js_1.areEqualCellIds)((0, utils_js_1.getOutputCellId)(newMark), baseMark.cellId)) {
            // The output and input cell IDs are the same, so this mark has no effect.
            return { count: baseMark.count, cellId: baseMark.cellId };
        }
        return (0, utils_js_1.normalizeCellRename)(baseMark.cellId, baseMark.count, attach, detach);
    }
    else {
        const length = baseMark.count;
        return createNoopMark(length, undefined);
    }
}
/**
 * Checks if `baseAttach` and `newDetach` are both moves, and if so updates their move endpoints as appropriate,
 * and removes their `finalEndpoint` endpoint fields. Note that can mutate `baseAttach` and `newDetach`.
 * If the effects are not both moves this function does nothing.
 * @param count - The number of cells targeted
 * @param baseAttach - The base attach effect at this location
 * @param newDetach - The new detach effect at this location
 */
function handleMovePivot(count, baseAttach, newDetach, moveEffects) {
    if ((0, moveEffectTable_js_1.isMoveIn)(baseAttach) && (0, moveEffectTable_js_1.isMoveOut)(newDetach)) {
        const finalSource = (0, utils_js_1.getEndpoint)(baseAttach);
        const finalDest = (0, utils_js_1.getEndpoint)(newDetach);
        setEndpoint(moveEffects, index_js_2.CrossFieldTarget.Source, finalSource, count, finalDest);
        const truncatedEndpoint1 = getTruncatedEndpointForInner(moveEffects, index_js_2.CrossFieldTarget.Destination, baseAttach.revision, baseAttach.id, count);
        if (truncatedEndpoint1 !== undefined) {
            setTruncatedEndpoint(moveEffects, index_js_2.CrossFieldTarget.Destination, finalDest, count, truncatedEndpoint1);
        }
        setEndpoint(moveEffects, index_js_2.CrossFieldTarget.Destination, finalDest, count, finalSource);
        const truncatedEndpoint2 = getTruncatedEndpointForInner(moveEffects, index_js_2.CrossFieldTarget.Source, newDetach.revision, newDetach.id, count);
        if (truncatedEndpoint2 !== undefined) {
            setTruncatedEndpoint(moveEffects, index_js_2.CrossFieldTarget.Source, finalSource, count, truncatedEndpoint2);
        }
        // The `finalEndpoint` field of AttachAndDetach move effect pairs is not used,
        // so we remove it as a normalization.
        delete baseAttach.finalEndpoint;
        delete newDetach.finalEndpoint;
    }
}
function createNoopMark(length, nodeChange, cellId) {
    const mark = { count: length };
    if (nodeChange !== undefined) {
        (0, internal_1.assert)(length === 1, 0x692 /* A mark with a node change must have length one */);
        mark.changes = nodeChange;
    }
    if (cellId !== undefined) {
        mark.cellId = cellId;
    }
    return mark;
}
function handleNodeChanges(baseMark, newMark, composeChild, moveEffects) {
    if (newMark.changes !== undefined) {
        const baseSource = (0, moveEffectTable_js_1.getMoveIn)(baseMark);
        // TODO: Make sure composeChild is not called twice on the node changes.
        if (baseSource !== undefined) {
            setModifyAfter(moveEffects, (0, utils_js_1.getEndpoint)(baseSource), newMark.changes);
            return undefined;
        }
    }
    return composeChildChanges(baseMark.changes, newMark.changes, composeChild);
}
function composeChildChanges(baseChange, newChange, composeChild) {
    if (baseChange === undefined && newChange === undefined) {
        return undefined;
    }
    return composeChild(baseChange, newChange);
}
function composeMark(mark, moveEffects, composeChild) {
    const nodeChanges = mark.changes !== undefined ? composeChild(mark.changes) : undefined;
    const updatedMark = withUpdatedEndpoint(mark, mark.count, moveEffects);
    return (0, utils_js_1.withNodeChange)(updatedMark, nodeChanges);
}
class ComposeQueue {
    constructor(baseMarks, newMarks, moveEffects, revisionMetadata) {
        this.moveEffects = moveEffects;
        this.revisionMetadata = revisionMetadata;
        this.baseMarks = new markQueue_js_1.MarkQueue(baseMarks, moveEffects);
        this.newMarks = new markQueue_js_1.MarkQueue(newMarks, moveEffects);
        this.baseMarksCellSources = (0, utils_js_1.cellSourcesFromMarks)(baseMarks, utils_js_1.getOutputCellId);
        this.newMarksCellSources = (0, utils_js_1.cellSourcesFromMarks)(newMarks, utils_js_1.getInputCellId);
    }
    isEmpty() {
        return this.baseMarks.isEmpty() && this.newMarks.isEmpty();
    }
    pop() {
        const baseMark = this.baseMarks.peek();
        const newMark = this.newMarks.peek();
        if (baseMark === undefined && newMark === undefined) {
            return {};
        }
        else if (baseMark === undefined) {
            return this.dequeueNew();
        }
        else if (newMark === undefined) {
            return this.dequeueBase();
        }
        else if ((0, utils_js_1.areOutputCellsEmpty)(baseMark) && (0, utils_js_1.areInputCellsEmpty)(newMark)) {
            const baseCellId = (0, utils_js_1.getOutputCellId)(baseMark) ?? (0, internal_1.fail)(0xb29 /* Expected defined output ID */);
            if ((0, utils_js_1.markEmptiesCells)(baseMark) && baseCellId.revision === undefined) {
                // The base revision should always be defined except when squashing changes into a transaction.
                // In the future, we want to support reattaches in the new change here.
                // We will need to be able to order the base mark relative to the new mark
                // (which requires the local changes to have a revision tag))
                (0, internal_1.assert)((0, utils_js_1.isNewAttach)(newMark), 0x695 /* TODO: Assign revision tags to each change in a transaction */);
                return this.dequeueNew();
            }
            const newCellId = (0, utils_js_1.getInputCellId)(newMark);
            (0, internal_1.assert)(newCellId !== undefined, 0x89d /* Both marks should have cell IDs */);
            const comparison = (0, utils_js_1.compareCellPositionsUsingTombstones)(baseCellId, newCellId, this.baseMarksCellSources, this.newMarksCellSources, this.revisionMetadata);
            switch (comparison) {
                case utils_js_1.CellOrder.SameCell:
                    return this.dequeueBoth();
                case utils_js_1.CellOrder.OldThenNew:
                    return this.dequeueBase();
                case utils_js_1.CellOrder.NewThenOld:
                    return this.dequeueNew();
                default:
                    (0, internal_1.unreachableCase)(comparison);
            }
        }
        else if ((0, utils_js_1.areOutputCellsEmpty)(baseMark)) {
            return this.dequeueBase();
        }
        else if ((0, utils_js_1.areInputCellsEmpty)(newMark)) {
            return this.dequeueNew();
        }
        else {
            return this.dequeueBoth();
        }
    }
    dequeueBase(length = Number.POSITIVE_INFINITY) {
        const baseMark = this.baseMarks.dequeueUpTo(length);
        const movedChanges = getMovedChangesFromMark(this.moveEffects, baseMark);
        if (movedChanges !== undefined) {
            this.moveEffects.onMoveIn(movedChanges);
        }
        const newMark = createNoopMark(baseMark.count, movedChanges, (0, utils_js_1.getOutputCellId)(baseMark));
        return { baseMark, newMark };
    }
    dequeueNew(length = Number.POSITIVE_INFINITY) {
        const newMark = this.newMarks.dequeueUpTo(length);
        const baseMark = createNoopMark(newMark.count, undefined, (0, utils_js_1.getInputCellId)(newMark));
        return {
            baseMark,
            newMark,
        };
    }
    dequeueBoth() {
        const length = this.peekMinLength();
        const baseMark = this.baseMarks.dequeueUpTo(length);
        let newMark = this.newMarks.dequeueUpTo(length);
        const movedChanges = getMovedChangesFromMark(this.moveEffects, baseMark);
        if (movedChanges !== undefined) {
            (0, internal_1.assert)(newMark.changes === undefined, 0x8da /* Unexpected node changeset collision */);
            newMark = (0, utils_js_1.withNodeChange)(newMark, movedChanges);
        }
        return {
            baseMark,
            newMark,
        };
    }
    peekMinLength() {
        const baseMark = this.baseMarks.peek();
        const newMark = this.newMarks.peek();
        (0, internal_1.assert)(baseMark !== undefined && newMark !== undefined, 0x8db /* Cannot peek length unless both mark queues are non-empty */);
        return Math.min(newMark.count, baseMark.count);
    }
}
exports.ComposeQueue = ComposeQueue;
function getMovedChangesFromMark(moveEffects, markEffect) {
    if ((0, utils_js_1.isAttachAndDetachEffect)(markEffect)) {
        return getMovedChangesFromMark(moveEffects, markEffect.detach);
    }
    if (!(0, moveEffectTable_js_1.isMoveOut)(markEffect)) {
        return undefined;
    }
    return getModifyAfter(moveEffects, markEffect.revision, markEffect.id);
}
// 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 getModifyAfter(moveEffects, revision, id) {
    const target = index_js_2.CrossFieldTarget.Source;
    const effect = (0, moveEffectTable_js_1.getMoveEffect)(moveEffects, target, revision, id, 1);
    if (effect.value?.modifyAfter !== undefined) {
        return effect.value.modifyAfter;
    }
    return undefined;
}
// TODO: Reduce the duplication between this and other MoveEffect helpers
function setModifyAfter(moveEffects, { revision, localId: id }, modifyAfter) {
    const target = index_js_2.CrossFieldTarget.Source;
    const count = 1;
    const effect = (0, moveEffectTable_js_1.getMoveEffect)(moveEffects, target, revision, id, count, false);
    const newEffect = effect.value !== undefined ? { ...effect.value, modifyAfter } : { modifyAfter };
    (0, moveEffectTable_js_1.setMoveEffect)(moveEffects, target, revision, id, count, newEffect);
}
function setEndpoint(moveEffects, target, id, count, endpoint) {
    const effect = (0, moveEffectTable_js_1.getMoveEffect)(moveEffects, target, id.revision, id.localId, count, false);
    const newEffect = effect.value !== undefined ? { ...effect.value, endpoint } : { endpoint };
    (0, moveEffectTable_js_1.setMoveEffect)(moveEffects, target, id.revision, id.localId, effect.length, newEffect);
    const remainingCount = count - effect.length;
    if (remainingCount > 0) {
        setEndpoint(moveEffects, target, (0, index_js_1.offsetChangeAtomId)(id, effect.length), remainingCount, (0, index_js_1.offsetChangeAtomId)(endpoint, effect.length));
    }
}
function setTruncatedEndpoint(moveEffects, target, id, count, truncatedEndpoint) {
    const effect = (0, moveEffectTable_js_1.getMoveEffect)(moveEffects, target, id.revision, id.localId, count);
    const newEffect = effect.value !== undefined
        ? { ...effect.value, truncatedEndpoint }
        : { truncatedEndpoint };
    (0, moveEffectTable_js_1.setMoveEffect)(moveEffects, target, id.revision, id.localId, effect.length, newEffect);
    const remainingCount = count - effect.length;
    if (remainingCount > 0) {
        setTruncatedEndpoint(moveEffects, target, (0, index_js_1.offsetChangeAtomId)(id, effect.length), remainingCount, (0, index_js_1.offsetChangeAtomId)(truncatedEndpoint, effect.length));
    }
}
function setTruncatedEndpointForInner(moveEffects, target, id, count, truncatedEndpointForInner) {
    const effect = (0, moveEffectTable_js_1.getMoveEffect)(moveEffects, target, id.revision, id.localId, count);
    const newEffect = effect.value !== undefined
        ? { ...effect.value, truncatedEndpointForInner }
        : { truncatedEndpointForInner };
    (0, moveEffectTable_js_1.setMoveEffect)(moveEffects, target, id.revision, id.localId, effect.length, newEffect);
    const remainingCount = count - effect.length;
    if (remainingCount > 0) {
        setTruncatedEndpointForInner(moveEffects, target, (0, index_js_1.offsetChangeAtomId)(id, effect.length), remainingCount, (0, index_js_1.offsetChangeAtomId)(truncatedEndpointForInner, effect.length));
    }
}
function withUpdatedEndpoint(mark, count, effects) {
    if ((0, utils_js_1.isAttachAndDetachEffect)(mark)) {
        return {
            ...mark,
            attach: withUpdatedEndpoint(mark.attach, count, effects),
            detach: withUpdatedEndpoint(mark.detach, count, effects),
        };
    }
    if (!(0, moveEffectTable_js_1.isMoveMark)(mark)) {
        return mark;
    }
    const finalDest = getComposedEndpoint(effects, (0, moveEffectTable_js_1.getCrossFieldTargetFromMove)(mark), mark.revision, mark.id, count);
    if (finalDest === undefined) {
        return mark;
    }
    const output = { ...mark };
    changeFinalEndpoint(output, finalDest);
    return output;
}
function changeFinalEndpoint(mark, endpoint) {
    if ((0, utils_js_1.areEqualCellIds)(endpoint, { revision: mark.revision, localId: mark.id })) {
        delete mark.finalEndpoint;
    }
    else {
        mark.finalEndpoint = endpoint;
    }
}
function getComposedEndpoint(moveEffects, target, revision, id, count) {
    const effect = (0, moveEffectTable_js_1.getMoveEffect)(moveEffects, target, revision, id, count);
    (0, internal_1.assert)(effect.length === count, 0x815 /* Expected effect to cover entire mark */);
    return effect.value?.truncatedEndpoint ?? effect.value?.endpoint;
}
function getTruncatedEndpointForInner(moveEffects, target, revision, id, count) {
    const effect = (0, moveEffectTable_js_1.getMoveEffect)(moveEffects, target, revision, id, count);
    (0, internal_1.assert)(effect.length === count, 0x934 /* Expected effect to cover entire mark */);
    return effect.value?.truncatedEndpointForInner;
}
//# sourceMappingURL=compose.js.map