"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpCompressor = void 0;
const client_utils_1 = require("@fluid-internal/client-utils");
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const lz4js_1 = require("lz4js");
const containerRuntime_js_1 = require("../containerRuntime.js");
const batchManager_js_1 = require("./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.
 */
class OpCompressor {
    constructor(logger) {
        this.logger = (0, internal_2.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) {
        (0, internal_1.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 = (0, lz4js_1.compress)(contentsAsBuffer);
        const compressedContent = client_utils_1.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: containerRuntime_js_1.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: (0, batchManager_js_1.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 internal_2.UsageError("Payload too large");
                this.logger.sendErrorEvent({
                    eventName: "BatchTooLarge",
                    size: batch.contentSizeInBytes,
                    length: batch.content.length,
                }, error);
                throw error;
            }
            throw e;
        }
    }
}
exports.OpCompressor = OpCompressor;
//# sourceMappingURL=opCompressor.js.map