"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.amendInvert = exports.invert = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const util_1 = require("../../util");
const modular_schema_1 = require("../modular-schema");
const markListFactory_1 = require("./markListFactory");
const utils_1 = require("./utils");
/**
 * Inverts a given changeset.
 * @param change - The changeset to produce the inverse of.
 * @returns The inverse of the given `change` such that the inverse can be applied after `change`.
 *
 * WARNING! This implementation is incomplete:
 * - Support for slices is not implemented.
 */
function invert(change, invertChild, reviver, genId, crossFieldManager) {
    return invertMarkList(change.change, change.revision, reviver, invertChild, crossFieldManager);
}
exports.invert = invert;
function amendInvert(invertedChange, originalRevision, reviver, genId, crossFieldManager) {
    transferMovedChanges(invertedChange, originalRevision, crossFieldManager);
    return invertedChange;
}
exports.amendInvert = amendInvert;
function invertMarkList(markList, revision, reviver, invertChild, crossFieldManager) {
    const inverseMarkList = new markListFactory_1.MarkListFactory();
    let inputIndex = 0;
    for (const mark of markList) {
        const inverseMarks = invertMark(mark, inputIndex, revision, reviver, invertChild, crossFieldManager);
        inverseMarkList.push(...inverseMarks);
        inputIndex += (0, utils_1.getInputLength)(mark);
    }
    return inverseMarkList.list;
}
function invertMark(mark, inputIndex, revision, reviver, invertChild, crossFieldManager) {
    var _a, _b, _c, _d, _e;
    if ((0, utils_1.isSkipMark)(mark)) {
        return [mark];
    }
    else {
        switch (mark.type) {
            case "Insert": {
                return [
                    {
                        type: "Delete",
                        count: mark.content.length,
                    },
                ];
            }
            case "Delete": {
                (0, common_utils_1.assert)(revision !== undefined, 0x5a1 /* Unable to revert to undefined revision */);
                return [
                    {
                        type: "Revive",
                        detachedBy: (_a = mark.revision) !== null && _a !== void 0 ? _a : revision,
                        detachIndex: inputIndex,
                        content: reviver(revision, inputIndex, mark.count),
                        count: mark.count,
                    },
                ];
            }
            case "Revive": {
                if (!(0, utils_1.isConflicted)(mark)) {
                    return [
                        {
                            type: "Delete",
                            count: mark.count,
                        },
                    ];
                }
                if (mark.lastDetachedBy === undefined) {
                    // The nodes were already revived, so the revive mark did not affect them.
                    return [invertModifyOrSkip(mark.count, mark.changes, inputIndex, invertChild)];
                }
                // The nodes were not revived and could not be revived.
                return [];
            }
            case "Modify": {
                return [
                    {
                        type: "Modify",
                        changes: invertChild(mark.changes, inputIndex),
                    },
                ];
            }
            case "MoveOut":
            case "ReturnFrom": {
                if ((0, utils_1.isConflicted)(mark)) {
                    (0, common_utils_1.assert)(mark.changes === undefined, 0x4e1 /* Nested changes should have been moved to the destination of the move/return that detached them */);
                    // The nodes were already detached so the mark had no effect
                    return [];
                }
                if (mark.isDstConflicted) {
                    // The nodes were present but the destination was conflicted, the mark had no effect on the nodes.
                    return [invertModifyOrSkip(mark.count, mark.changes, inputIndex, invertChild)];
                }
                if (mark.changes !== undefined) {
                    crossFieldManager.getOrCreate(modular_schema_1.CrossFieldTarget.Destination, (_b = mark.revision) !== null && _b !== void 0 ? _b : revision, mark.id, invertChild(mark.changes, inputIndex), true);
                }
                return [
                    {
                        type: "ReturnTo",
                        id: mark.id,
                        count: mark.count,
                        detachedBy: (_c = mark.revision) !== null && _c !== void 0 ? _c : revision,
                        detachIndex: inputIndex,
                    },
                ];
            }
            case "MoveIn":
            case "ReturnTo": {
                if (!(0, utils_1.isConflicted)(mark)) {
                    if (mark.isSrcConflicted) {
                        // The nodes could have been attached but were not because of the source.
                        return [];
                    }
                    const invertedMark = {
                        type: "ReturnFrom",
                        id: mark.id,
                        count: mark.count,
                        detachedBy: (_d = mark.revision) !== null && _d !== void 0 ? _d : revision,
                    };
                    const movedChanges = crossFieldManager.get(modular_schema_1.CrossFieldTarget.Destination, (_e = mark.revision) !== null && _e !== void 0 ? _e : revision, mark.id, true);
                    if (movedChanges !== undefined) {
                        invertedMark.changes = movedChanges;
                    }
                    return [invertedMark];
                }
                if (mark.type === "ReturnTo" && mark.lastDetachedBy === undefined) {
                    // The nodes were already attached, so the mark did not affect them.
                    return [mark.count];
                }
                // The nodes were not attached and could not be attached.
                return [];
            }
            default:
                (0, util_1.fail)("Not implemented");
        }
    }
}
function transferMovedChanges(marks, revision, crossFieldManager) {
    var _a;
    for (const mark of marks) {
        if ((0, utils_1.isObjMark)(mark) && (mark.type === "MoveOut" || mark.type === "ReturnFrom")) {
            const change = crossFieldManager.get(modular_schema_1.CrossFieldTarget.Destination, (_a = mark.revision) !== null && _a !== void 0 ? _a : revision, mark.id, true);
            if (change !== undefined) {
                mark.changes = change;
            }
        }
    }
}
function invertModifyOrSkip(length, changes, index, inverter) {
    if (changes !== undefined) {
        (0, common_utils_1.assert)(length === 1, 0x66c /* A modify mark must have length equal to one */);
        return { type: "Modify", changes: inverter(changes, index) };
    }
    return length;
}
//# sourceMappingURL=invert.js.map