"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = require("node:assert");
const constants_js_1 = require("../constants.js");
const mergeTree_js_1 = require("../mergeTree.js");
const mergeTreeNodes_js_1 = require("../mergeTreeNodes.js");
const ops_js_1 = require("../ops.js");
const segmentInfos_js_1 = require("../segmentInfos.js");
const textSegment_js_1 = require("../textSegment.js");
const testUtils_js_1 = require("./testUtils.js");
function splitAt(mergeTree, pos) {
    let segment;
    mergeTree.mapRange((seg) => {
        segment = seg;
        return false;
    }, mergeTree.collabWindow.currentSeq, mergeTree.collabWindow.clientId, undefined, pos, pos + 1, true);
    return segment;
}
describe("MergeTree", () => {
    let mergeTree;
    const remoteClientId = 35;
    const localClientId = 17;
    let currentSequenceNumber;
    const annotateStart = 1;
    const markerPosition = annotateStart + 2;
    const annotateEnd = markerPosition + 2;
    const splitPos = Math.floor((annotateEnd - annotateStart) / 2) + annotateStart;
    beforeEach(() => {
        mergeTree = new mergeTree_js_1.MergeTree();
        (0, testUtils_js_1.insertSegments)({
            mergeTree,
            pos: 0,
            segments: [textSegment_js_1.TextSegment.make("hello world!")],
            refSeq: constants_js_1.UniversalSequenceNumber,
            clientId: constants_js_1.LocalClientId,
            seq: constants_js_1.UniversalSequenceNumber,
            opArgs: undefined,
        });
        currentSequenceNumber = 0;
        (0, testUtils_js_1.insertSegments)({
            mergeTree,
            pos: markerPosition,
            segments: [mergeTreeNodes_js_1.Marker.make(ops_js_1.ReferenceType.Tile)],
            refSeq: currentSequenceNumber,
            clientId: remoteClientId,
            seq: ++currentSequenceNumber,
            opArgs: undefined,
        });
    });
    describe("annotateRange", () => {
        describe("not collaborating", () => {
            it("remote", () => {
                mergeTree.annotateRange(annotateStart, annotateEnd, {
                    props: { propertySource: "remote" },
                }, currentSequenceNumber, remoteClientId, currentSequenceNumber + 1, undefined);
                const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                const segment = segmentInfo.segment;
                node_assert_1.strict.equal(segment?.properties?.propertySource, "remote");
            });
            it("local", () => {
                mergeTree.annotateRange(annotateStart, annotateEnd, {
                    props: { propertySource: "local" },
                }, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                const segment = segmentInfo.segment;
                node_assert_1.strict.equal(segment.properties?.propertySource, "local");
            });
        });
        describe("collaborating", () => {
            beforeEach(() => {
                mergeTree.startCollaboration(localClientId, 
                /* minSeq: */ currentSequenceNumber, 
                /* currentSeq: */ currentSequenceNumber);
            });
            describe("local first", () => {
                const props = {
                    props: { propertySource: "local" },
                };
                beforeEach(() => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, props, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                });
                it("unsequenced local", () => {
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                });
                it("unsequenced local after unsequenced local", () => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { secondProperty: "local" },
                    }, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.secondProperty, "local");
                });
                it("unsequenced local split", () => {
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const splitSegment = splitAt(mergeTree, splitPos);
                    (0, segmentInfos_js_1.assertMergeNode)(splitSegment);
                    node_assert_1.strict.notEqual(segmentInfo.segment?.ordinal, splitSegment.ordinal);
                    node_assert_1.strict.equal(splitSegment.properties?.propertySource, "local");
                });
                it("unsequenced local after unsequenced local split", () => {
                    const secondChangeProps = {
                        props: {
                            secondChange: 1,
                        },
                    };
                    mergeTree.annotateRange(annotateStart, annotateEnd, secondChangeProps, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    const splitOnlyProps = {
                        props: {
                            splitOnly: 1,
                        },
                    };
                    mergeTree.annotateRange(splitPos, annotateEnd, splitOnlyProps, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    const splitSegmentInfo = mergeTree.getContainingSegment(splitPos, currentSequenceNumber, localClientId);
                    const splitSegment = splitSegmentInfo.segment;
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 2);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segment.properties?.secondChange, 1);
                    (0, node_assert_1.strict)(!segment.properties?.splitOnly);
                    node_assert_1.strict.equal(splitSegment.segmentGroups?.size, 3);
                    node_assert_1.strict.equal(splitSegment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(splitSegment.properties?.secondChange, 1);
                    node_assert_1.strict.equal(splitSegment.properties?.splitOnly, 1);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 1);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segment.properties?.secondChange, 1);
                    (0, node_assert_1.strict)(!segment.properties?.splitOnly);
                    node_assert_1.strict.equal(splitSegment?.segmentGroups?.size, 2);
                    node_assert_1.strict.equal(splitSegment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(splitSegment.properties?.secondChange, 1);
                    node_assert_1.strict.equal(splitSegment.properties?.splitOnly, 1);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...secondChangeProps,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 0);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segment.properties?.secondChange, 1);
                    (0, node_assert_1.strict)(!segment.properties?.splitOnly);
                    node_assert_1.strict.equal(splitSegment.segmentGroups?.size, 1);
                    node_assert_1.strict.equal(splitSegment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(splitSegment.properties?.secondChange, 1);
                    node_assert_1.strict.equal(splitSegment.properties?.splitOnly, 1);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: splitPos,
                            pos2: annotateEnd,
                            ...splitOnlyProps,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 0);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segment.properties?.secondChange, 1);
                    (0, node_assert_1.strict)(!segment.properties?.splitOnly);
                    node_assert_1.strict.equal(splitSegment.segmentGroups?.size, 0);
                    node_assert_1.strict.equal(splitSegment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(splitSegment.properties?.secondChange, 1);
                    node_assert_1.strict.equal(splitSegment.properties?.splitOnly, 1);
                });
                it("unsequenced local before remote", () => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "remote", remoteProperty: 1 },
                    }, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 1);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segment.properties?.remoteProperty, 1);
                });
                it("sequenced local", () => {
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 0);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                });
                it("sequenced local before remote", () => {
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "remote", remoteProperty: 1 },
                    }, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 0);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "remote");
                    node_assert_1.strict.equal(segment.properties?.remoteProperty, 1);
                });
                it("three local changes", () => {
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    const props2 = {
                        props: { propertySource: "local2", secondSource: 1 },
                    };
                    mergeTree.annotateRange(annotateStart, annotateEnd, props2, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local2");
                    node_assert_1.strict.equal(segment.properties?.secondSource, 1);
                    const props3 = {
                        props: {
                            thirdSource: 1,
                        },
                    };
                    mergeTree.annotateRange(annotateStart, annotateEnd, props3, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local2");
                    node_assert_1.strict.equal(segment.properties?.secondSource, 1);
                    node_assert_1.strict.equal(segment.properties?.thirdSource, 1);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local2");
                    node_assert_1.strict.equal(segment.properties?.secondSource, 1);
                    node_assert_1.strict.equal(segment.properties?.thirdSource, 1);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props2,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local2");
                    node_assert_1.strict.equal(segment.properties?.secondSource, 1);
                    node_assert_1.strict.equal(segment.properties?.thirdSource, 1);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props3,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local2");
                    node_assert_1.strict.equal(segment.properties?.secondSource, 1);
                    node_assert_1.strict.equal(segment.properties?.thirdSource, 1);
                });
                it("two local changes with interleaved remote", () => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { secondSource: "local2" },
                    }, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "remote", remoteOnly: 1, secondSource: "remote" },
                    }, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.remoteOnly, 1);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "remote");
                    node_assert_1.strict.equal(segment.properties?.secondSource, "local2");
                });
            });
            describe("remote first", () => {
                beforeEach(() => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "remote", remoteProperty: 1 },
                    }, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    (0, node_assert_1.strict)(segmentInfo.segment?.segmentGroups?.size !== 0);
                });
                it("remote only", () => {
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.propertySource, "remote");
                    node_assert_1.strict.equal(segment.properties?.remoteProperty, 1);
                });
                it("split remote", () => {
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const splitSegment = splitAt(mergeTree, annotateStart + 1);
                    (0, segmentInfos_js_1.assertMergeNode)(splitSegment);
                    node_assert_1.strict.notEqual(segmentInfo.segment?.ordinal, splitSegment.ordinal);
                    node_assert_1.strict.equal(splitSegment.properties?.propertySource, "remote");
                    node_assert_1.strict.equal(splitSegment.properties?.remoteProperty, 1);
                });
                it("remote before unsequenced local", () => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "local" },
                    }, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segment.properties?.remoteProperty, 1);
                });
                it("remote before sequenced local", () => {
                    const props = {
                        props: { propertySource: "local" },
                    };
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    (0, node_assert_1.strict)(segmentInfo.segment?.segmentGroups?.empty !== false);
                    mergeTree.annotateRange(annotateStart, annotateEnd, props, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    node_assert_1.strict.equal(segmentInfo.segment?.segmentGroups?.size, 1);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    (0, node_assert_1.strict)(segmentInfo.segment?.segmentGroups?.empty);
                    node_assert_1.strict.equal(segmentInfo.segment?.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segmentInfo.segment?.properties?.remoteProperty, 1);
                });
            });
            describe("local with rewrite first", () => {
                const props = {
                    props: { propertySource: "local" },
                };
                beforeEach(() => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, props, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                });
                it("unsequenced local after unsequenced local", () => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "local2", secondProperty: "local" },
                    }, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local2");
                    node_assert_1.strict.equal(segment.properties?.secondProperty, "local");
                });
                it("unsequenced local before remote", () => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "remote", remoteProperty: 1 },
                    }, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 1);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "local");
                    node_assert_1.strict.equal(segment.properties?.remoteProperty, 1);
                });
                it("sequenced local before remote", () => {
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "remote", remoteProperty: 1 },
                    }, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.segmentGroups?.size, 0);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "remote");
                    node_assert_1.strict.equal(segment.properties?.remoteProperty, 1);
                });
                it("two local changes with interleaved remote", () => {
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { secondSource: "local2" },
                    }, currentSequenceNumber, localClientId, constants_js_1.UnassignedSequenceNumber, undefined);
                    mergeTree.ackPendingSegment({
                        op: {
                            pos1: annotateStart,
                            pos2: annotateEnd,
                            ...props,
                            type: ops_js_1.MergeTreeDeltaType.ANNOTATE,
                        },
                        sequencedMessage: {
                            sequenceNumber: ++currentSequenceNumber,
                        },
                    });
                    mergeTree.annotateRange(annotateStart, annotateEnd, {
                        props: { propertySource: "remote", remoteOnly: 1, secondSource: "remote" },
                    }, currentSequenceNumber, remoteClientId, ++currentSequenceNumber, undefined);
                    const segmentInfo = mergeTree.getContainingSegment(annotateStart, currentSequenceNumber, localClientId);
                    const segment = segmentInfo.segment;
                    node_assert_1.strict.equal(segment.properties?.remoteOnly, 1);
                    node_assert_1.strict.equal(segment.properties?.propertySource, "remote");
                    node_assert_1.strict.equal(segment.properties?.secondSource, "local2");
                });
            });
        });
    });
});
//# sourceMappingURL=mergeTree.annotate.spec.js.map