/*!
 * 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, fail } from "@fluidframework/core-utils/internal";
import { UsageError, } from "@fluidframework/telemetry-utils/internal";
import { DependentFormatVersion, FluidClientVersion, FormatValidatorNoOp, } from "../codec/index.js";
import { LeafNodeStoredSchema, MapNodeStoredSchema, ObjectNodeStoredSchema, RevisionTagCodec, TreeStoredSchemaRepository, detachedFieldIndexCodecBuilder, makeDetachedFieldIndex, moveToDetachedField, } from "../core/index.js";
import { DetachedFieldIndexSummarizer, FieldKinds, ForestSummarizer, SchemaSummarizer, TreeCompressionStrategy, buildChunkedForest, buildForest, defaultIncrementalEncodingPolicy, defaultSchemaPolicy, fieldBatchCodecBuilder, forestCodecBuilder, jsonableTreeFromFieldCursor, makeMitigatedChangeFamily, makeTreeChunker, } from "../feature-libraries/index.js";
import { schemaCodecBuilder } from "../feature-libraries/index.js";
import { getCodecTreeForEditManagerFormatWithChange, getCodecTreeForMessageFormatWithChange, makeMessageCodecBuilder, MessageFormatVersion, SharedTreeCore, EditManagerFormatVersion, makeEditManagerCodecBuilder, } from "../shared-tree-core/index.js";
import { NodeKind, tryStoredSchemaAsArray, FieldKind, } from "../simple-tree/index.js";
import { breakingClass, throwIfBroken, } from "../util/index.js";
import { getCodecTreeForChangeFormat, SharedTreeChangeFormatVersion, } from "./sharedTreeChangeCodecs.js";
import { SharedTreeChangeFamily } from "./sharedTreeChangeFamily.js";
import { createTreeCheckout } from "./treeCheckout.js";
/**
 * 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, initialSequenceNumber, logger, idCompressor, optionsParam) {
            const options = {
                ...defaultSharedTreeOptions,
                ...optionsParam,
            };
            if (options.minVersionForCollab < FluidClientVersion.v2_0) {
                throw new UsageError("SharedTree requires minVersionForCollab of at least 2.0.0");
            }
            const schema = new TreeStoredSchemaRepository();
            const forest = buildConfiguredForest(breaker, options.forest, schema, idCompressor, options.shouldEncodeIncrementally);
            const revisionTagCodec = new RevisionTagCodec(idCompressor);
            const removedRoots = makeDetachedFieldIndex("repair", revisionTagCodec, idCompressor, options);
            const schemaCodec = schemaCodecBuilder.build(options);
            const schemaSummarizer = new SchemaSummarizer(schema, {
                getCurrentSeq: lastSequenceNumber,
            }, schemaCodec, options.minVersionForCollab);
            const fieldBatchCodec = fieldBatchCodecBuilder.build(options);
            const encoderContext = {
                schema: {
                    schema,
                    policy: defaultSchemaPolicy,
                },
                encodeType: options.treeEncodeType,
                originatorId: idCompressor.localSessionId,
                idCompressor,
            };
            const forestSummarizer = new ForestSummarizer(forest, revisionTagCodec, encoderContext, options, idCompressor, initialSequenceNumber, options.shouldEncodeIncrementally);
            const removedRootsSummarizer = new DetachedFieldIndexSummarizer(removedRoots, options.minVersionForCollab);
            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;
            });
            super(breaker, sharedObject, serializer, submitLocalMessage, logger, [schemaSummarizer, forestSummarizer, removedRootsSummarizer], changeFamily, options, changeFormatVersionForEditManager, changeFormatVersionForMessage, idCompressor, schema, defaultSchemaPolicy);
            this.logger = (__runInitializers(this, _instanceExtraInitializers), logger);
            this.checkouts = new Map();
            this.checkout = createTreeCheckout(idCompressor, this.mintRevisionTag, revisionTagCodec, {
                branch: this.getLocalBranch(),
                changeFamily,
                schema,
                forest,
                fieldBatchCodec,
                removedRoots,
                chunkCompressionStrategy: options.treeEncodeType,
                logger,
                breaker: this.breaker,
                disposeForksAfterTransaction: options.disposeForksAfterTransaction,
            });
            this.registerSharedBranchForEditing("main", this.checkout);
            this.registerCheckout("main", this.checkout);
            this.view = {
                contentSnapshot: () => this.contentSnapshot(),
                exportSimpleSchema: () => this.exportSimpleSchema(),
                exportVerbose: () => this.exportVerbose(),
                viewWith: this.viewWith.bind(this),
                viewSharedBranchWith: this.viewBranchWith.bind(this),
                createSharedBranch: this.createSharedBranch.bind(this),
                getSharedBranchIds: this.getSharedBranchIds.bind(this),
                kernel: this,
            };
        }
        registerCheckout(branchId, checkout) {
            this.checkouts.set(branchId, checkout);
        }
        exportVerbose() {
            return this.checkout.exportVerbose();
        }
        exportSimpleSchema() {
            return exportSimpleSchema(this.storedSchema);
        }
        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);
        }
        viewBranchWith(branchId, config) {
            const compressedId = this.idCompressor.tryRecompress(branchId);
            if (compressedId === undefined) {
                throw new UsageError(`No branch found with id: ${branchId}`);
            }
            return this.getCheckout(compressedId).viewWith(config);
        }
        getCheckout(branchId) {
            return this.checkouts.get(branchId) ?? this.checkoutBranch(branchId);
        }
        checkoutBranch(branchId) {
            const checkout = this.checkout.fork();
            checkout.switchBranch(this.getSharedBranch(branchId));
            this.registerSharedBranchForEditing(branchId, checkout);
            this.registerCheckout(branchId, checkout);
            return checkout;
        }
        async loadCore(services) {
            await super.loadCore(services);
            this.checkout.load();
        }
        didAttach() {
            for (const checkout of this.checkouts.values()) {
                if (checkout.transaction.size > 0) {
                    // 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) {
            for (const checkout of this.checkouts.values()) {
                assert(checkout.transaction.size === 0, 0x674 /* Unexpected transaction is open while applying stashed ops */);
            }
            super.applyStashedOp(...args);
        }
        submitCommit(branchId, commit, schemaAndPolicy, isResubmit) {
            const checkout = this.getCheckout(branchId);
            assert(checkout.transaction.size === 0, 0xaa6 /* Cannot submit a commit while a transaction is in progress */);
            if (isResubmit) {
                return super.submitCommit(branchId, 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.
            checkout.onCommitValid(commit, () => super.submitCommit(branchId, 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 { SharedTreeKernel };
export function exportSimpleSchema(storedSchema) {
    return {
        root: exportSimpleFieldSchemaStored(storedSchema.rootFieldSchema),
        definitions: new Map([...storedSchema.nodeSchema].map(([key, schema]) => {
            return [key, exportSimpleNodeSchemaStored(schema)];
        })),
    };
}
/**
 * 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
 */
export function persistedToSimpleSchema(persisted, options) {
    const schemaCodec = schemaCodecBuilder.buildDecoder(options);
    const stored = schemaCodec.decode(persisted);
    return exportSimpleSchema(stored);
}
/**
 * Defines for each EditManagerFormatVersion the SharedTreeChangeFormatVersion to use.
 * This is an arbitrary mapping that is injected in the EditManger codec.
 * Once an entry is defined and used in production, it cannot be changed.
 * This is because the format for SharedTree changes are not explicitly versioned.
 */
export const changeFormatVersionForEditManager = DependentFormatVersion.fromPairs([
    [EditManagerFormatVersion.v3, SharedTreeChangeFormatVersion.v3],
    [EditManagerFormatVersion.v4, SharedTreeChangeFormatVersion.v4],
    [EditManagerFormatVersion.vSharedBranches, SharedTreeChangeFormatVersion.v4],
    [EditManagerFormatVersion.v6, SharedTreeChangeFormatVersion.v5],
]);
/**
 * Defines for each MessageFormatVersion the SharedTreeChangeFormatVersion to use.
 * This is an arbitrary mapping that is injected in the message codec.
 * Once an entry is defined and used in production, it cannot be changed.
 * This is because the format for SharedTree changes are not explicitly versioned.
 */
export const changeFormatVersionForMessage = DependentFormatVersion.fromPairs([
    [MessageFormatVersion.v3, SharedTreeChangeFormatVersion.v3],
    [MessageFormatVersion.v4, SharedTreeChangeFormatVersion.v4],
    [MessageFormatVersion.vSharedBranches, SharedTreeChangeFormatVersion.v4],
    [MessageFormatVersion.v6, SharedTreeChangeFormatVersion.v5],
]);
function getCodecTreeForEditManagerFormat(clientVersion) {
    const editManagerVersion = makeEditManagerCodecBuilder().getCodecTree(clientVersion).version;
    const change = changeFormatVersionForEditManager.lookup(editManagerVersion);
    const changeCodecTree = getCodecTreeForChangeFormat(change, clientVersion);
    return getCodecTreeForEditManagerFormatWithChange(clientVersion, changeCodecTree);
}
function getCodecTreeForMessageFormat(clientVersion) {
    const messageVersion = makeMessageCodecBuilder().getCodecTree(clientVersion).version;
    assert(messageVersion !== undefined, 0xce3 /* Deprecated 'undefined' version shouldn't be selected */);
    const change = changeFormatVersionForMessage.lookup(messageVersion);
    const changeCodecTree = getCodecTreeForChangeFormat(change, clientVersion);
    return getCodecTreeForMessageFormatWithChange(clientVersion, changeCodecTree);
}
export function getCodecTreeForSharedTreeFormat(clientVersion) {
    const children = [];
    children.push(forestCodecBuilder.getCodecTree(clientVersion));
    children.push(schemaCodecBuilder.getCodecTree(clientVersion));
    children.push(detachedFieldIndexCodecBuilder.getCodecTree(clientVersion));
    children.push(getCodecTreeForEditManagerFormat(clientVersion));
    children.push(getCodecTreeForMessageFormat(clientVersion));
    children.push(fieldBatchCodecBuilder.getCodecTree(clientVersion));
    return {
        name: "SharedTree",
        version: undefined, // SharedTree does not have a version of its own.
        children,
    };
}
/**
 * Reference implementation of forest.
 * @remarks
 * A simple implementation with minimal complexity and moderate debuggability, validation and performance.
 * @privateRemarks
 * The "ObjectForest" forest type.
 * @beta
 */
export const ForestTypeReference = toForestType((breaker, schema, idCompressor) => 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.
 * @beta
 */
export const ForestTypeOptimized = toForestType((breaker, schema, idCompressor, shouldEncodeIncrementally) => buildChunkedForest(makeTreeChunker(schema, defaultSchemaPolicy, shouldEncodeIncrementally), 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.
 * @beta
 */
export const ForestTypeExpensiveDebug = toForestType((breaker, schema) => buildForest(breaker, schema, undefined, true));
function toForestType(factory) {
    return factory;
}
/**
 * Build and return a forest of the requested type.
 */
export function buildConfiguredForest(breaker, factory, schema, idCompressor, shouldEncodeIncrementally) {
    return factory(breaker, schema, idCompressor, shouldEncodeIncrementally);
}
export const defaultSharedTreeOptions = {
    jsonValidator: FormatValidatorNoOp,
    minVersionForCollab: FluidClientVersion.v2_0,
    forest: ForestTypeReference,
    treeEncodeType: TreeCompressionStrategy.Compressed,
    disposeForksAfterTransaction: true,
    shouldEncodeIncrementally: defaultIncrementalEncodingPolicy,
    enableSharedBranches: false,
    writeVersionOverrides: new Map(),
    allowPossiblyIncompatibleWriteVersionOverrides: false,
};
/**
 * Build the allowed types for a Stored Schema.
 *
 * @remarks Staged upgrades do not apply to stored schemas, so we omit the {@link SimpleAllowedTypeAttributes.isStaged | staging flag } when building {@link SimpleAllowedTypeAttributes}.
 * @param types - The types to create allowed types for.
 * @returns The allowed types.
 */
function buildSimpleAllowedTypeAttributesForStoredSchema(types) {
    const allowedTypesInfo = new Map();
    for (const type of types) {
        // Stored schemas do not have staged upgrades
        allowedTypesInfo.set(type, { isStaged: undefined });
    }
    return allowedTypesInfo;
}
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,
        simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(schema.types),
        metadata: {},
        persistedMetadata: schema.persistedMetadata,
    };
}
/**
 * Export a {@link SimpleNodeSchema} from a {@link TreeNodeStoredSchema}.
 * @privateRemarks
 * TODO: Persist node metadata once schema FormatV2 is supported.
 * Note on SimpleNodeSchema construction: In the persisted format `persistedMetadata` is just called `metadata` whereas the `metadata`
 * field on SimpleNodeSchema is not persisted.
 */
function exportSimpleNodeSchemaStored(schema) {
    const arrayTypes = tryStoredSchemaAsArray(schema);
    if (arrayTypes !== undefined) {
        return {
            kind: NodeKind.Array,
            simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(arrayTypes),
            metadata: {},
            persistedMetadata: schema.metadata,
        };
    }
    if (schema instanceof ObjectNodeStoredSchema) {
        const fields = new Map();
        for (const [storedKey, field] of schema.objectNodeFields) {
            fields.set(storedKey, { ...exportSimpleFieldSchemaStored(field), storedKey });
        }
        return {
            kind: NodeKind.Object,
            fields,
            allowUnknownOptionalFields: undefined,
            metadata: {},
            persistedMetadata: schema.metadata,
        };
    }
    if (schema instanceof MapNodeStoredSchema) {
        assert(schema.mapFields.kind === FieldKinds.optional.identifier, 0xa95 /* Invalid map schema */);
        return {
            kind: NodeKind.Map,
            simpleAllowedTypes: buildSimpleAllowedTypeAttributesForStoredSchema(schema.mapFields.types),
            metadata: {},
            persistedMetadata: schema.metadata,
        };
    }
    if (schema instanceof LeafNodeStoredSchema) {
        return {
            kind: NodeKind.Leaf,
            leafKind: schema.leafValue,
            metadata: {},
            persistedMetadata: schema.metadata,
        };
    }
    fail(0xacb /* invalid schema kind */);
}
//# sourceMappingURL=sharedTree.js.map