"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const assert_1 = require("assert");
const mergeTreeNodes_1 = require("../mergeTreeNodes.cjs");
const textSegment_1 = require("../textSegment.cjs");
const constants_1 = require("../constants.cjs");
const mergeTree_1 = require("../mergeTree.cjs");
const MergeTreeTextHelper_1 = require("../MergeTreeTextHelper.cjs");
const mergeTreeNodeWalk_1 = require("../mergeTreeNodeWalk.cjs");
const testUtils_1 = require("./testUtils.cjs");
const localClientId = 17;
const treeFactories = [
    {
        create: () => {
            const initialText = "hello world";
            const mergeTree = new mergeTree_1.MergeTree();
            (0, testUtils_1.insertSegments)({
                mergeTree,
                pos: 0,
                segments: [textSegment_1.TextSegment.make(initialText)],
                refSeq: constants_1.UniversalSequenceNumber,
                clientId: constants_1.LocalClientId,
                seq: constants_1.UniversalSequenceNumber,
                opArgs: undefined,
            });
            mergeTree.startCollaboration(localClientId, 
            /* minSeq: */ constants_1.UniversalSequenceNumber, 
            /* currentSeq: */ constants_1.UniversalSequenceNumber);
            return {
                initialText,
                mergeTree,
                middle: Math.round(initialText.length / 2),
                refSeq: constants_1.UniversalSequenceNumber,
                textHelper: new MergeTreeTextHelper_1.MergeTreeTextHelper(mergeTree),
            };
        },
        name: "single segment tree",
    },
    {
        create: () => {
            let initialText = "0";
            const mergeTree = new mergeTree_1.MergeTree();
            (0, testUtils_1.insertSegments)({
                mergeTree,
                pos: 0,
                segments: [textSegment_1.TextSegment.make(initialText)],
                refSeq: constants_1.UniversalSequenceNumber,
                clientId: constants_1.LocalClientId,
                seq: constants_1.UniversalSequenceNumber,
                opArgs: undefined,
            });
            for (let i = 1; i < mergeTreeNodes_1.MaxNodesInBlock - 1; i++) {
                const text = i.toString();
                (0, testUtils_1.insertText)({
                    mergeTree,
                    pos: mergeTree.getLength(constants_1.UniversalSequenceNumber, localClientId),
                    refSeq: constants_1.UniversalSequenceNumber,
                    clientId: localClientId,
                    seq: constants_1.UniversalSequenceNumber,
                    text,
                    props: undefined,
                    opArgs: undefined,
                });
                initialText += text;
            }
            const textHelper = new MergeTreeTextHelper_1.MergeTreeTextHelper(mergeTree);
            assert_1.strict.equal(textHelper.getText(constants_1.UniversalSequenceNumber, localClientId), initialText);
            const nodes = [mergeTree.root];
            while (nodes.length > 0) {
                const node = nodes.pop();
                assert_1.strict.equal(node.childCount, mergeTreeNodes_1.MaxNodesInBlock - 1);
                const childrenBlocks = node.children
                    .map((v) => v)
                    .filter((v) => v === undefined);
                nodes.push(...childrenBlocks);
            }
            mergeTree.startCollaboration(localClientId, 
            /* minSeq: */ constants_1.UniversalSequenceNumber, 
            /* currentSeq: */ constants_1.UniversalSequenceNumber);
            return {
                initialText,
                mergeTree,
                middle: Math.round(mergeTreeNodes_1.MaxNodesInBlock / 2),
                refSeq: constants_1.UniversalSequenceNumber,
                textHelper,
            };
        },
        name: "Full single layer tree",
    },
    {
        create: () => {
            let initialText = "0";
            const mergeTree = new mergeTree_1.MergeTree();
            (0, testUtils_1.insertSegments)({
                mergeTree,
                pos: 0,
                segments: [textSegment_1.TextSegment.make(initialText)],
                refSeq: constants_1.UniversalSequenceNumber,
                clientId: constants_1.LocalClientId,
                seq: constants_1.UniversalSequenceNumber,
                opArgs: undefined,
            });
            for (let i = 1; i < mergeTreeNodes_1.MaxNodesInBlock * 4; i++) {
                const text = i.toString();
                (0, testUtils_1.insertText)({
                    mergeTree,
                    pos: mergeTree.getLength(constants_1.UniversalSequenceNumber, localClientId),
                    refSeq: constants_1.UniversalSequenceNumber,
                    clientId: localClientId,
                    seq: constants_1.UniversalSequenceNumber,
                    text,
                    props: undefined,
                    opArgs: undefined,
                });
                initialText += text;
            }
            const remove = Math.round(initialText.length / 4);
            // remove from start
            mergeTree.markRangeRemoved(0, remove, constants_1.UniversalSequenceNumber, localClientId, constants_1.UniversalSequenceNumber, false, undefined);
            initialText = initialText.substring(remove);
            // remove from end
            mergeTree.markRangeRemoved(initialText.length - remove, initialText.length, constants_1.UniversalSequenceNumber, localClientId, constants_1.UniversalSequenceNumber, false, undefined);
            initialText = initialText.substring(0, initialText.length - remove);
            mergeTree.startCollaboration(localClientId, 
            /* minSeq: */ constants_1.UniversalSequenceNumber, 
            /* currentSeq: */ constants_1.UniversalSequenceNumber);
            return {
                initialText,
                mergeTree,
                middle: Math.round(initialText.length / 2),
                refSeq: constants_1.UniversalSequenceNumber,
                textHelper: new MergeTreeTextHelper_1.MergeTreeTextHelper(mergeTree),
            };
        },
        name: "Tree with remove segments",
    },
];
describe("MergeTree.insertingWalk", () => {
    treeFactories.forEach((tf) => {
        describe(tf.name, () => {
            const treeFactory = tf;
            let testData;
            beforeEach(() => {
                testData = treeFactory.create();
                (0, assert_1.strict)((0, testUtils_1.nodeOrdinalsHaveIntegrity)(testData.mergeTree.root));
            });
            afterEach(() => {
                (0, assert_1.strict)((0, testUtils_1.nodeOrdinalsHaveIntegrity)(testData.mergeTree.root));
            });
            describe("insertText", () => {
                it("at beginning", () => {
                    (0, testUtils_1.insertText)({
                        mergeTree: testData.mergeTree,
                        pos: 0,
                        refSeq: testData.refSeq,
                        clientId: localClientId,
                        seq: constants_1.UnassignedSequenceNumber,
                        text: "a",
                        props: undefined,
                        opArgs: undefined,
                    });
                    assert_1.strict.equal(testData.mergeTree.getLength(testData.refSeq, localClientId), testData.initialText.length + 1);
                    const currentValue = testData.textHelper.getText(testData.refSeq, localClientId);
                    assert_1.strict.equal(currentValue.length, testData.initialText.length + 1);
                    assert_1.strict.equal(currentValue, `a${testData.initialText}`);
                });
                it("at end", () => {
                    (0, testUtils_1.insertText)({
                        mergeTree: testData.mergeTree,
                        pos: testData.initialText.length,
                        refSeq: testData.refSeq,
                        clientId: localClientId,
                        seq: constants_1.UnassignedSequenceNumber,
                        text: "a",
                        props: undefined,
                        opArgs: undefined,
                    });
                    assert_1.strict.equal(testData.mergeTree.getLength(testData.refSeq, localClientId), testData.initialText.length + 1);
                    const currentValue = testData.textHelper.getText(testData.refSeq, localClientId);
                    assert_1.strict.equal(currentValue.length, testData.initialText.length + 1);
                    assert_1.strict.equal(currentValue, `${testData.initialText}a`);
                });
                it("in middle", () => {
                    (0, testUtils_1.insertText)({
                        mergeTree: testData.mergeTree,
                        pos: testData.middle,
                        refSeq: testData.refSeq,
                        clientId: localClientId,
                        seq: constants_1.UnassignedSequenceNumber,
                        text: "a",
                        props: undefined,
                        opArgs: undefined,
                    });
                    assert_1.strict.equal(testData.mergeTree.getLength(testData.refSeq, localClientId), testData.initialText.length + 1);
                    const currentValue = testData.textHelper.getText(testData.refSeq, localClientId);
                    assert_1.strict.equal(currentValue.length, testData.initialText.length + 1);
                    assert_1.strict.equal(currentValue, `${testData.initialText.substring(0, testData.middle)}` +
                        "a" +
                        `${testData.initialText.substring(testData.middle)}`);
                });
            });
        });
    });
    it("handles conflicts involving removed segments across block boundaries", () => {
        let initialText = "0";
        let seq = 0;
        const mergeTree = new mergeTree_1.MergeTree();
        mergeTree.startCollaboration(localClientId, 0, seq);
        (0, testUtils_1.insertSegments)({
            mergeTree,
            pos: 0,
            segments: [textSegment_1.TextSegment.make(initialText)],
            refSeq: constants_1.UniversalSequenceNumber,
            clientId: localClientId,
            seq: constants_1.UniversalSequenceNumber,
            opArgs: undefined,
        });
        for (let i = 1; i < mergeTreeNodes_1.MaxNodesInBlock; i++) {
            const text = String.fromCharCode(i + 64);
            (0, testUtils_1.insertText)({
                mergeTree,
                pos: 0,
                refSeq: constants_1.UniversalSequenceNumber,
                clientId: localClientId,
                seq: constants_1.UnassignedSequenceNumber,
                text,
                props: undefined,
                opArgs: undefined,
            });
            initialText += text;
        }
        const textHelper = new MergeTreeTextHelper_1.MergeTreeTextHelper(mergeTree);
        assert_1.strict.equal(mergeTree.root.childCount, 2);
        assert_1.strict.equal(textHelper.getText(0, localClientId), "GFEDCBA0");
        // Remove "DCBA"
        (0, testUtils_1.markRangeRemoved)({
            mergeTree,
            start: 3,
            end: 7,
            refSeq: constants_1.UniversalSequenceNumber,
            clientId: localClientId,
            seq: constants_1.UnassignedSequenceNumber,
            overwrite: false,
            opArgs: undefined,
        });
        assert_1.strict.equal(textHelper.getText(0, localClientId), "GFE0");
        // Simulate another client inserting concurrently with the above operations. Because
        // all segments but the 0 are unacked, this insert should place the segment directly
        // before the 0. Prior to this regression test, an issue with `rightExcursion` in the
        // merge conflict logic instead caused the segment to be placed before the removed segments.
        (0, testUtils_1.insertText)({
            mergeTree,
            pos: 0,
            refSeq: constants_1.UniversalSequenceNumber,
            clientId: localClientId + 1,
            seq: ++seq,
            text: "x",
        });
        const segments = [];
        (0, mergeTreeNodeWalk_1.walkAllChildSegments)(mergeTree.root, (seg) => {
            if (textSegment_1.TextSegment.is(seg)) {
                if (seg.localRemovedSeq !== undefined || seg.removedSeq !== undefined) {
                    segments.push(`(${seg.text})`);
                }
                else {
                    segments.push(seg.text);
                }
            }
            return true;
        });
        assert_1.strict.deepStrictEqual(segments, ["G", "F", "E", "(D)", "(C)", "(B)", "(A)", "x", "0"]);
    });
});
//# sourceMappingURL=mergeTree.insertingWalk.spec.cjs.map