"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ForestSummarizer = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = require("../../shared-tree-core/index.js");
const index_js_3 = require("../../util/index.js");
// eslint-disable-next-line import-x/no-internal-modules
const chunkTree_js_1 = require("../chunked-forest/chunkTree.js");
const index_js_4 = require("../chunked-forest/index.js");
const treeCompressionUtils_js_1 = require("../treeCompressionUtils.js");
const codec_js_1 = require("./codec.js");
const incrementalSummaryBuilder_js_1 = require("./incrementalSummaryBuilder.js");
const summaryFormatCommon_js_1 = require("./summaryFormatCommon.js");
const summaryTypes_js_1 = require("./summaryTypes.js");
/**
 * Provides methods for summarizing and loading a forest.
 */
class ForestSummarizer extends index_js_2.VersionedSummarizer {
    /**
     * @param encoderContext - The schema if provided here must be mutated by the caller to keep it up to date.
     */
    constructor(forest, revisionTagCodec, encoderContext, options, idCompressor, initialSequenceNumber, shouldEncodeIncrementally = index_js_4.defaultIncrementalEncodingPolicy) {
        super(summaryFormatCommon_js_1.forestSummaryKey, (0, summaryTypes_js_1.minVersionToForestSummaryFormatVersion)(options.minVersionForCollab), summaryFormatCommon_js_1.supportedForestSummaryFormatVersions, true /* supportPreVersioningFormat */);
        this.forest = forest;
        this.revisionTagCodec = revisionTagCodec;
        this.encoderContext = encoderContext;
        this.idCompressor = idCompressor;
        this.codec = codec_js_1.forestCodecBuilder.build(options);
        const summaryFormatWriteVersion = (0, summaryTypes_js_1.minVersionToForestSummaryFormatVersion)(options.minVersionForCollab);
        this.forestRootSummaryContentKey = (0, summaryTypes_js_1.getForestRootSummaryContentKey)(summaryFormatWriteVersion);
        // Incremental summary is supported in ForestSummaryFormatVersion.v3 onwards.
        // Note that even in versions that support it, it is possible that the
        // FieldBatchCodec will not use incremental encoding (for example if using its v1 formats which does not support it).
        const enableIncrementalSummary = summaryFormatWriteVersion >= summaryFormatCommon_js_1.ForestSummaryFormatVersion.v3 &&
            encoderContext.encodeType === treeCompressionUtils_js_1.TreeCompressionStrategy.CompressedIncremental;
        this.incrementalSummaryBuilder = new incrementalSummaryBuilder_js_1.ForestIncrementalSummaryBuilder(enableIncrementalSummary, (cursor) => this.forest.chunkField(cursor), shouldEncodeIncrementally, initialSequenceNumber);
    }
    /**
     * Summarization of the forest's tree content.
     * @returns a summary tree containing the forest's tree content.
     * @remarks
     * If incremental summary is disabled, all the content will be added to a single summary blob.
     * If incremental summary is enabled, the summary will be a tree.
     * See {@link ForestIncrementalSummaryBuilder} for details of what this tree looks like.
     *
     * TODO: when perf matters, this should be replaced with a chunked async version using a binary format.
     */
    summarizeInternal(props) {
        const { stringify, fullTree = false, incrementalSummaryContext, builder } = props;
        const rootCursor = this.forest.getCursorAboveDetachedFields();
        const fieldMap = new Map();
        // TODO: Encode all detached fields in one operation for better performance and compression
        (0, index_js_1.forEachField)(rootCursor, (cursor) => {
            const key = cursor.getFieldKey();
            const innerCursor = this.forest.allocateCursor("getTreeString");
            (0, internal_1.assert)(this.forest.tryMoveCursorToField({ fieldKey: key, parent: undefined }, innerCursor) ===
                1 /* TreeNavigationResult.Ok */, 0x892 /* failed to navigate to field */);
            fieldMap.set(key, innerCursor);
        });
        // Let the incremental summary builder know that we are starting a new summary.
        // It returns whether incremental encoding is enabled.
        const incrementalSummaryBehavior = this.incrementalSummaryBuilder.startSummary({
            fullTree,
            incrementalSummaryContext,
            stringify,
            builder,
        });
        const encoderContext = {
            ...this.encoderContext,
            incrementalEncoderDecoder: incrementalSummaryBehavior === incrementalSummaryBuilder_js_1.ForestIncrementalSummaryBehavior.Incremental
                ? this.incrementalSummaryBuilder
                : undefined,
        };
        const encoded = this.codec.encode(fieldMap, encoderContext);
        for (const value of fieldMap.values()) {
            value.free();
        }
        this.incrementalSummaryBuilder.completeSummary({
            incrementalSummaryContext,
            forestSummaryRootContent: stringify(encoded),
            forestSummaryRootContentKey: this.forestRootSummaryContentKey,
            builder,
        });
    }
    async loadInternal(services, parse, version) {
        // Get the key of the summary blob where the top-level forest content is stored based on the summary format version.
        // If the summary was generated as `ForestIncrementalSummaryBehavior.SingleBlob`, this blob will contain all
        // of forest's contents.
        // If the summary was generated as `ForestIncrementalSummaryBehavior.Incremental`, this blob will contain only
        // the top-level forest node's contents.
        // The contents of the incremental chunks will be in separate tree nodes and will be read later during decoding.
        const forestSummaryRootContentKey = (0, summaryTypes_js_1.getForestRootSummaryContentKey)(version);
        (0, internal_1.assert)(await services.contains(forestSummaryRootContentKey), 0xc21 /* Forest summary content missing in snapshot */);
        // Load the incremental summary builder so that it can download any incremental chunks in the
        // snapshot.
        await this.incrementalSummaryBuilder.load({
            services,
            readAndParseChunk: async (chunkBlobPath) => (0, index_js_3.readAndParseSnapshotBlob)(chunkBlobPath, services, parse),
        });
        // TODO: this code is parsing data without an optional validator, this should be defined in a typebox schema as part of the
        // forest summary format.
        const fields = this.codec.decode((await (0, index_js_3.readAndParseSnapshotBlob)(forestSummaryRootContentKey, services, parse)), {
            ...this.encoderContext,
            incrementalEncoderDecoder: this.incrementalSummaryBuilder,
        });
        const allocator = (0, index_js_3.idAllocatorFromMaxId)();
        const fieldChanges = [];
        const build = [];
        for (const [fieldKey, field] of fields) {
            const chunked = (0, chunkTree_js_1.chunkFieldSingle)(field, {
                policy: chunkTree_js_1.defaultChunkPolicy,
                idCompressor: this.idCompressor,
            });
            const buildId = { minor: allocator.allocate(chunked.topLevelLength) };
            build.push({
                id: buildId,
                trees: chunked,
            });
            fieldChanges.push([
                fieldKey,
                { marks: [{ count: chunked.topLevelLength, attach: buildId }] },
            ]);
        }
        (0, internal_1.assert)(this.forest.isEmpty, 0x797 /* forest must be empty */);
        (0, index_js_1.applyDelta)({ build, fields: new Map(fieldChanges) }, undefined, this.forest, (0, index_js_1.makeDetachedFieldIndex)("init", this.revisionTagCodec, this.idCompressor));
    }
}
exports.ForestSummarizer = ForestSummarizer;
//# sourceMappingURL=forestSummarizer.js.map