/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { IsoBuffer } from "@fluid-internal/client-utils";
import { assert } from "@fluidframework/core-utils/internal";
import { UsageError, createChildLogger } from "@fluidframework/telemetry-utils/internal";
import { compress } from "lz4js";
import { CompressionAlgorithms } from "../containerRuntime.js";
import { estimateSocketSize } from "./batchManager.js";
/**
 * Compresses batches of ops. It generates a single compressed op that contains
 * the contents of each op in the batch. It then submits empty ops for each original
 * op to reserve sequence numbers.
 */
export class OpCompressor {
    constructor(logger) {
        this.logger = createChildLogger({ logger, namespace: "OpCompressor" });
    }
    /**
     * Combines the contents of the batch into a single JSON string and compresses it, putting
     * the resulting string as the first message of the batch. The rest of the messages are
     * empty placeholders to reserve sequence numbers.
     * @param batch - The batch to compress
     * @returns A batch of the same length as the input batch, containing a single compressed message followed by empty placeholders
     */
    compressBatch(batch) {
        assert(batch.contentSizeInBytes > 0 && batch.content.length > 0, 0x5a4 /* Batch should not be empty */);
        const compressionStart = Date.now();
        const contentsAsBuffer = new TextEncoder().encode(this.serializeBatchContents(batch));
        const compressedContents = compress(contentsAsBuffer);
        const compressedContent = IsoBuffer.from(compressedContents).toString("base64");
        const duration = Date.now() - compressionStart;
        const messages = [];
        messages.push({
            ...batch.content[0],
            contents: JSON.stringify({ packedContents: compressedContent }),
            metadata: batch.content[0].metadata,
            compression: CompressionAlgorithms.lz4,
        });
        // Add empty placeholder messages to reserve the sequence numbers
        for (const message of batch.content.slice(1)) {
            messages.push({
                localOpMetadata: message.localOpMetadata,
                metadata: message.metadata,
                referenceSequenceNumber: message.referenceSequenceNumber,
            });
        }
        const compressedBatch = {
            contentSizeInBytes: compressedContent.length,
            content: messages,
            referenceSequenceNumber: batch.referenceSequenceNumber,
        };
        if (batch.contentSizeInBytes > 200000) {
            this.logger.sendPerformanceEvent({
                eventName: "CompressedBatch",
                duration,
                sizeBeforeCompression: batch.contentSizeInBytes,
                sizeAfterCompression: compressedBatch.contentSizeInBytes,
                opCount: compressedBatch.content.length,
                socketSize: estimateSocketSize(compressedBatch),
            });
        }
        return compressedBatch;
    }
    /**
     * Combine the batch's content strings into a single JSON string (a serialized array)
     */
    serializeBatchContents(batch) {
        try {
            // Yields a valid JSON array, since each message.contents is already serialized to JSON
            return `[${batch.content.map((message) => message.contents).join(",")}]`;
        }
        catch (e) {
            if (e.message === "Invalid string length") {
                // This is how JSON.stringify signals that
                // the content size exceeds its capacity
                const error = new UsageError("Payload too large");
                this.logger.sendErrorEvent({
                    eventName: "BatchTooLarge",
                    size: batch.contentSizeInBytes,
                    length: batch.content.length,
                }, error);
                throw error;
            }
            throw e;
        }
    }
}
//# sourceMappingURL=opCompressor.js.map