/*!
 * 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 });
};
import { assert } from "@fluidframework/core-utils/internal";
import { SharedObject, } from "@fluidframework/shared-object-base/internal";
import { UsageError, } from "@fluidframework/telemetry-utils/internal";
import { noopValidator } from "../codec/index.js";
import { LeafNodeStoredSchema, MapNodeStoredSchema, ObjectNodeStoredSchema, RevisionTagCodec, TreeStoredSchemaRepository, makeDetachedFieldIndex, moveToDetachedField, } from "../core/index.js";
import { DetachedFieldIndexSummarizer, FieldKinds, ForestSummarizer, SchemaSummarizer, TreeCompressionStrategy, buildChunkedForest, buildForest, defaultSchemaPolicy, jsonableTreeFromFieldCursor, makeFieldBatchCodec, makeMitigatedChangeFamily, makeTreeChunker, } from "../feature-libraries/index.js";
import { DefaultResubmitMachine, SharedTreeCore, } from "../shared-tree-core/index.js";
import { NodeKind, tryStoredSchemaAsArray, customFromCursorStored, FieldKind, } from "../simple-tree/index.js";
import { SchematizingSimpleTreeView } from "./schematizingTreeView.js";
import { SharedTreeReadonlyChangeEnricher } from "./sharedTreeChangeEnricher.js";
import { SharedTreeChangeFamily } from "./sharedTreeChangeFamily.js";
import { createTreeCheckout } from "./treeCheckout.js";
import { Breakable, breakingClass, fail, throwIfBroken } from "../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 },
    ],
]);
function getCodecVersions(formatVersion) {
    const versions = formatVersionToTopLevelCodecVersions.get(formatVersion);
    assert(versions !== undefined, 0x90e /* Unknown format version */);
    return versions;
}
/**
 * Shared object wrapping {@link SharedTreeKernel}.
 */
export class SharedTree extends SharedObject {
    constructor(id, runtime, attributes, optionsParam, telemetryContextPrefix = "fluid_sharedTree_") {
        super(id, runtime, attributes, telemetryContextPrefix);
        this.breaker = new Breakable("Shared Tree");
        if (runtime.idCompressor === undefined) {
            throw new UsageError("IdCompressor must be enabled to use SharedTree");
        }
        this.kernel = new SharedTreeKernel(this.breaker, this, this.serializer, (content, localOpMetadata) => this.submitLocalMessage(content, localOpMetadata), () => this.deltaManager.lastSequenceNumber, this.logger, runtime.idCompressor, optionsParam);
    }
    summarizeCore(serializer, telemetryContext, incrementalSummaryContext) {
        return this.kernel.summarizeCore(serializer, telemetryContext, incrementalSummaryContext);
    }
    processCore(message, local, localOpMetadata) {
        this.kernel.processCore(message, local, localOpMetadata);
    }
    processMessagesCore(messagesCollection) {
        this.kernel.processMessagesCore(messagesCollection);
    }
    onDisconnect() {
        this.kernel.onDisconnect();
    }
    exportVerbose() {
        return this.kernel.exportVerbose();
    }
    exportSimpleSchema() {
        return this.kernel.exportSimpleSchema();
    }
    contentSnapshot() {
        return this.kernel.contentSnapshot();
    }
    viewWith(config) {
        return this.kernel.viewWith(config);
    }
    async loadCore(services) {
        await this.kernel.loadCore(services);
    }
    didAttach() {
        this.kernel.didAttach();
    }
    applyStashedOp(...args) {
        this.kernel.applyStashedOp(...args);
    }
    reSubmitCore(...args) {
        this.kernel.reSubmitCore(...args);
    }
}
/**
 * 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 = [breakingClass];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = 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 = { ...defaultSharedTreeOptions, ...optionsParam };
            const codecVersions = getCodecVersions(options.formatVersion);
            const schema = new TreeStoredSchemaRepository();
            const forest = buildConfiguredForest(options.forest, schema, idCompressor);
            const revisionTagCodec = new RevisionTagCodec(idCompressor);
            const removedRoots = makeDetachedFieldIndex("repair", revisionTagCodec, idCompressor, options);
            const schemaSummarizer = new SchemaSummarizer(schema, options, {
                getCurrentSeq: lastSequenceNumber,
            });
            const fieldBatchCodec = makeFieldBatchCodec(options, codecVersions.fieldBatch);
            const encoderContext = {
                schema: {
                    schema,
                    policy: defaultSchemaPolicy,
                },
                encodeType: options.treeEncodeType,
                originatorId: idCompressor.localSessionId,
                idCompressor,
            };
            const forestSummarizer = new ForestSummarizer(forest, revisionTagCodec, fieldBatchCodec, encoderContext, options, idCompressor);
            const removedRootsSummarizer = new DetachedFieldIndexSummarizer(removedRoots);
            const innerChangeFamily = new SharedTreeChangeFamily(revisionTagCodec, fieldBatchCodec, options, options.treeEncodeType, idCompressor);
            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;
            });
            const changeEnricher = new SharedTreeReadonlyChangeEnricher(forest, schema, removedRoots);
            super(breaker, sharedObject, serializer, submitLocalMessage, logger, [schemaSummarizer, forestSummarizer, removedRootsSummarizer], changeFamily, options, codecVersions, idCompressor, schema, defaultSchemaPolicy, new DefaultResubmitMachine((change) => changeFamily.rebaser.invert(change, true, this.mintRevisionTag()), changeEnricher), changeEnricher);
            this.checkout = __runInitializers(this, _instanceExtraInitializers);
            const localBranch = this.getLocalBranch();
            this.checkout = 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);
                    }
                }
            });
        }
        exportVerbose() {
            const cursor = this.checkout.forest.allocateCursor("contentSnapshot");
            try {
                moveToDetachedField(this.checkout.forest, cursor);
                const length = cursor.getFieldLength();
                if (length === 0) {
                    return undefined;
                }
                else if (length === 1) {
                    cursor.enterNode(0);
                    return verboseFromCursor(cursor, this.storedSchema.nodeSchema);
                }
                else {
                    fail(0xac8 /* Invalid document root length */);
                }
            }
            finally {
                cursor.free();
            }
        }
        exportSimpleSchema() {
            return {
                ...exportSimpleFieldSchemaStored(this.storedSchema.rootFieldSchema),
                definitions: new Map([...this.storedSchema.nodeSchema].map(([key, schema]) => {
                    return [key, exportSimpleNodeSchemaStored(schema)];
                })),
            };
        }
        contentSnapshot() {
            const cursor = this.checkout.forest.allocateCursor("contentSnapshot");
            try {
                moveToDetachedField(this.checkout.forest, cursor);
                return {
                    schema: this.storedSchema.clone(),
                    tree: 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 UsageError("Cannot attach while a transaction is in progress. Commit or abort the transaction before attaching.");
            }
            super.didAttach();
        }
        applyStashedOp(...args) {
            assert(!this.checkout.transaction.isInProgress(), 0x674 /* Unexpected transaction is open while applying stashed ops */);
            super.applyStashedOp(...args);
        }
        submitCommit(commit, schemaAndPolicy, isResubmit) {
            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 = [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;
})();
export function getBranch(treeOrView) {
    if (treeOrView instanceof SchematizingSimpleTreeView) {
        return treeOrView.checkout;
    }
    const kernel = treeOrView.kernel;
    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;
}
/**
 * Format versions supported by SharedTree.
 *
 * Each version documents a required minimum version of the \@fluidframework/tree package.
 * @alpha
 */
export const 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,
};
/**
 * Reference implementation of forest.
 * @remarks
 * A simple implementation with minimal complexity and moderate debuggability, validation and performance.
 * @privateRemarks
 * The "ObjectForest" forest type.
 * @alpha
 */
export const ForestTypeReference = toForestType(() => buildForest());
/**
 * 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
 */
export const ForestTypeOptimized = toForestType((schema, idCompressor) => buildChunkedForest(makeTreeChunker(schema, 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
 */
export const ForestTypeExpensiveDebug = toForestType(() => buildForest(undefined, true));
function toForestType(factory) {
    return factory;
}
/**
 * Build and return a forest of the requested type.
 */
export function buildConfiguredForest(factory, schema, idCompressor) {
    return factory(schema, idCompressor);
}
export const defaultSharedTreeOptions = {
    jsonValidator: noopValidator,
    forest: ForestTypeReference,
    treeEncodeType: TreeCompressionStrategy.Compressed,
    formatVersion: SharedTreeFormatVersion.v3,
    disposeForksAfterTransaction: true,
};
function verboseFromCursor(reader, schema) {
    const fields = customFromCursorStored(reader, schema, verboseFromCursor);
    const nodeSchema = schema.get(reader.type) ?? fail(0xac9 /* missing schema for type in cursor */);
    if (nodeSchema instanceof LeafNodeStoredSchema) {
        return fields;
    }
    return {
        type: reader.type,
        fields: fields,
    };
}
function exportSimpleFieldSchemaStored(schema) {
    let kind;
    switch (schema.kind) {
        case FieldKinds.identifier.identifier:
            kind = FieldKind.Identifier;
            break;
        case FieldKinds.optional.identifier:
            kind = FieldKind.Optional;
            break;
        case FieldKinds.required.identifier:
            kind = FieldKind.Required;
            break;
        case FieldKinds.forbidden.identifier:
            kind = FieldKind.Optional;
            assert(schema.types.size === 0, 0xa94 /* invalid forbidden field */);
            break;
        default:
            fail(0xaca /* invalid field kind */);
    }
    return { kind, allowedTypes: schema.types };
}
function exportSimpleNodeSchemaStored(schema) {
    const arrayTypes = tryStoredSchemaAsArray(schema);
    if (arrayTypes !== undefined) {
        return { kind: NodeKind.Array, allowedTypes: arrayTypes };
    }
    if (schema instanceof ObjectNodeStoredSchema) {
        const fields = {};
        for (const [key, field] of schema.objectNodeFields) {
            fields[key] = exportSimpleFieldSchemaStored(field);
        }
        return { kind: NodeKind.Object, fields };
    }
    if (schema instanceof MapNodeStoredSchema) {
        assert(schema.mapFields.kind === FieldKinds.optional.identifier, 0xa95 /* Invalid map schema */);
        return { kind: NodeKind.Map, allowedTypes: schema.mapFields.types };
    }
    if (schema instanceof LeafNodeStoredSchema) {
        return { kind: NodeKind.Leaf, leafKind: schema.leafValue };
    }
    fail(0xacb /* invalid schema kind */);
}
//# sourceMappingURL=sharedTree.js.map