"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultOptions = void 0;
const stochastic_test_utils_1 = require("@fluid-private/stochastic-test-utils");
const ops_js_1 = require("../ops.js");
const mergeTreeOperationRunner_js_1 = require("./mergeTreeOperationRunner.js");
const testClient_js_1 = require("./testClient.js");
const testClientLogger_js_1 = require("./testClientLogger.js");
function applyMessagesWithReconnect(startingSeq, messageDatas, clients, stashClients) {
    let seq = startingSeq;
    const reconnectClientMsgs = [];
    let minSeq = 0;
    // apply ops as stashed ops except for client #1
    const stashedOps = [];
    for (const messageData of messageDatas) {
        if (messageData[0].clientId !== clients[1].longClientId) {
            const index = clients
                .map((c) => c.longClientId)
                .indexOf(messageData[0].clientId);
            const op = messageData[0].contents;
            stashClients[index].applyStashedOp(op);
            stashedOps.push([
                op,
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                stashClients[index].peekPendingSegmentGroups(op.type === ops_js_1.MergeTreeDeltaType.GROUP ? op.ops.length : 1),
                index,
            ]);
        }
    }
    // this should put all stash clients (except #1) in the same state as the
    // respective normal clients, having local changes only.
    for (let i = 0; i < clients.length; ++i) {
        if (i !== 1) {
            testClientLogger_js_1.TestClientLogger.validate([clients[i], stashClients[i]]);
        }
    }
    testClientLogger_js_1.TestClientLogger.validate([clients[0], stashClients[1]]);
    // apply the ops to the normal clients. they will all be the same now,
    // except #1 which has local changes other clients haven't seen yet
    for (const [message, sg] of messageDatas) {
        if (message.clientId === clients[1].longClientId) {
            reconnectClientMsgs.push([message.contents, sg]);
        }
        else {
            message.sequenceNumber = ++seq;
            for (const c of clients)
                c.applyMsg(message);
            minSeq = message.minimumSequenceNumber;
        }
    }
    // regenerate the ops that were applied as stashed ops. this simulates resubmit()
    const regeneratedStashedOps = stashedOps.map((op) => stashClients[op[2]].makeOpMessage(stashClients[op[2]].regeneratePendingOp(op[0], op[1])));
    // apply the regenerated stashed ops
    let stashedOpSeq = startingSeq;
    for (const msg of regeneratedStashedOps) {
        msg.sequenceNumber = ++stashedOpSeq;
        for (const c of stashClients)
            c.applyMsg(msg);
    }
    // all stash and normal clients should now be in the same state,
    // except #1 (normal) which still has local changes
    testClientLogger_js_1.TestClientLogger.validate([...clients.filter((_, i) => i !== 1), ...stashClients]);
    // regenerate ops for client #1
    const reconnectMsgs = [];
    for (const opData of reconnectClientMsgs) {
        const newMsg = clients[1].makeOpMessage(clients[1].regeneratePendingOp(opData[0], opData[1]));
        newMsg.minimumSequenceNumber = minSeq;
        reconnectMsgs.push(newMsg);
    }
    // apply regenerated ops as stashed ops for client #1
    const stashedRegeneratedOps = reconnectMsgs.map((message) => {
        const op = message.contents;
        stashClients[1].applyStashedOp(op);
        return [
            op,
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            stashClients[1].peekPendingSegmentGroups(op.type === ops_js_1.MergeTreeDeltaType.GROUP ? op.ops.length : 1),
        ];
    });
    // now both clients at index 1 should be the same
    testClientLogger_js_1.TestClientLogger.validate([clients[1], stashClients[1]]);
    // apply the regenerated ops from client #1
    for (const message of reconnectMsgs) {
        message.sequenceNumber = ++seq;
        for (const c of clients)
            c.applyMsg(message);
    }
    // resubmit regenerated stashed ops
    const reRegeneratedStashedMessages = stashedRegeneratedOps.map((stashedOp) => stashClients[1].makeOpMessage(stashClients[1].regeneratePendingOp(stashedOp[0], stashedOp[1])));
    for (const reRegeneratedStashedOp of reRegeneratedStashedMessages) {
        reRegeneratedStashedOp.sequenceNumber = ++stashedOpSeq;
        for (const c of stashClients)
            c.applyMsg(reRegeneratedStashedOp);
    }
    // all clients should now be the same
    testClientLogger_js_1.TestClientLogger.validate([...clients, ...stashClients]);
    return seq;
}
exports.defaultOptions = {
    minLength: 16,
    clients: { min: 3, max: 12 },
    opsPerRoundRange: { min: 40, max: 120 },
    rounds: 3,
    operations: [mergeTreeOperationRunner_js_1.annotateRange, mergeTreeOperationRunner_js_1.removeRange, mergeTreeOperationRunner_js_1.insert],
    growthFunc: (input) => input * 2,
};
// Generate a list of single character client names, support up to 69 clients
const clientNames = (0, mergeTreeOperationRunner_js_1.generateClientNames)();
function runApplyStashedOpFarmTests(opts, extraSeed) {
    (0, mergeTreeOperationRunner_js_1.doOverRange)(opts.clients, opts.growthFunc.bind(opts), (clientCount) => {
        it(`applyStashedOpFarm_${clientCount}`, async () => {
            const random = (0, stochastic_test_utils_1.makeRandom)(0xdeadbeef, 0xfeedbed, clientCount, extraSeed ?? 0);
            const testOpts = { ...opts };
            if (extraSeed) {
                testOpts.resultsFilePostfix ??= "";
                testOpts.resultsFilePostfix += extraSeed;
            }
            const clients = [new testClient_js_1.TestClient({ mergeTreeEnableAnnotateAdjust: true })];
            // This test is based on reconnectFarm, but we keep a second set of clients. For
            // these clients, we apply the generated ops as stashed ops, then regenerate
            // them to simulate resubmit(), then apply them. In the end, they should arrive
            // at the same state as the "normal" set of clients
            let stashClients = [];
            for (const [i, c] of clients.entries())
                c.startOrUpdateCollaboration(clientNames[i]);
            stashClients = [new testClient_js_1.TestClient({ mergeTreeEnableAnnotateAdjust: true })];
            for (const [i, c] of stashClients.entries())
                c.startOrUpdateCollaboration(clientNames[i]);
            let seq = 0;
            for (const c of clients)
                c.updateMinSeq(seq);
            for (const c of stashClients)
                c.updateMinSeq(seq);
            // Add double the number of clients each iteration
            const targetClients = Math.max(opts.clients.min, clientCount);
            for (let cc = clients.length; cc < targetClients; cc++) {
                const newClient = await testClient_js_1.TestClient.createFromClientSnapshot(clients[0], clientNames[cc]);
                clients.push(newClient);
                // add 1 stash client per normal client
                const anotherNewClient = await testClient_js_1.TestClient.createFromClientSnapshot(clients[0], clientNames[cc]);
                stashClients.push(anotherNewClient);
            }
            seq = (0, mergeTreeOperationRunner_js_1.runMergeTreeOperationRunner)(random, seq, clients, opts.minLength, opts, (s, m, c) => applyMessagesWithReconnect(s, m, c, stashClients));
        }).timeout(30 * 1000);
    });
}
(0, stochastic_test_utils_1.describeFuzz)("MergeTree.Client", ({ testCount }) => {
    const opts = exports.defaultOptions;
    if (testCount > 1) {
        (0, mergeTreeOperationRunner_js_1.doOverRange)({ min: 0, max: testCount - 1 }, (x) => x + 1, (seed) => {
            describe(`with seed ${seed}`, () => {
                runApplyStashedOpFarmTests(opts, seed);
            });
        });
    }
    else {
        runApplyStashedOpFarmTests(opts);
    }
});
//# sourceMappingURL=client.applyStashedOpFarm.spec.js.map