"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getModifyAfter = exports.applyMoveEffectsToMark = exports.getMoveIn = exports.isMoveIn = exports.isMoveOut = exports.isMoveMark = exports.getMoveEffect = exports.setMoveEffect = exports.MoveEnd = void 0;
const core_utils_1 = require("@fluidframework/core-utils");
const modular_schema_1 = require("../modular-schema");
const util_1 = require("../../util");
const utils_1 = require("./utils");
var MoveEnd;
(function (MoveEnd) {
    MoveEnd[MoveEnd["Source"] = 0] = "Source";
    MoveEnd[MoveEnd["Dest"] = 1] = "Dest";
})(MoveEnd || (exports.MoveEnd = MoveEnd = {}));
function setMoveEffect(moveEffects, target, revision, id, count, effect, invalidate = true) {
    effect.basis = id;
    moveEffects.set(target, revision, id, count, effect, invalidate);
}
exports.setMoveEffect = setMoveEffect;
function getMoveEffect(moveEffects, target, revision, id, count, addDependency = true) {
    const result = moveEffects.get(target, revision, id, count, addDependency);
    return result.value !== undefined
        ? { ...result, value: adjustMoveEffectBasis(result.value, id) }
        : result;
}
exports.getMoveEffect = getMoveEffect;
function isMoveMark(effect) {
    return isMoveOut(effect) || isMoveIn(effect);
}
exports.isMoveMark = isMoveMark;
function isMoveOut(effect) {
    return effect.type === "MoveOut";
}
exports.isMoveOut = isMoveOut;
function isMoveIn(effect) {
    return effect.type === "MoveIn";
}
exports.isMoveIn = isMoveIn;
function getMoveIn(effect) {
    switch (effect.type) {
        case "MoveIn":
            return effect;
        case "AttachAndDetach":
            return getMoveIn(effect.attach);
        default:
            return undefined;
    }
}
exports.getMoveIn = getMoveIn;
function adjustMoveEffectBasis(effect, newBasis) {
    if (effect.basis === newBasis) {
        return effect;
    }
    const adjusted = { ...effect, basis: newBasis };
    const basisShift = newBasis - effect.basis;
    (0, core_utils_1.assert)(basisShift > 0, 0x812 /* Expected basis shift to be positive */);
    if (effect.endpoint !== undefined) {
        adjusted.endpoint = {
            ...effect.endpoint,
            localId: (0, util_1.brand)(effect.endpoint.localId + basisShift),
        };
    }
    if (effect.movedMark !== undefined) {
        const [_mark1, mark2] = (0, utils_1.splitMark)(effect.movedMark, basisShift);
        adjusted.movedMark = mark2;
    }
    return adjusted;
}
function applyMoveEffectsToDest(markEffect, count, revision, effects, consumeEffects) {
    updateEndpoint(markEffect, count, modular_schema_1.CrossFieldTarget.Destination, revision, effects, consumeEffects);
}
function applyMoveEffectsToSource(mark, revision, effects, consumeEffect, composeChildren) {
    let nodeChange = mark.changes;
    const modifyAfter = getModifyAfter(effects, mark.revision ?? revision, mark.id, mark.count, consumeEffect);
    if (modifyAfter !== undefined) {
        (0, core_utils_1.assert)(composeChildren !== undefined, 0x569 /* Must provide a change composer if modifying moves */);
        nodeChange = composeChildren(mark.changes, modifyAfter);
    }
    const newMark = (0, utils_1.cloneMark)(mark);
    if (isMoveOut(newMark)) {
        applySourceEffects(newMark, mark.count, revision, effects, consumeEffect);
    }
    if (nodeChange !== undefined) {
        newMark.changes = nodeChange;
    }
    else {
        delete newMark.changes;
    }
    return newMark;
}
function applySourceEffects(markEffect, count, revision, effects, consumeEffects) {
    updateEndpoint(markEffect, count, modular_schema_1.CrossFieldTarget.Source, revision, effects, consumeEffects);
}
function updateEndpoint(markEffect, count, target, revision, effects, consumeEffects) {
    const markRevision = markEffect.revision ?? revision;
    const finalDest = getEndpoint(effects, target, markRevision, markEffect.id, count, consumeEffects);
    if (finalDest !== undefined) {
        if ((0, utils_1.areEqualCellIds)(finalDest, { revision: markRevision, localId: markEffect.id })) {
            delete markEffect.finalEndpoint;
        }
        else {
            markEffect.finalEndpoint = finalDest;
        }
    }
}
function applyMoveEffectsToMark(mark, revision, effects, consumeEffect, composeChildren) {
    if ((0, utils_1.isAttachAndDetachEffect)(mark)) {
        if (isMoveIn(mark.attach)) {
            if (isMoveOut(mark.detach)) {
                // Move effects should not be applied to intermediate move locations.
                return [mark];
            }
            const attachRevision = mark.attach.revision ?? revision;
            const effect = getMoveEffect(effects, modular_schema_1.CrossFieldTarget.Destination, attachRevision, mark.attach.id, mark.count);
            if (effect.length < mark.count) {
                const [firstMark, secondMark] = (0, utils_1.splitMark)(mark, effect.length);
                const updatedAttach = firstMark.attach;
                applyMoveEffectsToDest(updatedAttach, firstMark.count, attachRevision, effects, consumeEffect);
                return [
                    {
                        ...firstMark,
                        attach: updatedAttach,
                    },
                    ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                ];
            }
            else {
                const updatedAttach = { ...mark.attach };
                applyMoveEffectsToDest(updatedAttach, mark.count, attachRevision, effects, consumeEffect);
                return [
                    {
                        ...mark,
                        attach: updatedAttach,
                    },
                ];
            }
        }
        if (isMoveOut(mark.detach)) {
            const detachRevision = mark.detach.revision ?? revision;
            const effect = getMoveEffect(effects, modular_schema_1.CrossFieldTarget.Source, detachRevision, mark.detach.id, mark.count);
            if (effect.length < mark.count) {
                const [firstMark, secondMark] = (0, utils_1.splitMark)(mark, effect.length);
                applySourceEffects(firstMark.detach, firstMark.count, detachRevision, effects, consumeEffect);
                const newFirstChanges = getModifyAfter(effects, detachRevision, firstMark.detach.id, firstMark.count, consumeEffect);
                if (newFirstChanges !== undefined) {
                    (0, core_utils_1.assert)(composeChildren !== undefined, 0x813 /* Must provide a change composer if modifying moves */);
                    firstMark.changes = composeChildren(firstMark.changes, newFirstChanges);
                }
                return [
                    firstMark,
                    ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                ];
            }
            const newMark = (0, utils_1.cloneMark)(mark);
            applySourceEffects(newMark.detach, mark.count, detachRevision, effects, consumeEffect);
            const newChanges = getModifyAfter(effects, detachRevision, mark.detach.id, mark.count, consumeEffect);
            if (newChanges !== undefined) {
                (0, core_utils_1.assert)(composeChildren !== undefined, 0x814 /* Must provide a change composer if modifying moves */);
                newMark.changes = composeChildren(mark.changes, newChanges);
            }
            return [newMark];
        }
    }
    else if (isMoveMark(mark) || mark.type === "Placeholder") {
        const type = mark.type;
        switch (type) {
            case "Placeholder":
            case "MoveOut": {
                const effect = getMoveEffect(effects, modular_schema_1.CrossFieldTarget.Source, mark.revision ?? revision, mark.id, mark.count);
                if (effect.length < mark.count) {
                    const [firstMark, secondMark] = (0, utils_1.splitMark)(mark, effect.length);
                    return [
                        applyMoveEffectsToSource(firstMark, revision, effects, consumeEffect, composeChildren),
                        ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                    ];
                }
                return [
                    applyMoveEffectsToSource(mark, revision, effects, consumeEffect, composeChildren),
                ];
            }
            case "MoveIn": {
                const effect = getMoveEffect(effects, modular_schema_1.CrossFieldTarget.Destination, mark.revision ?? revision, mark.id, mark.count);
                if (effect.length < mark.count) {
                    const [firstMark, secondMark] = (0, utils_1.splitMark)(mark, effect.length);
                    applyMoveEffectsToDest(firstMark, firstMark.count, revision, effects, consumeEffect);
                    return [
                        firstMark,
                        ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                    ];
                }
                const newMark = (0, utils_1.cloneMark)(mark);
                applyMoveEffectsToDest(newMark, mark.count, revision, effects, consumeEffect);
                return [newMark];
            }
            default:
                (0, core_utils_1.unreachableCase)(type);
        }
    }
    return [mark];
}
exports.applyMoveEffectsToMark = applyMoveEffectsToMark;
// 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, count, consumeEffect = true) {
    const target = modular_schema_1.CrossFieldTarget.Source;
    const effect = getMoveEffect(moveEffects, target, revision, id, count);
    if (effect.value?.modifyAfter !== undefined) {
        (0, core_utils_1.assert)(effect.length === count, 0x6ee /* Expected effect to cover entire mark */);
        if (consumeEffect) {
            const newEffect = { ...effect.value };
            delete newEffect.modifyAfter;
            setMoveEffect(moveEffects, target, revision, id, count, newEffect, false);
        }
        return effect.value.modifyAfter;
    }
    return undefined;
}
exports.getModifyAfter = getModifyAfter;
function getEndpoint(moveEffects, target, revision, id, count, consumeEffect = true) {
    const effect = getMoveEffect(moveEffects, target, revision, id, count);
    (0, core_utils_1.assert)(effect.length === count, 0x815 /* Expected effect to cover entire mark */);
    if (effect.value?.endpoint === undefined) {
        return undefined;
    }
    if (consumeEffect) {
        const newEffect = { ...effect.value };
        delete newEffect.endpoint;
        setMoveEffect(moveEffects, target, revision, id, count, newEffect, false);
    }
    return effect.value.endpoint;
}
//# sourceMappingURL=moveEffectTable.js.map