/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, unreachableCase } from "@fluidframework/core-utils";
import { CrossFieldTarget } from "../modular-schema";
import { cloneMark, splitMark } from "./utils";
export var MoveEnd;
(function (MoveEnd) {
    MoveEnd[MoveEnd["Source"] = 0] = "Source";
    MoveEnd[MoveEnd["Dest"] = 1] = "Dest";
})(MoveEnd || (MoveEnd = {}));
export var PairedMarkUpdate;
(function (PairedMarkUpdate) {
    /**
     * Indicates that the mark's matching mark is now inactive.
     */
    PairedMarkUpdate[PairedMarkUpdate["Deactivated"] = 0] = "Deactivated";
    /**
     * Indicates that the mark's matching mark is now active.
     */
    PairedMarkUpdate[PairedMarkUpdate["Reactivated"] = 1] = "Reactivated";
})(PairedMarkUpdate || (PairedMarkUpdate = {}));
export function setMoveEffect(moveEffects, target, revision, id, count, effect, invalidate = true) {
    moveEffects.set(target, revision, id, count, effect, invalidate);
}
export function getMoveEffect(moveEffects, target, revision, id, count, addDependency = true) {
    return moveEffects.get(target, revision, id, count, addDependency);
}
export function isMoveMark(mark) {
    switch (mark.type) {
        case "MoveIn":
        case "MoveOut":
        case "ReturnFrom":
        case "ReturnTo":
            return true;
        default:
            return false;
    }
}
function applyMoveEffectsToDest(mark, revision, effects, consumeEffect) {
    const newMark = {
        ...mark,
    };
    const statusUpdate = getPairedMarkStatus(effects, CrossFieldTarget.Destination, mark.revision ?? revision, mark.id, mark.count, consumeEffect);
    if (statusUpdate === PairedMarkUpdate.Deactivated) {
        newMark.isSrcConflicted = true;
    }
    else if (statusUpdate === PairedMarkUpdate.Reactivated) {
        delete newMark.isSrcConflicted;
    }
    return newMark;
}
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) {
        assert(composeChildren !== undefined, 0x569 /* Must provide a change composer if modifying moves */);
        nodeChange = composeChildren(mark.changes, modifyAfter);
    }
    const newMark = cloneMark(mark);
    if (nodeChange !== undefined) {
        newMark.changes = nodeChange;
    }
    else {
        delete newMark.changes;
    }
    const statusUpdate = getPairedMarkStatus(effects, CrossFieldTarget.Source, mark.revision ?? revision, mark.id, mark.count, consumeEffect);
    if (statusUpdate !== undefined) {
        assert(newMark.type === "ReturnFrom", 0x56a /* TODO: support updating MoveOut.isSrcConflicted */);
        if (statusUpdate === PairedMarkUpdate.Deactivated) {
            newMark.isDstConflicted = true;
        }
        else {
            delete newMark.isDstConflicted;
        }
    }
    return newMark;
}
export function applyMoveEffectsToMark(mark, revision, effects, consumeEffect, composeChildren) {
    if (isMoveMark(mark)) {
        const type = mark.type;
        switch (type) {
            case "MoveOut":
            case "ReturnFrom": {
                const effect = getMoveEffect(effects, CrossFieldTarget.Source, mark.revision ?? revision, mark.id, mark.count);
                if (effect === undefined) {
                    return [mark];
                }
                if (effect.start > mark.id) {
                    const [firstMark, secondMark] = splitMark(mark, effect.start - mark.id);
                    return [
                        firstMark,
                        ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                    ];
                }
                const lastEffectId = effect.start + effect.length - 1;
                const lastMarkId = mark.id + mark.count - 1;
                if (lastEffectId < lastMarkId) {
                    const [firstMark, secondMark] = splitMark(mark, lastEffectId - mark.id + 1);
                    return [
                        applyMoveEffectsToSource(firstMark, revision, effects, consumeEffect, composeChildren),
                        ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                    ];
                }
                return [
                    applyMoveEffectsToSource(mark, revision, effects, consumeEffect, composeChildren),
                ];
            }
            case "MoveIn":
            case "ReturnTo": {
                const effect = getMoveEffect(effects, CrossFieldTarget.Destination, mark.revision ?? revision, mark.id, mark.count);
                if (effect === undefined) {
                    return [mark];
                }
                if (effect.start > mark.id) {
                    const [firstMark, secondMark] = splitMark(mark, effect.start - mark.id);
                    return [
                        firstMark,
                        ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                    ];
                }
                const lastEffectId = effect.start + effect.length - 1;
                const lastMarkId = mark.id + mark.count - 1;
                if (lastEffectId < lastMarkId) {
                    const [firstMark, secondMark] = splitMark(mark, lastEffectId - mark.id + 1);
                    return [
                        applyMoveEffectsToDest(firstMark, revision, effects, consumeEffect),
                        ...applyMoveEffectsToMark(secondMark, revision, effects, consumeEffect, composeChildren),
                    ];
                }
                return [applyMoveEffectsToDest(mark, revision, effects, consumeEffect)];
            }
            default:
                unreachableCase(type);
        }
    }
    return [mark];
}
// 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
export function getModifyAfter(moveEffects, revision, id, count, consumeEffect = true) {
    const target = CrossFieldTarget.Source;
    const effect = getMoveEffect(moveEffects, target, revision, id, count);
    if (effect?.value.modifyAfter !== undefined) {
        assert(effect.start <= id && effect.start + effect.length >= id + 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;
}
// 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 getPairedMarkStatus(moveEffects, target, revision, id, count, consumeEffect = true) {
    const effect = getMoveEffect(moveEffects, target, revision, id, count);
    if (effect?.value.pairedMarkStatus !== undefined) {
        assert(effect.start <= id && effect.start + effect.length >= id + count, 0x6ef /* Expected effect to cover entire mark */);
        if (consumeEffect) {
            const newEffect = { ...effect.value };
            delete newEffect.pairedMarkStatus;
            setMoveEffect(moveEffects, target, revision, id, count, newEffect, false);
        }
        return effect.value.pairedMarkStatus;
    }
    return undefined;
}
//# sourceMappingURL=moveEffectTable.js.map