"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
    if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
    return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultSharedTreeOptions = exports.buildConfiguredForest = exports.ForestTypeExpensiveDebug = exports.ForestTypeOptimized = exports.ForestTypeReference = exports.SharedTreeFormatVersion = exports.getBranch = exports.persistedToSimpleSchema = exports.exportSimpleSchema = exports.SharedTreeKernel = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const internal_2 = require("@fluidframework/telemetry-utils/internal");
const index_js_1 = require("../codec/index.js");
const index_js_2 = require("../core/index.js");
const index_js_3 = require("../feature-libraries/index.js");
const index_js_4 = require("../shared-tree-core/index.js");
const index_js_5 = require("../simple-tree/index.js");
const schematizingTreeView_js_1 = require("./schematizingTreeView.js");
const sharedTreeChangeEnricher_js_1 = require("./sharedTreeChangeEnricher.js");
const sharedTreeChangeFamily_js_1 = require("./sharedTreeChangeFamily.js");
const treeCheckout_js_1 = require("./treeCheckout.js");
const index_js_6 = require("../util/index.js");
const formatVersionToTopLevelCodecVersions = new Map([
    [
        1,
        { forest: 1, schema: 1, detachedFieldIndex: 1, editManager: 1, message: 1, fieldBatch: 1 },
    ],
    [
        2,
        { forest: 1, schema: 1, detachedFieldIndex: 1, editManager: 2, message: 2, fieldBatch: 1 },
    ],
    [
        3,
        { forest: 1, schema: 1, detachedFieldIndex: 1, editManager: 3, message: 3, fieldBatch: 1 },
    ],
    [
        4,
        { forest: 1, schema: 1, detachedFieldIndex: 1, editManager: 4, message: 4, fieldBatch: 1 },
    ],
    [
        5,
        { forest: 1, schema: 2, detachedFieldIndex: 1, editManager: 4, message: 4, fieldBatch: 1 },
    ],
]);
function getCodecVersions(formatVersion) {
    const versions = formatVersionToTopLevelCodecVersions.get(formatVersion);
    (0, internal_1.assert)(versions !== undefined, 0x90e /* Unknown format version */);
    return versions;
}
/**
 * SharedTreeCore, configured with a good set of indexes and field kinds which will maintain compatibility over time.
 *
 * TODO: detail compatibility requirements.
 */
let SharedTreeKernel = (() => {
    let _classDecorators = [index_js_6.breakingClass];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = index_js_4.SharedTreeCore;
    let _instanceExtraInitializers = [];
    let _contentSnapshot_decorators;
    var SharedTreeKernel = _classThis = class extends _classSuper {
        get storedSchema() {
            return this.checkout.storedSchema;
        }
        constructor(breaker, sharedObject, serializer, submitLocalMessage, lastSequenceNumber, logger, idCompressor, optionsParam) {
            const options = { ...exports.defaultSharedTreeOptions, ...optionsParam };
            const codecVersions = getCodecVersions(options.formatVersion);
            const schema = new index_js_2.TreeStoredSchemaRepository();
            const forest = buildConfiguredForest(breaker, options.forest, schema, idCompressor);
            const revisionTagCodec = new index_js_2.RevisionTagCodec(idCompressor);
            const removedRoots = (0, index_js_2.makeDetachedFieldIndex)("repair", revisionTagCodec, idCompressor, options);
            const schemaCodec = (0, index_js_3.makeSchemaCodec)(options, codecVersions.schema);
            const schemaSummarizer = new index_js_3.SchemaSummarizer(schema, {
                getCurrentSeq: lastSequenceNumber,
            }, schemaCodec);
            const fieldBatchCodec = (0, index_js_3.makeFieldBatchCodec)(options, codecVersions.fieldBatch);
            const encoderContext = {
                schema: {
                    schema,
                    policy: index_js_3.defaultSchemaPolicy,
                },
                encodeType: options.treeEncodeType,
                originatorId: idCompressor.localSessionId,
                idCompressor,
            };
            const forestSummarizer = new index_js_3.ForestSummarizer(forest, revisionTagCodec, fieldBatchCodec, encoderContext, options, idCompressor);
            const removedRootsSummarizer = new index_js_3.DetachedFieldIndexSummarizer(removedRoots);
            const innerChangeFamily = new sharedTreeChangeFamily_js_1.SharedTreeChangeFamily(revisionTagCodec, fieldBatchCodec, options, options.treeEncodeType, idCompressor);
            const changeFamily = (0, index_js_3.makeMitigatedChangeFamily)(innerChangeFamily, sharedTreeChangeFamily_js_1.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;
            });
            const changeEnricher = new sharedTreeChangeEnricher_js_1.SharedTreeReadonlyChangeEnricher(forest, schema, removedRoots);
            super(breaker, sharedObject, serializer, submitLocalMessage, logger, [schemaSummarizer, forestSummarizer, removedRootsSummarizer], changeFamily, options, codecVersions, idCompressor, schema, index_js_3.defaultSchemaPolicy, new index_js_4.DefaultResubmitMachine((change) => changeFamily.rebaser.invert(change, true, this.mintRevisionTag()), changeEnricher), changeEnricher);
            this.checkout = __runInitializers(this, _instanceExtraInitializers);
            const localBranch = this.getLocalBranch();
            this.checkout = (0, treeCheckout_js_1.createTreeCheckout)(idCompressor, this.mintRevisionTag, revisionTagCodec, {
                branch: localBranch,
                changeFamily,
                schema,
                forest,
                fieldBatchCodec,
                removedRoots,
                chunkCompressionStrategy: options.treeEncodeType,
                logger,
                breaker: this.breaker,
                disposeForksAfterTransaction: options.disposeForksAfterTransaction,
            });
            this.checkout.transaction.events.on("started", () => {
                if (sharedObject.isAttached()) {
                    // It is currently forbidden to attach during a transaction, so transaction state changes can be ignored until after attaching.
                    this.commitEnricher.startTransaction();
                }
            });
            this.checkout.transaction.events.on("aborting", () => {
                if (sharedObject.isAttached()) {
                    // It is currently forbidden to attach during a transaction, so transaction state changes can be ignored until after attaching.
                    this.commitEnricher.abortTransaction();
                }
            });
            this.checkout.transaction.events.on("committing", () => {
                if (sharedObject.isAttached()) {
                    // It is currently forbidden to attach during a transaction, so transaction state changes can be ignored until after attaching.
                    this.commitEnricher.commitTransaction();
                }
            });
            this.checkout.events.on("beforeBatch", (event) => {
                if (event.type === "append" && sharedObject.isAttached()) {
                    if (this.checkout.transaction.isInProgress()) {
                        this.commitEnricher.addTransactionCommits(event.newCommits);
                    }
                }
            });
            this.view = {
                contentSnapshot: () => this.contentSnapshot(),
                exportSimpleSchema: () => this.exportSimpleSchema(),
                exportVerbose: () => this.exportVerbose(),
                viewWith: this.viewWith.bind(this),
                kernel: this,
            };
        }
        exportVerbose() {
            return this.checkout.exportVerbose();
        }
        exportSimpleSchema() {
            return exportSimpleSchema(this.storedSchema);
        }
        contentSnapshot() {
            const cursor = this.checkout.forest.allocateCursor("contentSnapshot");
            try {
                (0, index_js_2.moveToDetachedField)(this.checkout.forest, cursor);
                return {
                    schema: this.storedSchema.clone(),
                    tree: (0, index_js_3.jsonableTreeFromFieldCursor)(cursor),
                    removed: this.checkout.getRemovedRoots(),
                };
            }
            finally {
                cursor.free();
            }
        }
        viewWith(config) {
            return this.checkout.viewWith(config);
        }
        async loadCore(services) {
            await super.loadCore(services);
            this.checkout.load();
        }
        didAttach() {
            if (this.checkout.transaction.isInProgress()) {
                // Attaching during a transaction is not currently supported.
                // At least part of of the system is known to not handle this case correctly - commit enrichment - and there may be others.
                throw new internal_2.UsageError("Cannot attach while a transaction is in progress. Commit or abort the transaction before attaching.");
            }
            super.didAttach();
        }
        applyStashedOp(...args) {
            (0, internal_1.assert)(!this.checkout.transaction.isInProgress(), 0x674 /* Unexpected transaction is open while applying stashed ops */);
            super.applyStashedOp(...args);
        }
        submitCommit(commit, schemaAndPolicy, isResubmit) {
            (0, internal_1.assert)(!this.checkout.transaction.isInProgress(), 0xaa6 /* Cannot submit a commit while a transaction is in progress */);
            if (isResubmit) {
                return super.submitCommit(commit, schemaAndPolicy, isResubmit);
            }
            // Refrain from submitting new commits until they are validated by the checkout.
            // This is not a strict requirement for correctness in our system, but in the event that there is a bug when applying commits to the checkout
            // that causes a crash (e.g. in the forest), this will at least prevent this client from sending the problematic commit to any other clients.
            this.checkout.onCommitValid(commit, () => super.submitCommit(commit, schemaAndPolicy, isResubmit));
        }
        onDisconnect() { }
    };
    __setFunctionName(_classThis, "SharedTreeKernel");
    (() => {
        const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
        _contentSnapshot_decorators = [index_js_6.throwIfBroken];
        __esDecorate(_classThis, null, _contentSnapshot_decorators, { kind: "method", name: "contentSnapshot", static: false, private: false, access: { has: obj => "contentSnapshot" in obj, get: obj => obj.contentSnapshot }, metadata: _metadata }, null, _instanceExtraInitializers);
        __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
        SharedTreeKernel = _classThis = _classDescriptor.value;
        if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        __runInitializers(_classThis, _classExtraInitializers);
    })();
    return SharedTreeKernel = _classThis;
})();
exports.SharedTreeKernel = SharedTreeKernel;
function exportSimpleSchema(storedSchema) {
    return {
        root: exportSimpleFieldSchemaStored(storedSchema.rootFieldSchema),
        definitions: new Map([...storedSchema.nodeSchema].map(([key, schema]) => {
            return [key, exportSimpleNodeSchemaStored(schema)];
        })),
    };
}
exports.exportSimpleSchema = exportSimpleSchema;
/**
 * A way to parse schema in the persisted format from {@link extractPersistedSchema}.
 * @remarks
 * This behaves identically to {@link ITreeAlpha.exportSimpleSchema},
 * except that it gets the schema from the caller instead of from an existing tree.
 *
 * This can be useful for inspecting the contents of persisted schema,
 * such as those generated by {@link extractPersistedSchema} for use in testing.
 * Since that data format is otherwise unspecified,
 * this provides a way to inspect its contents with documented semantics.
 * @alpha
 */
function persistedToSimpleSchema(persisted, options) {
    const schemaCodec = (0, index_js_3.makeSchemaCodec)(options, index_js_2.SchemaVersion.v1);
    const stored = schemaCodec.decode(persisted);
    return exportSimpleSchema(stored);
}
exports.persistedToSimpleSchema = persistedToSimpleSchema;
function getBranch(treeOrView) {
    if (treeOrView instanceof schematizingTreeView_js_1.SchematizingSimpleTreeView) {
        return treeOrView.checkout;
    }
    const kernel = treeOrView.kernel;
    (0, internal_1.assert)(kernel instanceof SharedTreeKernel, 0xb56 /* Invalid ITree */);
    // This cast is safe so long as TreeCheckout supports all the operations on the branch interface.
    return kernel.checkout;
}
exports.getBranch = getBranch;
/**
 * Format versions supported by SharedTree.
 *
 * Each version documents a required minimum version of the \@fluidframework/tree package.
 * @alpha
 */
exports.SharedTreeFormatVersion = {
    /**
     * Requires \@fluidframework/tree \>= 2.0.0.
     *
     * @deprecated - FF does not currently plan on supporting this format long-term.
     * Do not write production documents using this format, as they may not be loadable in the future.
     */
    v1: 1,
    /**
     * Requires \@fluidframework/tree \>= 2.0.0.
     */
    v2: 2,
    /**
     * Requires \@fluidframework/tree \>= 2.0.0.
     */
    v3: 3,
    /**
     * Requires \@fluidframework/tree \>= 2.0.0.
     */
    v5: 5,
};
/**
 * Reference implementation of forest.
 * @remarks
 * A simple implementation with minimal complexity and moderate debuggability, validation and performance.
 * @privateRemarks
 * The "ObjectForest" forest type.
 * @alpha
 */
exports.ForestTypeReference = toForestType((breaker, schema, idCompressor) => (0, index_js_3.buildForest)(breaker, schema));
/**
 * Optimized implementation of forest.
 * @remarks
 * A complex optimized forest implementation, which has minimal validation and debuggability to optimize for performance.
 * Uses an internal representation optimized for size designed to scale to larger datasets with reduced overhead.
 * @privateRemarks
 * The "ChunkedForest" forest type.
 * @alpha
 */
exports.ForestTypeOptimized = toForestType((breaker, schema, idCompressor) => (0, index_js_3.buildChunkedForest)((0, index_js_3.makeTreeChunker)(schema, index_js_3.defaultSchemaPolicy), undefined, idCompressor));
/**
 * Slow implementation of forest intended only for debugging.
 * @remarks
 * Includes validation with scales poorly.
 * May be asymptotically slower than {@link ForestTypeReference}, and may perform very badly with larger data sizes.
 * @privateRemarks
 * The "ObjectForest" forest type with expensive asserts for debugging.
 * @alpha
 */
exports.ForestTypeExpensiveDebug = toForestType((breaker, schema) => (0, index_js_3.buildForest)(breaker, schema, undefined, true));
function toForestType(factory) {
    return factory;
}
/**
 * Build and return a forest of the requested type.
 */
function buildConfiguredForest(breaker, factory, schema, idCompressor) {
    return factory(breaker, schema, idCompressor);
}
exports.buildConfiguredForest = buildConfiguredForest;
exports.defaultSharedTreeOptions = {
    jsonValidator: index_js_1.noopValidator,
    oldestCompatibleClient: index_js_1.FluidClientVersion.v2_0,
    forest: exports.ForestTypeReference,
    treeEncodeType: index_js_3.TreeCompressionStrategy.Compressed,
    formatVersion: exports.SharedTreeFormatVersion.v3,
    disposeForksAfterTransaction: true,
};
function exportSimpleFieldSchemaStored(schema) {
    let kind;
    switch (schema.kind) {
        case index_js_3.FieldKinds.identifier.identifier:
            kind = index_js_5.FieldKind.Identifier;
            break;
        case index_js_3.FieldKinds.optional.identifier:
            kind = index_js_5.FieldKind.Optional;
            break;
        case index_js_3.FieldKinds.required.identifier:
            kind = index_js_5.FieldKind.Required;
            break;
        case index_js_3.FieldKinds.forbidden.identifier:
            kind = index_js_5.FieldKind.Optional;
            (0, internal_1.assert)(schema.types.size === 0, 0xa94 /* invalid forbidden field */);
            break;
        default:
            (0, internal_1.fail)(0xaca /* invalid field kind */);
    }
    return {
        kind,
        allowedTypesIdentifiers: schema.types,
        metadata: {},
        persistedMetadata: schema.persistedMetadata,
    };
}
/**
 * Export a {@link SimpleNodeSchema} from a {@link TreeNodeStoredSchema}.
 * @privateRemarks
 * TODO: Persist node metadata once schema FormatV2 is supported.
 */
function exportSimpleNodeSchemaStored(schema) {
    const arrayTypes = (0, index_js_5.tryStoredSchemaAsArray)(schema);
    if (arrayTypes !== undefined) {
        return {
            kind: index_js_5.NodeKind.Array,
            allowedTypesIdentifiers: arrayTypes,
            metadata: {},
            persistedMetadata: schema.metadata,
        };
    }
    if (schema instanceof index_js_2.ObjectNodeStoredSchema) {
        const fields = new Map();
        for (const [storedKey, field] of schema.objectNodeFields) {
            fields.set(storedKey, { ...exportSimpleFieldSchemaStored(field), storedKey });
        }
        return { kind: index_js_5.NodeKind.Object, fields, metadata: {}, persistedMetadata: schema.metadata };
    }
    if (schema instanceof index_js_2.MapNodeStoredSchema) {
        (0, internal_1.assert)(schema.mapFields.kind === index_js_3.FieldKinds.optional.identifier, 0xa95 /* Invalid map schema */);
        return {
            kind: index_js_5.NodeKind.Map,
            allowedTypesIdentifiers: schema.mapFields.types,
            metadata: {},
            persistedMetadata: schema.metadata,
        };
    }
    if (schema instanceof index_js_2.LeafNodeStoredSchema) {
        return {
            kind: index_js_5.NodeKind.Leaf,
            leafKind: schema.leafValue,
            metadata: {},
            persistedMetadata: schema.metadata,
        };
    }
    (0, internal_1.fail)(0xacb /* invalid schema kind */);
}
//# sourceMappingURL=sharedTree.js.map