"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.sequenceNumbersMatch = exports.estimateSocketSize = exports.BatchManager = exports.generateBatchId = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
/** Compose original client ID and client sequence number into BatchId to stamp on the message during reconnect */
function generateBatchId(originalClientId, batchStartCsn) {
    return `${originalClientId}_[${batchStartCsn}]`;
}
exports.generateBatchId = generateBatchId;
/**
 * Estimated size of the stringification overhead for an op accumulated
 * from runtime to loader to the service.
 */
const opOverhead = 200;
/**
 * Helper class that manages partial batch & rollback.
 */
class BatchManager {
    get length() {
        return this.pendingBatch.length;
    }
    get contentSizeInBytes() {
        return this.batchContentSize;
    }
    get sequenceNumbers() {
        return {
            referenceSequenceNumber: this.referenceSequenceNumber,
            clientSequenceNumber: this.clientSequenceNumber,
        };
    }
    get referenceSequenceNumber() {
        return this.pendingBatch.length === 0
            ? undefined
            : // Non null asserting here since we are checking the length above
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.pendingBatch[this.pendingBatch.length - 1].referenceSequenceNumber;
    }
    constructor(options) {
        this.options = options;
        this.pendingBatch = [];
        this.batchContentSize = 0;
        this.hasReentrantOps = false;
    }
    push(message, reentrant, currentClientSequenceNumber) {
        const contentSize = this.batchContentSize + (message.contents?.length ?? 0);
        const opCount = this.pendingBatch.length;
        this.hasReentrantOps = this.hasReentrantOps || reentrant;
        // Attempt to estimate batch size, aka socket message size.
        // Each op has pretty large envelope, estimating to be 200 bytes.
        // Also content will be strigified, and that adds a lot of overhead due to a lot of escape characters.
        // Not taking it into account, as compression work should help there - compressed payload will be
        // initially stored as base64, and that requires only 2 extra escape characters.
        const socketMessageSize = contentSize + opOverhead * opCount;
        if (socketMessageSize >= this.options.hardLimit) {
            return false;
        }
        if (this.pendingBatch.length === 0) {
            this.clientSequenceNumber = currentClientSequenceNumber;
        }
        this.batchContentSize = contentSize;
        this.pendingBatch.push(message);
        return true;
    }
    get empty() {
        return this.pendingBatch.length === 0;
    }
    /**
     * Gets the pending batch and clears state for the next batch.
     */
    popBatch(batchId) {
        const batch = {
            messages: this.pendingBatch,
            contentSizeInBytes: this.batchContentSize,
            referenceSequenceNumber: this.referenceSequenceNumber,
            hasReentrantOps: this.hasReentrantOps,
        };
        this.pendingBatch = [];
        this.batchContentSize = 0;
        this.clientSequenceNumber = undefined;
        this.hasReentrantOps = false;
        return addBatchMetadata(batch, batchId);
    }
    /**
     * Capture the pending state at this point
     */
    checkpoint() {
        const startPoint = this.pendingBatch.length;
        return {
            rollback: (process) => {
                for (let i = this.pendingBatch.length; i > startPoint;) {
                    i--;
                    // Non null asserting here since we are iterating though pendingBatch
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                    const message = this.pendingBatch[i];
                    this.batchContentSize -= message.contents?.length ?? 0;
                    process(message);
                }
                this.pendingBatch.length = startPoint;
            },
        };
    }
}
exports.BatchManager = BatchManager;
const addBatchMetadata = (batch, batchId) => {
    const batchEnd = batch.messages.length - 1;
    const firstMsg = batch.messages[0];
    const lastMsg = batch.messages[batchEnd];
    (0, internal_1.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;
};
/**
 * Estimates the real size in bytes on the socket for a given batch. It assumes that
 * the envelope size (and the size of an empty op) is 200 bytes, taking into account
 * extra overhead from stringification.
 *
 * @param batch - the batch to inspect
 * @returns An estimate of the payload size in bytes which will be produced when the batch is sent over the wire
 */
const estimateSocketSize = (batch) => {
    return batch.contentSizeInBytes + opOverhead * batch.messages.length;
};
exports.estimateSocketSize = estimateSocketSize;
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));
};
exports.sequenceNumbersMatch = sequenceNumbersMatch;
//# sourceMappingURL=batchManager.js.map