"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SequenceEditBuilder = void 0;
const core_1 = require("../../core");
const util_1 = require("../../util");
const treeTextCursor_1 = require("../treeTextCursor");
const sequenceChangeFamily_1 = require("./sequenceChangeFamily");
class SequenceEditBuilder extends core_1.EditBuilder {
    constructor(changeReceiver, anchorSet) {
        super(sequenceChangeFamily_1.sequenceChangeFamily, changeReceiver, anchorSet);
        this.opId = 0;
    }
    setValue(node, value) {
        const modify = { type: "Modify", value: { id: 0 } };
        // Only set the `SetValue.value` field if the given `value` is defined.
        // This ensures the object properly round-trips through JSON.
        if (value !== undefined) {
            modify.value.value = value;
        }
        this.applyMarkAtPath(modify, node);
    }
    insert(place, cursor) {
        const id = this.opId++;
        const content = (0, treeTextCursor_1.jsonableTreeFromCursor)(cursor);
        const insert = { type: "Insert", id, content: [content] };
        this.applyMarkAtPath(insert, place);
    }
    delete(place, count) {
        const id = this.opId++;
        const mark = { type: "Delete", id, count };
        this.applyMarkAtPath(mark, place);
    }
    move(source, count, destination) {
        var _a, _b, _c, _d;
        if (count === 0) {
            return;
        }
        const id = this.opId++;
        const moveOut = { type: "MoveOut", id, count };
        const moveIn = { type: "MoveIn", id, count };
        if (source.parent === destination.parent) {
            const srcIndex = source.parentIndex;
            const dstIndex = destination.parentIndex;
            if (source.parentField === destination.parentField) {
                const marks = [];
                if (dstIndex <= srcIndex) {
                    if (dstIndex > 0) {
                        marks.push(dstIndex);
                    }
                    marks.push(moveIn);
                    const gap = srcIndex - dstIndex;
                    if (gap > 0) {
                        marks.push(gap);
                    }
                    marks.push(moveOut);
                }
                else {
                    if (srcIndex > 0) {
                        marks.push(srcIndex);
                    }
                    const gap = dstIndex - srcIndex;
                    if (gap < count) {
                        // The target attach point lies within the range of nodes being detached.
                        // Split the operation into two moves.
                        const id2 = this.opId++;
                        marks.push({ type: "MoveOut", id, count: gap }, { type: "MoveIn", id, count: gap }, { type: "MoveIn", id: id2, count: count - gap }, { type: "MoveOut", id: id2, count: count - gap });
                    }
                    else {
                        marks.push(moveOut);
                        const updatedGap = gap - count;
                        if (updatedGap >= 0) {
                            marks.push(updatedGap);
                        }
                        marks.push(moveIn);
                    }
                }
                this.applyFieldMarksAtPath({ [source.parentField]: marks }, source.parent);
            }
            else {
                this.applyFieldMarksAtPath({
                    [source.parentField]: srcIndex > 0 ? [srcIndex, moveOut] : [moveOut],
                    [destination.parentField]: dstIndex > 0 ? [dstIndex, moveIn] : [moveIn],
                }, source.parent);
            }
        }
        else {
            // The source and destination are under different parent nodes
            let a = {
                marks: toFieldMarks(moveOut, source),
                path: source.parent,
            };
            let b = {
                marks: toFieldMarks(moveIn, destination),
                path: destination.parent,
            };
            let depthDiff = (0, core_1.getDepth)(source) - (0, core_1.getDepth)(destination);
            // Ensure that a represents the deeper mark
            if (depthDiff < 0) {
                [a, b] = [b, a];
                depthDiff = -depthDiff;
            }
            // Nest the deeper mark so that they are both at the same depth
            a = wrapN(a.marks, a.path, depthDiff);
            // Nest both marks one level at a time until they reach the same parent
            while (((_a = a.path) === null || _a === void 0 ? void 0 : _a.parent) !== ((_b = b.path) === null || _b === void 0 ? void 0 : _b.parent)) {
                // The paths are at same depth they must both be defined in order to have different parents
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                a = { marks: wrap1(a.marks, a.path), path: (_c = a.path) === null || _c === void 0 ? void 0 : _c.parent };
                // The paths are at same depth they must both be defined in order to have different parents
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                b = { marks: wrap1(b.marks, b.path), path: (_d = b.path) === null || _d === void 0 ? void 0 : _d.parent };
            }
            if (a.path === undefined) {
                this.applyChange({ marks: Object.assign(Object.assign({}, a.marks), b.marks) });
            }
            else {
                const aPath = a.path;
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const bPath = b.path;
                const keyA = aPath.parentField;
                const keyB = bPath.parentField;
                const aFieldMarks = wrap1(a.marks, aPath);
                const bFieldMarks = wrap1(b.marks, bPath);
                if (keyA !== keyB) {
                    this.applyFieldMarksAtPath(Object.assign(Object.assign({}, aFieldMarks), bFieldMarks), aPath.parent);
                }
                else {
                    let indexA = aPath.parentIndex;
                    let indexB = bPath.parentIndex;
                    if (indexA === indexB) {
                        (0, util_1.fail)("If the two paths have the same key and the same index then they should have shared an UpPath earlier");
                    }
                    let aMarkList = aFieldMarks[keyA];
                    let bMarkList = bFieldMarks[keyB];
                    if (indexA > indexB) {
                        [aMarkList, indexA, bMarkList, indexB] = [
                            bMarkList,
                            indexB,
                            aMarkList,
                            indexA,
                        ];
                    }
                    const marks = aMarkList;
                    const gap = indexB - indexA - 1;
                    if (gap > 0) {
                        marks.push(gap);
                    }
                    marks.push(bMarkList[1]);
                    this.applyFieldMarksAtPath({ [keyA]: marks }, aPath.parent);
                }
            }
        }
    }
    applyMarkAtPath(mark, path) {
        this.applyFieldMarksAtPath(toFieldMarks(mark, path), path.parent);
    }
    applyFieldMarksAtPath(marks, path) {
        this.applyChange({ marks: wrap(marks, path) });
    }
}
exports.SequenceEditBuilder = SequenceEditBuilder;
function toFieldMarks(mark, node) {
    const key = node.parentField;
    const index = node.parentIndex;
    return {
        [key]: index === 0 ? [mark] : [index, mark],
    };
}
function wrapN(mark, node, depth) {
    let currentNode = node;
    let out = mark;
    let currentDepth = 0;
    while (currentNode !== undefined && currentDepth < depth) {
        out = wrap1(out, currentNode);
        currentDepth += 1;
        currentNode = currentNode.parent;
    }
    return { marks: out, path: currentNode };
}
function wrap(mark, node) {
    let currentNode = node;
    let out = mark;
    while (currentNode !== undefined) {
        out = wrap1(out, currentNode);
        currentNode = currentNode.parent;
    }
    return out;
}
function wrap1(marks, node) {
    return toFieldMarks({ type: "Modify", fields: marks }, node);
}
//# sourceMappingURL=sequenceEditBuilder.js.map