/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { LoggingError, tagData, TelemetryDataTag, } from "@fluidframework/telemetry-utils/internal";
import { asBatchMetadata } from "../metadata.js";
import { serializeOp } from "./opSerialization.js";
/**
 * Compose original client ID and client sequence number into BatchId to stamp on the message during reconnect
 */
export function generateBatchId(originalClientId, batchStartCsn) {
    return `${originalClientId}_[${batchStartCsn}]`;
}
/**
 * Get the effective batch ID for the input argument.
 * Supports either an IPendingMessage or BatchStartInfo.
 * If the batch ID is explicitly present, return it.
 * Otherwise, generate a new batch ID using the client ID and batch start CSN.
 */
export function getEffectiveBatchId(pendingMessageOrBatchStartInfo) {
    if ("localOpMetadata" in pendingMessageOrBatchStartInfo) {
        const pendingMessage = pendingMessageOrBatchStartInfo;
        return (asBatchMetadata(pendingMessage.opMetadata)?.batchId ??
            generateBatchId(pendingMessage.batchInfo.clientId, pendingMessage.batchInfo.batchStartCsn));
    }
    const batchStart = pendingMessageOrBatchStartInfo;
    return batchStart.batchId ?? generateBatchId(batchStart.clientId, batchStart.batchStartCsn);
}
/**
 * Helper class that manages partial batch & rollback.
 */
export class BatchManager {
    get length() {
        return this.pendingBatch.length;
    }
    get sequenceNumbers() {
        return {
            referenceSequenceNumber: this.referenceSequenceNumber,
            clientSequenceNumber: this.clientSequenceNumber,
        };
    }
    get referenceSequenceNumber() {
        return this.pendingBatch.length === 0
            ? undefined
            : // NOTE: In case of reentrant ops, there could be multiple reference sequence numbers, but we will rebase before submitting.
                this.pendingBatch[this.pendingBatch.length - 1].referenceSequenceNumber;
    }
    constructor(options) {
        this.options = options;
        this.pendingBatch = [];
        this.hasReentrantOps = false;
    }
    push(message, reentrant, currentClientSequenceNumber) {
        this.hasReentrantOps = this.hasReentrantOps || reentrant;
        if (this.pendingBatch.length === 0) {
            this.clientSequenceNumber = currentClientSequenceNumber;
        }
        this.pendingBatch.push(message);
    }
    get empty() {
        return this.pendingBatch.length === 0;
    }
    /**
     * Gets the pending batch and clears state for the next batch.
     */
    popBatch(batchId) {
        assert(this.pendingBatch[0] !== undefined, 0xb8a /* expected non-empty batch */);
        const batch = {
            messages: this.pendingBatch,
            referenceSequenceNumber: this.referenceSequenceNumber,
            hasReentrantOps: this.hasReentrantOps,
            staged: this.pendingBatch[0].staged,
        };
        this.pendingBatch = [];
        this.clientSequenceNumber = undefined;
        this.hasReentrantOps = false;
        return addBatchMetadata(batch, batchId);
    }
    /**
     * Capture the pending state at this point
     */
    checkpoint() {
        const startSequenceNumber = this.clientSequenceNumber;
        const startPoint = this.pendingBatch.length;
        return {
            rollback: (process) => {
                this.clientSequenceNumber = startSequenceNumber;
                const rollbackOpsLifo = this.pendingBatch.splice(startPoint).reverse();
                for (const message of rollbackOpsLifo) {
                    process(message);
                }
                const count = this.pendingBatch.length - startPoint;
                if (count !== 0) {
                    throw new LoggingError("Ops generated during rollback", {
                        count,
                        ...tagData(TelemetryDataTag.UserData, {
                            ops: serializeOp(this.pendingBatch.slice(startPoint).map((b) => b.runtimeOp)),
                        }),
                    });
                }
            },
        };
    }
}
const addBatchMetadata = (batch, batchId) => {
    const batchEnd = batch.messages.length - 1;
    const firstMsg = batch.messages[0];
    const lastMsg = batch.messages[batchEnd];
    assert(firstMsg !== undefined && lastMsg !== undefined, 0x9d1 /* expected non-empty batch */);
    const firstMetadata = firstMsg.metadata ?? {};
    const lastMetadata = lastMsg.metadata ?? {};
    // Multi-message batches: mark the first and last messages with the "batch" flag indicating batch start/end
    if (batch.messages.length > 1) {
        firstMetadata.batch = true;
        lastMetadata.batch = false;
        firstMsg.metadata = firstMetadata;
        lastMsg.metadata = lastMetadata;
    }
    // If batchId is provided (e.g. in case of resubmit): stamp it on the first message
    if (batchId !== undefined) {
        firstMetadata.batchId = batchId;
        firstMsg.metadata = firstMetadata;
    }
    return batch;
};
export const sequenceNumbersMatch = (seqNums, otherSeqNums) => {
    return ((seqNums.referenceSequenceNumber === undefined ||
        otherSeqNums.referenceSequenceNumber === undefined ||
        seqNums.referenceSequenceNumber === otherSeqNums.referenceSequenceNumber) &&
        (seqNums.clientSequenceNumber === undefined ||
            otherSeqNums.clientSequenceNumber === undefined ||
            seqNums.clientSequenceNumber === otherSeqNums.clientSequenceNumber));
};
//# sourceMappingURL=batchManager.js.map