"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.invert = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const markListFactory_js_1 = require("./markListFactory.js");
const types_js_1 = require("./types.js");
const utils_js_1 = require("./utils.js");
/**
 * Inverts a given changeset.
 * @param change - The changeset to produce the inverse of.
 * @param isRollback - Whether the inverse is being produced for a rollback.
 * @param genId - The ID allocator to use for generating new IDs.
 * @param revision - The revision to use for the inverse changeset.
 * @param crossFieldManager - The cross-field manager to use for tracking cross-field changes.
 * @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, isRollback, genId, revision, crossFieldManager) {
    return invertMarkList(change, isRollback, crossFieldManager, revision);
}
exports.invert = invert;
function invertMarkList(markList, isRollback, crossFieldManager, revision) {
    const inverseMarkList = new markListFactory_js_1.MarkListFactory();
    for (const mark of markList) {
        const inverseMarks = invertMark(mark, isRollback, crossFieldManager, revision);
        inverseMarkList.push(...inverseMarks);
    }
    return inverseMarkList.list;
}
function invertMark(mark, isRollback, crossFieldManager, revision) {
    if (!(0, utils_js_1.isImpactful)(mark)) {
        const inputId = (0, utils_js_1.getInputCellId)(mark);
        return [invertNodeChangeOrSkip(mark.count, mark.changes, inputId)];
    }
    const type = mark.type;
    switch (type) {
        case types_js_1.NoopMarkType: {
            return [mark];
        }
        case "Rename": {
            const inputId = (0, utils_js_1.getInputCellId)(mark);
            (0, internal_1.assert)(inputId !== undefined, 0x9f5 /* Rename mark must have cell ID */);
            // In older client versions, the ID of a detached root was the same as the ID of the cell it was last detached from.
            // To preserve compatibility with those versions, we must make sure that we do not rename the cell if we are not also
            // renaming the associated detached root, if there is one.
            if (isRollback) {
                // A rollback inverse inverts any associated root rename, so we should also invert the cell rename.
                const inverse = {
                    type: "Rename",
                    count: mark.count,
                    cellId: mark.idOverride,
                    // Unlike a remove or move-out, which follow a node, there is no way for this mark to assign the original input cell ID to another cell.
                    // This means it should be safe to always restore the input cell ID (as opposed to only doing it on rollbacks).
                    // Despite that, we still only do it on rollback for the sake of consistency: once a cell has been assigned an ID,
                    // the only way for that cell to be assigned that ID again is if it is rolled back to that state.
                    idOverride: inputId,
                };
                return [(0, utils_js_1.withNodeChange)(inverse, mark.changes)];
            }
            else {
                // An undo does not invert root renames, so we should not invert cell renames.
                return [invertNodeChangeOrSkip(mark.count, mark.changes, mark.idOverride)];
            }
        }
        case "Remove": {
            (0, internal_1.assert)(mark.revision !== undefined, 0x5a1 /* Unable to revert to undefined revision */);
            const outputId = (0, utils_js_1.getOutputCellId)(mark);
            const inputId = (0, utils_js_1.getInputCellId)(mark);
            (0, internal_1.assert)(inputId === undefined, "Unexpected remove of detached node");
            const attachId = { revision: isRollback ? mark.revision : revision, localId: mark.id };
            crossFieldManager.invertDetach({ revision: mark.revision, localId: mark.id }, mark.count, mark.changes, attachId);
            const inverse = {
                type: "Insert",
                id: mark.id,
                cellId: outputId,
                count: mark.count,
                revision: attachId.revision,
            };
            return [inverse];
        }
        case "Insert": {
            return invertInsert(mark, revision, crossFieldManager, isRollback);
        }
        default:
            (0, internal_1.unreachableCase)(type);
    }
}
function invertInsert(mark, revision, manager, isRollback) {
    const entry = manager.invertAttach((0, utils_js_1.getAttachedRootId)(mark), mark.count);
    if (entry.length < mark.count) {
        const [mark1, mark2] = (0, utils_js_1.splitMark)(mark, entry.length);
        return [
            invertInsertSegment(mark1, revision, isRollback, entry.value),
            ...invertInsert(mark2, revision, manager, isRollback),
        ];
    }
    return [invertInsertSegment(mark, revision, isRollback, entry.value)];
}
function invertInsertSegment(mark, revision, isRollback, detachEntry) {
    const inputId = (0, utils_js_1.getInputCellId)(mark);
    (0, internal_1.assert)(inputId !== undefined, 0x80c /* Active inserts should target empty cells */);
    const detachId = invertAttachId((0, utils_js_1.getAttachedRootId)(mark), revision, isRollback, detachEntry);
    const removeMark = {
        type: "Remove",
        count: mark.count,
        id: detachId.localId,
        revision: detachId.revision,
    };
    if (isRollback && !(0, utils_js_1.areEqualCellIds)(inputId, detachId)) {
        removeMark.cellRename = inputId;
    }
    if (detachEntry?.nodeChange !== undefined) {
        removeMark.changes = detachEntry.nodeChange;
    }
    return removeMark;
}
function invertAttachId(attachId, revision, isRollback, detachEntry) {
    if (!isRollback) {
        return {
            revision,
            localId: attachId.localId,
        };
    }
    if (detachEntry?.detachId !== undefined) {
        return detachEntry.detachId;
    }
    return attachId;
}
function invertNodeChangeOrSkip(count, changes, cellId) {
    if (changes !== undefined) {
        (0, internal_1.assert)(count === 1, 0x66c /* A modify mark must have length equal to one */);
        const noop = {
            count,
            changes,
        };
        if (cellId !== undefined) {
            noop.cellId = cellId;
        }
        return noop;
    }
    if (cellId !== undefined) {
        return { count, cellId };
    }
    return { count };
}
//# sourceMappingURL=invert.js.map