"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 node_assert_1 = require("node:assert");
const constants_js_1 = require("../constants.js");
const opBuilder_js_1 = require("../opBuilder.js");
const segmentInfos_js_1 = require("../segmentInfos.js");
const textSegment_js_1 = require("../textSegment.js");
const testClient_js_1 = require("./testClient.js");
describe("MergeTree.markRangeRemoved", () => {
    let client;
    beforeEach(() => {
        client = new testClient_js_1.TestClient();
        client.startOrUpdateCollaboration("local");
        for (const char of "hello world") {
            client.applyMsg(client.makeOpMessage(client.insertTextLocal(client.getLength(), char), client.getCurrentSeq() + 1));
        }
        node_assert_1.strict.equal(client.getText(), "hello world");
    });
    it("local remove followed by local insert", () => {
        client.removeRangeLocal(0, client.getLength());
        node_assert_1.strict.equal(client.getText(), "");
        client.insertTextLocal(0, "text");
        node_assert_1.strict.equal(client.getText(), "text");
    });
    it("local insert followed by local remove", () => {
        client.insertTextLocal(0, "text");
        node_assert_1.strict.equal(client.getText(), "texthello world");
        client.removeRangeLocal(0, client.getLength());
        node_assert_1.strict.equal(client.getText(), "");
    });
    it("remote remove followed by local insert", () => {
        client.applyMsg(client.makeOpMessage((0, opBuilder_js_1.createRemoveRangeOp)(0, client.getLength()), client.mergeTree.collabWindow.currentSeq + 1, client.mergeTree.collabWindow.currentSeq, "remote"));
        node_assert_1.strict.equal(client.getText(), "");
        client.insertTextLocal(0, "text");
        node_assert_1.strict.equal(client.getText(), "text");
    });
    it("local remove followed by remote insert", () => {
        client.removeRangeLocal(0, client.getLength());
        node_assert_1.strict.equal(client.getText(), "");
        client.applyMsg(client.makeOpMessage((0, opBuilder_js_1.createInsertSegmentOp)(0, textSegment_js_1.TextSegment.make("text")), client.mergeTree.collabWindow.currentSeq + 1, client.mergeTree.collabWindow.currentSeq, "remote"));
        node_assert_1.strict.equal(client.getText(), "text");
    });
    it("local remove followed by remote overlapping remove", () => {
        const originalSeq = client.getCurrentSeq();
        let seq = originalSeq;
        const remoteDeleteMessage = client.makeOpMessage((0, opBuilder_js_1.createRemoveRangeOp)(0, client.getLength()), ++seq, undefined, "remote");
        const segmentExpectedRemovedSeq = seq;
        const { segment } = client.getContainingSegment(0) ?? {};
        (0, node_assert_1.strict)(segment !== undefined, "expected to find segment");
        const localDeleteMessage = client.makeOpMessage(client.removeRangeLocal(0, client.getLength()), ++seq, originalSeq /* refSeq */);
        node_assert_1.strict.equal(client.getText(), "");
        (0, segmentInfos_js_1.assertRemoved)(segment);
        const localRemoveInfo = segment.removes[0];
        node_assert_1.strict.equal(localRemoveInfo.seq, constants_js_1.UnassignedSequenceNumber);
        (0, node_assert_1.strict)(localRemoveInfo.localSeq !== undefined);
        client.applyMsg(remoteDeleteMessage);
        const remoteRemoveInfo = segment.removes[0];
        node_assert_1.strict.deepEqual(segment.removes[1], localRemoveInfo);
        node_assert_1.strict.equal(remoteRemoveInfo.seq, segmentExpectedRemovedSeq);
        node_assert_1.strict.equal(client.getText(), "");
        // localRemovedSeq should remain on the segment until the local removal has been acked.
        // This ensures there's enough information to determine segment length in the case of
        // reconnect.
        client.applyMsg(localDeleteMessage);
        const finalRemoveInfo = segment.removes[0];
        node_assert_1.strict.equal(segment.removes.length, 2);
        node_assert_1.strict.equal(finalRemoveInfo.seq, segmentExpectedRemovedSeq);
        node_assert_1.strict.equal(finalRemoveInfo.localSeq, undefined);
        node_assert_1.strict.equal(client.getText(), "");
    });
    it("remote remove followed by remote insert", () => {
        const removeMsg = client.makeOpMessage((0, opBuilder_js_1.createRemoveRangeOp)(0, client.getLength()), client.mergeTree.collabWindow.currentSeq + 1, client.mergeTree.collabWindow.currentSeq, "remote2");
        const insertMsg = client.makeOpMessage((0, opBuilder_js_1.createInsertSegmentOp)(0, textSegment_js_1.TextSegment.make("text")), client.mergeTree.collabWindow.currentSeq + 2, client.mergeTree.collabWindow.currentSeq, "remote");
        client.applyMsg(removeMsg);
        client.applyMsg(insertMsg);
        node_assert_1.strict.equal(client.getText(), "text");
    });
    it("remote insert followed by remote remove", () => {
        const insertMsg = client.makeOpMessage((0, opBuilder_js_1.createInsertSegmentOp)(0, textSegment_js_1.TextSegment.make("text")), client.mergeTree.collabWindow.currentSeq + 1, client.mergeTree.collabWindow.currentSeq, "remote");
        const removeMsg = client.makeOpMessage((0, opBuilder_js_1.createRemoveRangeOp)(0, client.getLength()), client.mergeTree.collabWindow.currentSeq + 2, client.mergeTree.collabWindow.currentSeq, "remote2");
        client.applyMsg(insertMsg);
        client.applyMsg(removeMsg);
        node_assert_1.strict.equal(client.getText(), "text");
    });
    it("local and remote clients race to insert at position of removed segment", () => {
        // Note: This test constructs its own TestClients to avoid being initialized with "hello world".
        // First we run through the ops from the perspective of a passive observer (i.e., all operations are remote).
        const expected = new testClient_js_1.TestClient();
        expected.startOrUpdateCollaboration("3");
        {
            let seq = 0;
            // Client 1 locally inserts and removes the letter "a".
            expected.insertTextRemote(0, "a", undefined, ++seq, 
            /* refSeq: */ 0, 
            /* longClientId: */ "1");
            expected.removeRangeRemote(0, 1, ++seq, /* refSeq: */ 0, /* longClientId: */ "1");
            const refSeqAt2 = expected.getCurrentSeq();
            // In parallel, Client 2 inserted "x" without knowledge of Client 1's insertion/removal.
            expected.insertTextRemote(0, "X", undefined, ++seq, 
            /* refSeq: */ 0, 
            /* longClientId: */ "2");
            // Client 1 inserts "c" having received acks for its own edits, but has not yet having
            // observed the insertion of "X" from client 2.
            expected.insertTextRemote(0, "c", undefined, ++seq, refSeqAt2, /* longClientId: */ "1");
        }
        // Next, we run through the same sequence from the perspective of client 1:
        const actual = new testClient_js_1.TestClient();
        actual.startOrUpdateCollaboration("1");
        {
            let seq = 0;
            // Client 1 locally inserts and removes the letter "a".
            const op1 = actual.insertTextLocal(0, "a");
            const op2 = actual.removeRangeLocal(0, 1);
            // Client 1 receives ACKs for op1 and op2.
            actual.applyMsg(actual.makeOpMessage(op1, ++seq, /* refSeq: */ 0));
            actual.applyMsg(actual.makeOpMessage(op2, ++seq, /* refSeq: */ 0));
            const refSeqAt2 = actual.getCurrentSeq();
            // Client 1 locally inserts "c".
            const op4 = actual.insertTextLocal(0, "c");
            // Client 1 then processes the parallel insertion of "X" from Client 2 at refSeq=0
            actual.insertTextRemote(0, "X", undefined, ++seq, 
            /* refSeq: */ 0, 
            /* longClientId: */ "2");
            // Finally, client 1 receives the ack for its insertion of "c".
            actual.applyMsg(actual.makeOpMessage(op4, ++seq, refSeqAt2));
        }
        node_assert_1.strict.equal(actual.getText(), expected.getText());
    });
});
//# sourceMappingURL=mergeTree.markRangeRemoved.spec.js.map