"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ForestSummarizer = exports.forestSummaryKey = void 0;
const client_utils_1 = require("@fluid-internal/client-utils");
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../core/index.js");
const index_js_2 = 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_3 = require("../chunked-forest/index.js");
const codec_js_1 = require("./codec.js");
const incrementalSummaryBuilder_js_1 = require("./incrementalSummaryBuilder.js");
const treeCompressionUtils_js_1 = require("../treeCompressionUtils.js");
/**
 * The key for the tree that contains the overall forest's summary tree.
 * This tree is added by the parent of the forest summarizer.
 * See {@link ForestIncrementalSummaryBuilder} for details on the summary structure.
 */
exports.forestSummaryKey = "Forest";
/**
 * Provides methods for summarizing and loading a forest.
 */
class ForestSummarizer {
    /**
     * @param encoderContext - The schema if provided here must be mutated by the caller to keep it up to date.
     */
    constructor(forest, revisionTagCodec, fieldBatchCodec, encoderContext, options, idCompressor, initialSequenceNumber, shouldEncodeIncrementally = index_js_3.defaultIncrementalEncodingPolicy) {
        this.forest = forest;
        this.revisionTagCodec = revisionTagCodec;
        this.encoderContext = encoderContext;
        this.idCompressor = idCompressor;
        this.key = exports.forestSummaryKey;
        // TODO: this should take in CodecWriteOptions, and use it to pick the write version.
        this.codec = (0, codec_js_1.makeForestSummarizerCodec)(options, fieldBatchCodec);
        this.incrementalSummaryBuilder = new incrementalSummaryBuilder_js_1.ForestIncrementalSummaryBuilder(encoderContext.encodeType ===
            treeCompressionUtils_js_1.TreeCompressionStrategy.CompressedIncremental /* 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.
     */
    summarize(props) {
        const { stringify, fullTree = false, incrementalSummaryContext } = 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,
        });
        const encoderContext = {
            ...this.encoderContext,
            incrementalEncoderDecoder: incrementalSummaryBehavior === incrementalSummaryBuilder_js_1.ForestIncrementalSummaryBehavior.Incremental
                ? this.incrementalSummaryBuilder
                : undefined,
        };
        const encoded = this.codec.encode(fieldMap, encoderContext);
        fieldMap.forEach((value) => value.free());
        return this.incrementalSummaryBuilder.completeSummary({
            incrementalSummaryContext,
            forestSummaryContent: stringify(encoded),
        });
    }
    async load(services, parse) {
        // The contents of the top-level forest must be present under a summary blob named `forestSummaryContentKey`.
        // 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.
        (0, internal_1.assert)(await services.contains(incrementalSummaryBuilder_js_1.forestSummaryContentKey), 0xc21 /* Forest summary content missing in snapshot */);
        const readAndParseBlob = async (id) => {
            const treeBuffer = await services.readBlob(id);
            const treeBufferString = (0, client_utils_1.bufferToString)(treeBuffer, "utf8");
            return parse(treeBufferString);
        };
        // Load the incremental summary builder so that it can download any incremental chunks in the
        // snapshot.
        await this.incrementalSummaryBuilder.load(services, readAndParseBlob);
        // 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 readAndParseBlob(incrementalSummaryBuilder_js_1.forestSummaryContentKey), {
            ...this.encoderContext,
            incrementalEncoderDecoder: this.incrementalSummaryBuilder,
        });
        const allocator = (0, index_js_2.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, [{ 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