"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const assert_1 = __importDefault(require("assert"));
const stochastic_test_utils_1 = require("@fluid-private/stochastic-test-utils");
const revertibles_1 = require("../revertibles.cjs");
const mergeTreeNodeWalk_1 = require("../mergeTreeNodeWalk.cjs");
const mergeTreeOperationRunner_1 = require("./mergeTreeOperationRunner.cjs");
const testClient_1 = require("./testClient.cjs");
const testClientLogger_1 = require("./testClientLogger.cjs");
const defaultOptions = {
    initialOps: 5,
    minLength: { min: 1, max: 256, growthFunc: (i) => i * i },
    concurrentOpsWithRevert: { min: 0, max: 8 },
    revertOps: { min: 1, max: 16 },
    ackBeforeRevert: ["None", "Some", "All"],
    rounds: 10,
    operations: [mergeTreeOperationRunner_1.removeRange, mergeTreeOperationRunner_1.annotateRange],
    growthFunc: (i) => i * 2,
};
describe("MergeTree.Client", () => {
    (0, mergeTreeOperationRunner_1.doOverRanges)(defaultOptions, ({ minLength: minLen, concurrentOpsWithRevert, revertOps }) => {
        for (const ackBeforeRevert of defaultOptions.ackBeforeRevert) {
            it(`InitialOps: ${defaultOptions.initialOps} MinLen: ${minLen}  ConcurrentOpsWithRevert: ${concurrentOpsWithRevert} RevertOps: ${revertOps} AckBeforeRevert: ${ackBeforeRevert}`, async () => {
                const random = (0, stochastic_test_utils_1.makeRandom)(minLen, revertOps, [...ackBeforeRevert].reduce((pv, cv) => pv + cv.charCodeAt(0), 0), concurrentOpsWithRevert);
                const clients = (0, testClientLogger_1.createClientsAtInitialState)({
                    initialState: "",
                    options: {},
                }, "A", "B", "C");
                let seq = 0;
                for (let rnd = 0; rnd < defaultOptions.rounds; rnd++) {
                    clients.all.forEach((c) => c.updateMinSeq(seq));
                    const logger = new testClientLogger_1.TestClientLogger(clients.all, `Round ${rnd}`);
                    {
                        // init with random values
                        const initialMsgs = (0, mergeTreeOperationRunner_1.generateOperationMessagesForClients)(random, seq, clients.all, logger, defaultOptions.initialOps, minLen, defaultOptions.operations);
                        seq = (0, mergeTreeOperationRunner_1.applyMessages)(seq, initialMsgs, clients.all, logger);
                    }
                    // cache the base text to ensure we get back to it after revert
                    const undoBaseText = logger.validate({
                        clear: true,
                        errorPrefix: "After Initial Ops",
                    });
                    const clientB_Revertibles = [];
                    const clientBDriver = (0, testClient_1.createRevertDriver)(clients.B);
                    const deltaCallback = (op, delta) => {
                        if (op.sequencedMessage === undefined) {
                            (0, revertibles_1.appendToMergeTreeDeltaRevertibles)(delta, clientB_Revertibles);
                        }
                    };
                    const msgs = [];
                    {
                        clientBDriver.submitOpCallback = (op) => msgs.push([
                            clients.B.makeOpMessage(op, undefined, undefined, undefined, seq),
                            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                            clients.B.peekPendingSegmentGroups(),
                        ]);
                        clients.B.on("delta", deltaCallback);
                        msgs.push(...(0, mergeTreeOperationRunner_1.generateOperationMessagesForClients)(random, seq, [clients.A, clients.B], logger, revertOps, minLen, defaultOptions.operations));
                    }
                    if (concurrentOpsWithRevert > 0) {
                        // add modifications from another client
                        msgs.push(...(0, mergeTreeOperationRunner_1.generateOperationMessagesForClients)(random, seq, [clients.A, clients.C], logger, concurrentOpsWithRevert, minLen, defaultOptions.operations));
                    }
                    let redoBaseText;
                    if (ackBeforeRevert !== "None") {
                        const ackAll = ackBeforeRevert === "All";
                        seq = (0, mergeTreeOperationRunner_1.applyMessages)(seq, msgs.splice(0, ackAll
                            ? msgs.length
                            : random.integer(0, Math.floor(msgs.length / 2))), clients.all, logger);
                        if (ackAll) {
                            redoBaseText = logger.validate({ errorPrefix: "Before Revert Ack" });
                        }
                    }
                    try {
                        (0, revertibles_1.revertMergeTreeDeltaRevertibles)(clientBDriver, clientB_Revertibles.splice(0));
                        seq = (0, mergeTreeOperationRunner_1.applyMessages)(seq, msgs.splice(0), clients.all, logger);
                    }
                    catch (e) {
                        throw logger.addLogsToError(e);
                    }
                    logger.validate({
                        clear: true,
                        baseText: concurrentOpsWithRevert === 0 ? undoBaseText : undefined,
                        errorPrefix: "After Revert (undo)",
                    });
                    try {
                        // reset the callback before the final revert
                        // to avoid accruing any new detached references
                        clients.B.off("delta", deltaCallback);
                        (0, revertibles_1.revertMergeTreeDeltaRevertibles)(clientBDriver, clientB_Revertibles.splice(0));
                        seq = (0, mergeTreeOperationRunner_1.applyMessages)(seq, msgs.splice(0), clients.all, logger);
                        (0, mergeTreeNodeWalk_1.walkAllChildSegments)(clients.B.mergeTree.root, (seg) => {
                            if (seg?.trackingCollection.empty === false) {
                                assert_1.default.notDeepStrictEqual(seg?.trackingCollection.empty, false, "there should be no left over tracking group");
                            }
                            if (seg?.localRefs?.empty === false) {
                                assert_1.default.notDeepStrictEqual(seg?.localRefs?.empty, false, "there should be no left over local references");
                            }
                        });
                        const mergeTreeWithRevert = clients.B.mergeTree;
                        assert_1.default.notDeepStrictEqual(mergeTreeWithRevert.__mergeTreeRevertible?.detachedReferences?.localRefs
                            ?.empty, false, "there should be no left over local references in detached references");
                    }
                    catch (e) {
                        throw logger.addLogsToError(e);
                    }
                    logger.validate({
                        errorPrefix: "After Re-Revert (redo)",
                        baseText: redoBaseText,
                    });
                    logger.dispose();
                }
            });
        }
    });
});
//# sourceMappingURL=revertibleFarm.spec.cjs.map