"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SnapshotLegacy = void 0;
const common_utils_1 = require("@fluidframework/common-utils");
const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
const constants_1 = require("./constants");
const properties_1 = require("./properties");
const snapshotChunks_1 = require("./snapshotChunks");
class SnapshotLegacy {
    constructor(mergeTree, logger, filename, onCompletion) {
        var _a, _b;
        this.mergeTree = mergeTree;
        this.filename = filename;
        this.onCompletion = onCompletion;
        this.logger = telemetry_utils_1.ChildLogger.create(logger, "Snapshot");
        this.chunkSize = (_b = (_a = mergeTree === null || mergeTree === void 0 ? void 0 : mergeTree.options) === null || _a === void 0 ? void 0 : _a.mergeTreeSnapshotChunkSize) !== null && _b !== void 0 ? _b : SnapshotLegacy.sizeOfFirstChunk;
    }
    getSeqLengthSegs(allSegments, allLengths, approxSequenceLength, startIndex = 0) {
        const segs = [];
        let sequenceLength = 0;
        let segCount = 0;
        while ((sequenceLength < approxSequenceLength) && ((startIndex + segCount) < allSegments.length)) {
            const pseg = allSegments[startIndex + segCount];
            segs.push(pseg);
            sequenceLength += allLengths[startIndex + segCount];
            segCount++;
        }
        return {
            version: undefined,
            chunkStartSegmentIndex: startIndex,
            chunkSegmentCount: segCount,
            chunkLengthChars: sequenceLength,
            totalLengthChars: this.header.segmentsTotalLength,
            totalSegmentCount: allSegments.length,
            chunkSequenceNumber: this.header.seq,
            segmentTexts: segs,
        };
    }
    /**
     * Emits the snapshot to an ITree. If provided the optional IFluidSerializer will be used when serializing
     * the summary data rather than JSON.stringify.
     */
    emit(catchUpMsgs, serializer, bind) {
        var _a, _b;
        const chunk1 = this.getSeqLengthSegs(this.segments, this.segmentLengths, this.chunkSize);
        let length = chunk1.chunkLengthChars;
        let segments = chunk1.chunkSegmentCount;
        const tree = {
            entries: [
                {
                    mode: protocol_definitions_1.FileMode.File,
                    path: SnapshotLegacy.header,
                    type: protocol_definitions_1.TreeEntry.Blob,
                    value: {
                        contents: snapshotChunks_1.serializeAsMinSupportedVersion(SnapshotLegacy.header, chunk1, this.logger, this.mergeTree.options, serializer, bind),
                        encoding: "utf-8",
                    },
                },
            ],
        };
        if (chunk1.chunkSegmentCount < chunk1.totalSegmentCount) {
            const chunk2 = this.getSeqLengthSegs(this.segments, this.segmentLengths, this.header.segmentsTotalLength, chunk1.chunkSegmentCount);
            length += chunk2.chunkLengthChars;
            segments += chunk2.chunkSegmentCount;
            tree.entries.push({
                mode: protocol_definitions_1.FileMode.File,
                path: SnapshotLegacy.body,
                type: protocol_definitions_1.TreeEntry.Blob,
                value: {
                    contents: snapshotChunks_1.serializeAsMinSupportedVersion(SnapshotLegacy.body, chunk2, this.logger, this.mergeTree.options, serializer, bind),
                    encoding: "utf-8",
                },
            });
        }
        common_utils_1.assert(length === this.header.segmentsTotalLength, 0x05d /* "emit: mismatch in segmentsTotalLength" */);
        common_utils_1.assert(segments === chunk1.totalSegmentCount, 0x05e /* "emit: mismatch in totalSegmentCount" */);
        if (catchUpMsgs !== undefined && catchUpMsgs.length > 0) {
            tree.entries.push({
                mode: protocol_definitions_1.FileMode.File,
                path: (_b = (_a = this.mergeTree.options) === null || _a === void 0 ? void 0 : _a.catchUpBlobName) !== null && _b !== void 0 ? _b : SnapshotLegacy.catchupOps,
                type: protocol_definitions_1.TreeEntry.Blob,
                value: {
                    contents: serializer ? serializer.stringify(catchUpMsgs, bind) : JSON.stringify(catchUpMsgs),
                    encoding: "utf-8",
                },
            });
        }
        return tree;
    }
    extractSync() {
        const collabWindow = this.mergeTree.getCollabWindow();
        this.seq = collabWindow.minSeq;
        this.header = {
            segmentsTotalLength: this.mergeTree.getLength(this.mergeTree.collabWindow.minSeq, constants_1.NonCollabClient),
            seq: this.mergeTree.collabWindow.minSeq,
        };
        const segs = [];
        let prev;
        const extractSegment = 
        // eslint-disable-next-line max-len
        (segment, pos, refSeq, clientId, start, end) => {
            // eslint-disable-next-line eqeqeq
            if ((segment.seq != constants_1.UnassignedSequenceNumber) && (segment.seq <= this.seq) &&
                // eslint-disable-next-line eqeqeq
                ((segment.removedSeq === undefined) || (segment.removedSeq == constants_1.UnassignedSequenceNumber) ||
                    (segment.removedSeq > this.seq))) {
                if (prev && prev.canAppend(segment)
                    && properties_1.matchProperties(prev.properties, segment.properties)) {
                    prev = prev.clone();
                    prev.append(segment.clone());
                }
                else {
                    if (prev) {
                        segs.push(prev);
                    }
                    prev = segment;
                }
            }
            return true;
        };
        this.mergeTree.map({ leaf: extractSegment }, this.seq, constants_1.NonCollabClient, undefined);
        if (prev) {
            segs.push(prev);
        }
        this.segments = [];
        this.segmentLengths = [];
        let totalLength = 0;
        segs.map((segment) => {
            totalLength += segment.cachedLength;
            this.segments.push(segment.toJSONObject());
            this.segmentLengths.push(segment.cachedLength);
        });
        // We observed this.header.segmentsTotalLength < totalLength to happen in some cases
        // When this condition happens, we might not write out all segments in getSeqLengthSegs()
        // when writing out "body". Issue #1995 tracks following up on the core of the problem.
        // In the meantime, this code makes sure we will write out all segments properly
        // eslint-disable-next-line eqeqeq
        if (this.header.segmentsTotalLength != totalLength) {
            this.logger.sendErrorEvent({
                eventName: "SegmentsTotalLengthMismatch",
                totalLength,
                segmentsTotalLength: this.header.segmentsTotalLength,
            });
            this.header.segmentsTotalLength = totalLength;
        }
        return this.segments;
    }
}
exports.SnapshotLegacy = SnapshotLegacy;
SnapshotLegacy.header = "header";
SnapshotLegacy.body = "body";
SnapshotLegacy.catchupOps = "catchupOps";
// Split snapshot into two entries - headers (small) and body (overflow) for faster loading initial content
// Please note that this number has no direct relationship to anything other than size of raw text (characters).
// As we produce json for the blob (and then send over the wire compressed), this number
// is really hard to correlate with any actual metric that matters (like bytes over the wire).
// For test with small number of chunks it would be closer to blob size,
// for very chunky text, blob size can easily be 4x-8x of that number.
SnapshotLegacy.sizeOfFirstChunk = 10000;
//# sourceMappingURL=snapshotlegacy.js.map