/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert } from "@fluidframework/core-utils/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { noopValidator } from "../codec/index.js";
import { RevisionTagCodec, TreeStoredSchemaRepository, makeDetachedFieldIndex, moveToDetachedField, } from "../core/index.js";
import { createEmitter } from "../events/index.js";
import { DetachedFieldIndexSummarizer, ForestSummarizer, SchemaSummarizer, TreeCompressionStrategy, ViewSchema, buildChunkedForest, buildForest, createNodeKeyManager, nodeKeyFieldKey as defaultNodeKeyFieldKey, defaultSchemaPolicy, jsonableTreeFromFieldCursor, makeFieldBatchCodec, makeMitigatedChangeFamily, makeTreeChunker, } from "../feature-libraries/index.js";
import { SharedTreeCore } from "../shared-tree-core/index.js";
import { brand } from "../util/index.js";
import { ensureSchema } from "./schematizeTree.js";
import { SchematizingSimpleTreeView, requireSchema } from "./schematizingTreeView.js";
import { SharedTreeChangeFamily } from "./sharedTreeChangeFamily.js";
import { createTreeCheckout } from "./treeCheckout.js";
const formatVersionToTopLevelCodecVersions = new Map([
    [1, { forest: 1, schema: 1, detachedFieldIndex: 1, editManager: 1, message: 1, fieldBatch: 1 }],
]);
function getCodecVersions(formatVersion) {
    const versions = formatVersionToTopLevelCodecVersions.get(formatVersion);
    assert(versions !== undefined, "Unknown format version");
    return versions;
}
/**
 * Shared tree, configured with a good set of indexes and field kinds which will maintain compatibility over time.
 *
 * TODO: detail compatibility requirements.
 */
export class SharedTree extends SharedTreeCore {
    get storedSchema() {
        return this.checkout.storedSchema;
    }
    constructor(id, runtime, attributes, optionsParam, telemetryContextPrefix) {
        if (runtime.idCompressor === undefined) {
            throw new UsageError("IdCompressor must be enabled to use SharedTree");
        }
        const options = { ...defaultSharedTreeOptions, ...optionsParam };
        const codecVersions = getCodecVersions(options.formatVersion);
        const schema = new TreeStoredSchemaRepository();
        const forest = options.forest === ForestType.Optimized
            ? buildChunkedForest(makeTreeChunker(schema, defaultSchemaPolicy))
            : buildForest();
        const revisionTagCodec = new RevisionTagCodec(runtime.idCompressor);
        const removedRoots = makeDetachedFieldIndex("repair", revisionTagCodec, options);
        const schemaSummarizer = new SchemaSummarizer(runtime, schema, options, {
            getCurrentSeq: () => this.runtime.deltaManager.lastSequenceNumber,
        });
        const fieldBatchCodec = makeFieldBatchCodec(options, codecVersions.fieldBatch);
        const encoderContext = {
            schema: {
                schema,
                policy: defaultSchemaPolicy,
            },
            encodeType: options.treeEncodeType,
        };
        const forestSummarizer = new ForestSummarizer(forest, revisionTagCodec, fieldBatchCodec, encoderContext, options);
        const removedRootsSummarizer = new DetachedFieldIndexSummarizer(removedRoots);
        const innerChangeFamily = new SharedTreeChangeFamily(revisionTagCodec, fieldBatchCodec, options, options.treeEncodeType);
        const changeFamily = makeMitigatedChangeFamily(innerChangeFamily, SharedTreeChangeFamily.emptyChange, (error) => {
            // TODO:6344 Add telemetry for these errors.
            // Rethrowing the error has a different effect depending on the context in which the
            // ChangeFamily was invoked:
            // - If the ChangeFamily was invoked as part of incoming op processing, rethrowing the error
            // will cause the runtime to disconnect the client, log a severe error, and not reconnect.
            // This will not cause the host application to crash because it is not on the stack at that time.
            // TODO: let the host application know that the client is now disconnected.
            // - If the ChangeFamily was invoked as part of dealing with a local change, rethrowing the
            // error will cause the host application to crash. This is not ideal, but is better than
            // letting the application either send an invalid change to the server or allowing the
            // application to continue working when its local branches contain edits that cannot be
            // reflected in its views.
            // The best course of action for a host application in such a state is to restart.
            // TODO: let the host application know about this situation and provide a way to
            // programmatically reload the SharedTree container.
            throw error;
        });
        super([schemaSummarizer, forestSummarizer, removedRootsSummarizer], changeFamily, options, codecVersions, id, runtime, attributes, telemetryContextPrefix, { schema, policy: defaultSchemaPolicy });
        this._events = createEmitter();
        const localBranch = this.getLocalBranch();
        this.checkout = createTreeCheckout(runtime.idCompressor, this.mintRevisionTag, revisionTagCodec, {
            branch: localBranch,
            changeFamily,
            schema,
            forest,
            fieldBatchCodec,
            events: this._events,
            removedRoots,
            chunkCompressionStrategy: options.treeEncodeType,
        });
    }
    contentSnapshot() {
        const cursor = this.checkout.forest.allocateCursor();
        try {
            moveToDetachedField(this.checkout.forest, cursor);
            return {
                schema: this.storedSchema.clone(),
                tree: jsonableTreeFromFieldCursor(cursor),
                removed: this.checkout.getRemovedRoots(),
            };
        }
        finally {
            cursor.free();
        }
    }
    schematizeFlexTree(config, onDispose) {
        const viewSchema = new ViewSchema(defaultSchemaPolicy, {}, config.schema);
        if (!ensureSchema(viewSchema, config.allowedSchemaModifications, this.checkout, config)) {
            return undefined;
        }
        return requireSchema(this.checkout, viewSchema, onDispose, createNodeKeyManager(this.runtime.idCompressor), brand(defaultNodeKeyFieldKey));
    }
    schematize(config) {
        const view = new SchematizingSimpleTreeView(this.checkout, config, createNodeKeyManager(this.runtime.idCompressor), brand(defaultNodeKeyFieldKey));
        // As a subjective API design choice, we initialize the tree here if it is not already initialized.
        if (view.error?.canInitialize === true) {
            view.upgradeSchema();
        }
        return view;
    }
    async loadCore(services) {
        await super.loadCore(services);
        this._events.emit("afterBatch");
    }
}
/**
 * Format versions supported by SharedTree.
 *
 * Each version documents a required minimum version of the \@fluidframework/tree package.
 * @internal
 */
export const SharedTreeFormatVersion = {
    /**
     * Requires \@fluidframework/tree \>= 2.0.0.
     */
    v1: 1,
};
/**
 * Used to distinguish between different forest types.
 * @internal
 */
export var ForestType;
(function (ForestType) {
    /**
     * The "ObjectForest" forest type.
     */
    ForestType[ForestType["Reference"] = 0] = "Reference";
    /**
     * The "ChunkedForest" forest type.
     */
    ForestType[ForestType["Optimized"] = 1] = "Optimized";
})(ForestType || (ForestType = {}));
export const defaultSharedTreeOptions = {
    jsonValidator: noopValidator,
    forest: ForestType.Reference,
    treeEncodeType: TreeCompressionStrategy.Compressed,
    formatVersion: SharedTreeFormatVersion.v1,
};
/**
 * A channel factory that creates {@link ISharedTree}s.
 * @internal
 */
export class SharedTreeFactory {
    constructor(options = {}) {
        this.options = options;
        this.type = "https://graph.microsoft.com/types/tree";
        this.attributes = {
            type: this.type,
            snapshotFormatVersion: "0.0.0",
            packageVersion: "0.0.0",
        };
    }
    async load(runtime, id, services, channelAttributes) {
        const tree = new SharedTree(id, runtime, channelAttributes, this.options, "SharedTree");
        await tree.load(services);
        return tree;
    }
    create(runtime, id) {
        const tree = new SharedTree(id, runtime, this.attributes, this.options, "SharedTree");
        tree.initializeLocal();
        return tree;
    }
}
//# sourceMappingURL=sharedTree.js.map