"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 format_1 = require("./format");
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, _f;
    switch (mark.type) {
        case format_1.NoopMarkType: {
            return [mark];
        }
        case "Insert": {
            const inverse = (0, utils_1.withNodeChange)({ type: "Delete", count: mark.content.length }, invertNodeChange(mark.changes, inputIndex, invertChild));
            return [inverse];
        }
        case "Delete": {
            (0, common_utils_1.assert)(revision !== undefined, 0x5a1 /* Unable to revert to undefined revision */);
            if (mark.detachEvent === undefined) {
                const inverse = (0, utils_1.withNodeChange)({
                    type: "Revive",
                    detachEvent: { revision: (_a = mark.revision) !== null && _a !== void 0 ? _a : revision, index: inputIndex },
                    content: reviver(revision, inputIndex, mark.count),
                    count: mark.count,
                    inverseOf: (_b = mark.revision) !== null && _b !== void 0 ? _b : revision,
                }, invertNodeChange(mark.changes, inputIndex, invertChild));
                return [inverse];
            }
            // TODO: preserve modifications to the removed nodes.
            return [];
        }
        case "Revive": {
            if (!(0, utils_1.isReattachConflicted)(mark)) {
                const inverse = (0, utils_1.withNodeChange)({
                    type: "Delete",
                    count: mark.count,
                }, invertNodeChange(mark.changes, inputIndex, invertChild));
                return [inverse];
            }
            return [
                invertModifyOrSkip(mark.count, mark.changes, inputIndex, invertChild, mark.detachEvent),
            ];
        }
        case "Modify": {
            if (mark.detachEvent === undefined) {
                return [
                    {
                        type: "Modify",
                        changes: invertChild(mark.changes, inputIndex),
                    },
                ];
            }
            // TODO: preserve modifications to the removed nodes.
            return [];
        }
        case "MoveOut":
        case "ReturnFrom": {
            if ((0, utils_1.areInputCellsEmpty)(mark)) {
                // TODO: preserve modifications to the removed nodes.
                return [];
            }
            if (mark.type === "ReturnFrom" && 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, (_c = mark.revision) !== null && _c !== void 0 ? _c : revision, mark.id, invertChild(mark.changes, inputIndex), true);
            }
            return [
                {
                    type: "ReturnTo",
                    id: mark.id,
                    count: mark.count,
                    detachEvent: {
                        revision: (_e = (_d = mark.revision) !== null && _d !== void 0 ? _d : revision) !== null && _e !== void 0 ? _e : (0, util_1.fail)("Revision must be defined"),
                        index: inputIndex,
                    },
                },
            ];
        }
        case "MoveIn":
        case "ReturnTo": {
            if (mark.isSrcConflicted) {
                return mark.type === "ReturnTo" && mark.detachEvent === undefined
                    ? [{ count: mark.count }]
                    : [];
            }
            if (mark.type === "ReturnTo") {
                if (mark.detachEvent === undefined) {
                    // The nodes were already attached, so the mark did not affect them.
                    return [{ count: mark.count }];
                }
                else if ((0, utils_1.isConflictedReattach)(mark)) {
                    // The nodes were not attached and could not be attached.
                    return [];
                }
            }
            const invertedMark = {
                type: "ReturnFrom",
                id: mark.id,
                count: mark.count,
            };
            const movedChanges = crossFieldManager.get(modular_schema_1.CrossFieldTarget.Destination, (_f = mark.revision) !== null && _f !== void 0 ? _f : revision, mark.id, true);
            if (movedChanges !== undefined) {
                invertedMark.changes = movedChanges;
            }
            return [invertedMark];
        }
        default:
            (0, util_1.fail)("Not implemented");
    }
}
function transferMovedChanges(marks, revision, crossFieldManager) {
    var _a;
    for (const mark of marks) {
        if (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, detachEvent) {
    if (changes !== undefined) {
        (0, common_utils_1.assert)(length === 1, 0x66c /* A modify mark must have length equal to one */);
        const modify = { type: "Modify", changes: inverter(changes, index) };
        if (detachEvent !== undefined) {
            modify.detachEvent = detachEvent;
        }
        return modify;
    }
    return { count: detachEvent === undefined ? length : 0 };
}
function invertNodeChange(change, index, inverter) {
    return change === undefined ? undefined : inverter(change, index);
}
//# sourceMappingURL=invert.js.map