/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { applyDelta, forEachField, makeDetachedFieldIndex, } from "../../core/index.js";
import { VersionedSummarizer, } from "../../shared-tree-core/index.js";
import { idAllocatorFromMaxId, readAndParseSnapshotBlob, } from "../../util/index.js";
// eslint-disable-next-line import-x/no-internal-modules
import { chunkFieldSingle, defaultChunkPolicy } from "../chunked-forest/chunkTree.js";
import { defaultIncrementalEncodingPolicy, } from "../chunked-forest/index.js";
import { TreeCompressionStrategy } from "../treeCompressionUtils.js";
import { forestCodecBuilder } from "./codec.js";
import { ForestIncrementalSummaryBehavior, ForestIncrementalSummaryBuilder, } from "./incrementalSummaryBuilder.js";
import { ForestSummaryFormatVersion, forestSummaryKey, supportedForestSummaryFormatVersions, } from "./summaryFormatCommon.js";
import { minVersionToForestSummaryFormatVersion, getForestRootSummaryContentKey, } from "./summaryTypes.js";
/**
 * Provides methods for summarizing and loading a forest.
 */
export class ForestSummarizer extends 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 = defaultIncrementalEncodingPolicy) {
        super(forestSummaryKey, minVersionToForestSummaryFormatVersion(options.minVersionForCollab), supportedForestSummaryFormatVersions, true /* supportPreVersioningFormat */);
        this.forest = forest;
        this.revisionTagCodec = revisionTagCodec;
        this.encoderContext = encoderContext;
        this.idCompressor = idCompressor;
        this.codec = forestCodecBuilder.build(options);
        const summaryFormatWriteVersion = minVersionToForestSummaryFormatVersion(options.minVersionForCollab);
        this.forestRootSummaryContentKey = 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 >= ForestSummaryFormatVersion.v3 &&
            encoderContext.encodeType === TreeCompressionStrategy.CompressedIncremental;
        this.incrementalSummaryBuilder = new 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
        forEachField(rootCursor, (cursor) => {
            const key = cursor.getFieldKey();
            const innerCursor = this.forest.allocateCursor("getTreeString");
            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 === 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 = getForestRootSummaryContentKey(version);
        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) => 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 readAndParseSnapshotBlob(forestSummaryRootContentKey, services, parse)), {
            ...this.encoderContext,
            incrementalEncoderDecoder: this.incrementalSummaryBuilder,
        });
        const allocator = idAllocatorFromMaxId();
        const fieldChanges = [];
        const build = [];
        for (const [fieldKey, field] of fields) {
            const chunked = chunkFieldSingle(field, {
                policy: 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 }] },
            ]);
        }
        assert(this.forest.isEmpty, 0x797 /* forest must be empty */);
        applyDelta({ build, fields: new Map(fieldChanges) }, undefined, this.forest, makeDetachedFieldIndex("init", this.revisionTagCodec, this.idCompressor));
    }
}
//# sourceMappingURL=forestSummarizer.js.map