/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils";
import { SequenceField as SF } from "../../../feature-libraries/index.js";
// eslint-disable-next-line import/no-internal-modules
import { isNewAttach } from "../../../feature-libraries/sequence-field/utils.js";
import { brand } from "../../../util/index.js";
import { TestChange } from "../../testChange.js";
import { mintRevisionTag } from "../../utils.js";
const tag = mintRevisionTag();
export const cases = {
    no_change: [],
    insert: createInsertChangeset(1, 2, brand(1)),
    modify: SF.sequenceFieldEditor.buildChildChange(0, TestChange.mint([], 1)),
    modify_insert: [
        createSkipMark(1),
        createInsertMark(1, brand(1), { changes: TestChange.mint([], 2) }),
    ],
    remove: createRemoveChangeset(1, 3),
    revive: createReviveChangeset(2, 2, { revision: tag, localId: brand(0) }),
    pin: [createPinMark(4, brand(0))],
    move: createMoveChangeset(1, 2, 4),
    return: createReturnChangeset(1, 3, 0, { revision: tag, localId: brand(0) }),
    transient_insert: [
        { count: 1 },
        createAttachAndDetachMark(createInsertMark(2, brand(1)), createRemoveMark(2, brand(2))),
    ],
};
function createInsertChangeset(index, count, id) {
    return SF.sequenceFieldEditor.insert(index, count, id ?? brand(0));
}
function createRemoveChangeset(startIndex, size, id) {
    return SF.sequenceFieldEditor.remove(startIndex, size, id ?? brand(0));
}
function createRedundantRemoveChangeset(index, size, detachEvent) {
    const changeset = createRemoveChangeset(index, size);
    changeset[changeset.length - 1].cellId = detachEvent;
    return changeset;
}
function createRedundantReviveChangeset(startIndex, count, detachEvent) {
    const markList = SF.sequenceFieldEditor.revive(startIndex, count, detachEvent);
    const mark = markList[markList.length - 1];
    delete mark.cellId;
    return markList;
}
function createReviveChangeset(startIndex, count, detachEvent) {
    return SF.sequenceFieldEditor.revive(startIndex, count, detachEvent);
}
function createMoveChangeset(sourceIndex, count, destIndex, id = brand(0)) {
    return SF.sequenceFieldEditor.move(sourceIndex, count, destIndex, id);
}
function createReturnChangeset(sourceIndex, count, destIndex, detachEvent) {
    return SF.sequenceFieldEditor.return(sourceIndex, count, destIndex, detachEvent);
}
function createModifyChangeset(index, change) {
    return SF.sequenceFieldEditor.buildChildChange(index, change);
}
function createModifyDetachedChangeset(index, change, detachEvent) {
    const changeset = createModifyChangeset(index, change);
    const modify = changeset[changeset.length - 1];
    modify.cellId = detachEvent;
    return changeset;
}
/**
 * @param count - The number of nodes inserted.
 * @param cellId - The first cell to insert the content into (potentially includes lineage information).
 * Also defines the ChangeAtomId to associate with the mark.
 * @param overrides - Any additional properties to add to the mark.
 */
function createInsertMark(count, cellId, overrides) {
    const cellIdObject = typeof cellId === "object" ? cellId : { localId: cellId };
    const mark = {
        type: "Insert",
        count,
        id: cellIdObject.localId,
        cellId: cellIdObject,
    };
    if (cellIdObject.revision !== undefined) {
        mark.revision = cellIdObject.revision;
    }
    return { ...mark, ...overrides };
}
/**
 * This overload creates a revive that targets empty cells.
 * See `createPinMark` for a revive that targets populated cells.
 * @param count - The number of nodes to revive.
 * If a number is passed, that many dummy nodes will be generated.
 * @param cellId - The first cell to revive content into.
 * The mark id defaults to the local ID of this CellId.
 * @param overrides - Any additional properties to add to the mark.
 * Use this to give the mark a `RevisionTag`
 */
function createReviveMark(count, cellId, overrides) {
    return {
        type: "Insert",
        count,
        cellId,
        id: cellId.localId,
        ...overrides,
    };
}
function createPinMark(count, id, overrides) {
    return {
        type: "Insert",
        count,
        id,
        ...overrides,
    };
}
/**
 * @param count - The number of nodes to remove.
 * @param markId - The id to associate with the mark.
 * Defines how later edits refer the emptied cells.
 * @param overrides - Any additional properties to add to the mark.
 */
function createRemoveMark(count, markId, overrides) {
    const cellId = typeof markId === "object" ? markId : { localId: markId };
    const mark = {
        type: "Remove",
        count,
        id: cellId.localId,
    };
    if (cellId.revision !== undefined) {
        mark.revision = cellId.revision;
    }
    return { ...mark, ...overrides };
}
/**
 * @param count - The number of nodes to move.
 * @param markId - The id to associate with the marks.
 * Defines how later edits refer the emptied cells.
 * @param overrides - Any additional properties to add to the mark.
 * @returns A pair of marks, the first for moving out, the second for moving in.
 */
function createMoveMarks(count, markId, overrides) {
    const moveOut = createMoveOutMark(count, markId, overrides);
    const { changes: _, ...overridesWithNoChanges } = overrides ?? {};
    const moveIn = createMoveInMark(count, markId, overridesWithNoChanges);
    return [moveOut, moveIn];
}
/**
 * @param count - The number of nodes to move out.
 * @param markId - The id to associate with the mark.
 * Defines how later edits refer the emptied cells.
 * @param overrides - Any additional properties to add to the mark.
 */
function createMoveOutMark(count, markId, overrides) {
    const atomId = typeof markId === "object" ? markId : { localId: markId };
    const mark = {
        type: "MoveOut",
        count,
        id: atomId.localId,
    };
    if (atomId.revision !== undefined) {
        mark.revision = atomId.revision;
    }
    return { ...mark, ...overrides };
}
/**
 * @param count - The number of nodes moved in.
 * @param cellId - The first cell to move the content into (potentially includes lineage information).
 * Also defines the ChangeAtomId to associate with the mark.
 * @param overrides - Any additional properties to add to the mark.
 */
function createMoveInMark(count, cellId, overrides) {
    const cellIdObject = typeof cellId === "object" ? cellId : { localId: cellId };
    const mark = {
        type: "MoveIn",
        id: cellIdObject.localId,
        cellId: cellIdObject,
        count,
    };
    if (cellIdObject.revision !== undefined) {
        mark.revision = cellIdObject.revision;
    }
    return { ...mark, ...overrides };
}
/**
 * @param count - The number of nodes to attach.
 * @param markId - The id to associate with the mark.
 * @param cellId - The cell to return the nodes to.
 * If undefined, the mark targets populated cells and is therefore muted.
 * @param overrides - Any additional properties to add to the mark.
 */
function createReturnToMark(count, markId, cellId, overrides) {
    const atomId = typeof markId === "object" ? markId : { localId: markId };
    const mark = {
        type: "MoveIn",
        id: atomId.localId,
        count,
    };
    if (cellId !== undefined) {
        mark.cellId = cellId;
    }
    if (atomId.revision !== undefined) {
        mark.revision = atomId.revision;
    }
    return { ...mark, ...overrides };
}
/**
 * @param changes - The changes to apply to the node.
 * @param cellId - Describes the cell that the target node used to reside in. Used when the target node is removed.
 */
function createModifyMark(changes, cellId) {
    const mark = {
        count: 1,
        changes,
    };
    if (cellId !== undefined) {
        mark.cellId = cellId;
    }
    return mark;
}
function createSkipMark(count) {
    return { count };
}
function createTomb(revision, localId = brand(0), count = 1) {
    return { count, cellId: { revision, localId } };
}
function createAttachAndDetachMark(attach, detach, overrides) {
    assert(attach.count === detach.count, "Attach and detach must have the same count");
    assert(attach.cellId !== undefined, "AttachAndDetach attach should apply to an empty cell");
    assert(detach.cellId === undefined, "AttachAndDetach detach should apply to an populated cell");
    assert(attach.changes === undefined && detach.changes === undefined, "Attach and detach must not carry changes");
    // As a matter of normalization, we only use AttachAndDetach marks to represent cases where the detach's
    // implicit revival semantics would not be a sufficient representation.
    assert(attach.type === "MoveIn" || isNewAttach(attach), "Unnecessary AttachAndDetach mark");
    const mark = {
        type: "AttachAndDetach",
        count: attach.count,
        cellId: attach.cellId,
        attach: SF.extractMarkEffect(attach),
        detach: SF.extractMarkEffect(detach),
        ...overrides,
    };
    return mark;
}
function overrideCellId(cellId, mark) {
    mark.cellId = cellId;
    return mark;
}
export const MarkMaker = {
    onEmptyCell: overrideCellId,
    insert: createInsertMark,
    revive: createReviveMark,
    skip: createSkipMark,
    tomb: createTomb,
    pin: createPinMark,
    remove: createRemoveMark,
    modify: createModifyMark,
    move: createMoveMarks,
    moveOut: createMoveOutMark,
    moveIn: createMoveInMark,
    returnTo: createReturnToMark,
    attachAndDetach: createAttachAndDetachMark,
};
export const ChangeMaker = {
    insert: createInsertChangeset,
    remove: createRemoveChangeset,
    redundantRemove: createRedundantRemoveChangeset,
    revive: createReviveChangeset,
    redundantRevive: createRedundantReviveChangeset,
    move: createMoveChangeset,
    return: createReturnChangeset,
    modify: createModifyChangeset,
    modifyDetached: createModifyDetachedChangeset,
};
//# sourceMappingURL=testEdits.js.map