/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { createChildLogger, } from "@fluidframework/telemetry-utils/internal";
function isGroupContents(opContents) {
    return (opContents?.type ===
        OpGroupingManager.groupedBatchOp);
}
export function isGroupedBatch(op) {
    return isGroupContents(op.contents);
}
export class OpGroupingManager {
    constructor(config, logger) {
        this.config = config;
        this.logger = createChildLogger({ logger, namespace: "OpGroupingManager" });
    }
    /**
     * Creates a new batch with a single message of type "groupedBatch" and empty contents.
     * This is needed as a placeholder if a batch becomes empty on resubmit, but we are tracking batch IDs.
     * @param resubmittingBatchId - batch ID of the resubmitting batch
     * @param referenceSequenceNumber - reference sequence number
     * @returns - The outbound batch as well as the interior placeholder message
     */
    createEmptyGroupedBatch(resubmittingBatchId, referenceSequenceNumber) {
        assert(this.config.groupedBatchingEnabled, 0xa00 /* cannot create empty grouped batch when grouped batching is disabled */);
        const emptyGroupedBatch = {
            type: "groupedBatch",
            contents: [],
        };
        const serializedOp = JSON.stringify(emptyGroupedBatch);
        const placeholderMessage = {
            metadata: { batchId: resubmittingBatchId },
            localOpMetadata: { emptyBatch: true },
            referenceSequenceNumber,
            runtimeOp: emptyGroupedBatch,
        };
        const outboundBatch = {
            contentSizeInBytes: 0,
            messages: [{ ...placeholderMessage, runtimeOp: undefined, contents: serializedOp }],
            referenceSequenceNumber,
        };
        return { outboundBatch, placeholderMessage };
    }
    /**
     * Converts the given batch into a "grouped batch" - a batch with a single message of type "groupedBatch",
     * with contents being an array of the original batch's messages.
     *
     * If the batch already has only 1 message, it is returned as-is.
     *
     * @remarks - Remember that a BatchMessage has its content JSON serialized, so the incoming batch message contents
     * must be parsed first, and then the type and contents mentioned above are hidden in that JSON serialization.
     */
    groupBatch(batch) {
        assert(this.groupedBatchingEnabled(), 0xb79 /* grouping disabled! */);
        assert(batch.messages.length > 0, 0xb7a /* Unexpected attempt to group an empty batch */);
        if (batch.messages.length === 1) {
            return batch;
        }
        if (batch.messages.length >= 1000) {
            this.logger.sendTelemetryEvent({
                eventName: "GroupLargeBatch",
                length: batch.messages.length,
                reentrant: batch.hasReentrantOps,
                referenceSequenceNumber: batch.messages[0].referenceSequenceNumber,
            });
        }
        // We expect this will be on the first message, if present at all.
        let groupedBatchId;
        for (const message of batch.messages) {
            if (message.metadata) {
                const { batch: _batch, batchId, ...rest } = message.metadata;
                if (batchId) {
                    groupedBatchId = batchId;
                }
                assert(Object.keys(rest).length === 0, 0x5dd /* cannot group ops with metadata */);
            }
        }
        const serializedContent = JSON.stringify({
            type: OpGroupingManager.groupedBatchOp,
            contents: batch.messages.map((message) => ({
                contents: message.contents === undefined ? undefined : JSON.parse(message.contents),
                metadata: message.metadata,
                compression: message.compression,
            })),
        });
        const groupedBatch = {
            ...batch,
            messages: [
                {
                    metadata: { batchId: groupedBatchId },
                    referenceSequenceNumber: batch.messages[0].referenceSequenceNumber,
                    contents: serializedContent,
                },
            ],
        };
        return groupedBatch;
    }
    ungroupOp(op) {
        assert(isGroupContents(op.contents), 0x947 /* can only ungroup a grouped batch */);
        const contents = op.contents;
        let fakeCsn = 1;
        return contents.contents.map((subMessage) => ({
            ...op,
            clientSequenceNumber: fakeCsn++,
            contents: subMessage.contents,
            metadata: subMessage.metadata,
            compression: subMessage.compression,
        }));
    }
    groupedBatchingEnabled() {
        return this.config.groupedBatchingEnabled;
    }
}
OpGroupingManager.groupedBatchOp = "groupedBatch";
//# sourceMappingURL=opGroupingManager.js.map