/*!
 * 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 __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
import { createEmitter } from "@fluid-internal/client-utils";
import { assert, unreachableCase, fail } from "@fluidframework/core-utils/internal";
import { isStableId } from "@fluidframework/id-compressor/internal";
import { UsageError, } from "@fluidframework/telemetry-utils/internal";
import { FluidClientVersion, FormatValidatorNoOp, } from "../codec/index.js";
import { CommitKind, RevertibleStatus, TreeStoredSchemaRepository, combineVisitors, makeDetachedFieldIndex, rebaseChange, rootFieldKey, tagChange, visitDelta, isAncestor, moveToDetachedField, LeafNodeStoredSchema, diffHistories, makeAnonChange, deltaFieldMapHasVisibleChanges, } from "../core/index.js";
import { allowsRepoSuperset, buildForest, createNodeIdentifierManager, defaultSchemaPolicy, fieldBatchCodecBuilder, TreeStatus, intoDelta, jsonableTreeFromCursor, } from "../feature-libraries/index.js";
import { SquashingTransactionStack, SharedTreeBranch, TransactionResult as InternalTransactionResult, onForkTransitive, } from "../shared-tree-core/index.js";
import { getInnerNode, getKernel, customFromCursorStored, } from "../simple-tree/index.js";
import { Breakable, breakingMethod, disposeSymbol, getOrCreate, hasSome, throwIfBroken, } from "../util/index.js";
import { SchematizingSimpleTreeView } from "./schematizingTreeView.js";
import { SharedTreeChangeEnricher } from "./sharedTreeChangeEnricher.js";
import { SharedTreeChangeFamily, hasSchemaChange } from "./sharedTreeChangeFamily.js";
/**
 * Yields all defined (non-`undefined`) labels from a {@link LabelTree}, depth-first.
 */
function* collectTreeLabels(node) {
    if (node.label !== undefined) {
        yield node.label;
    }
    for (const child of node.sublabels) {
        yield* collectTreeLabels(child);
    }
}
/**
 * Builds the labels set for a change event from the label tree.
 * If the tree exists and contains at least one defined label, returns a set of all
 * values with the tree attached. Otherwise returns an empty set.
 */
function buildLabelsSet(labelTreeNode) {
    const set = new Set();
    if (labelTreeNode !== undefined) {
        for (const value of collectTreeLabels(labelTreeNode)) {
            set.add(value);
        }
        if (set.size > 0) {
            set.tree = labelTreeNode;
        }
    }
    return set;
}
/**
 * Creates a {@link TreeCheckout}.
 * @param args - an object containing optional components that will be used to build the view.
 * Any components not provided will be created by default.
 * @remarks This does not create a {@link SharedTree}, but rather a view with the minimal state
 * and functionality required to implement {@link ITreeCheckout}.
 */
export function createTreeCheckout(idCompressor, mintRevisionTag, revisionTagCodec, args) {
    const breaker = args?.breaker ?? new Breakable("TreeCheckout");
    const schema = args?.schema ?? new TreeStoredSchemaRepository();
    const forest = args?.forest ?? buildForest(breaker, schema);
    const defaultCodecOptions = {
        jsonValidator: FormatValidatorNoOp,
        minVersionForCollab: FluidClientVersion.v2_0,
    };
    const codecOptions = { ...defaultCodecOptions, ...args?.codecOptions };
    const changeFamily = args?.changeFamily ??
        new SharedTreeChangeFamily(revisionTagCodec, args?.fieldBatchCodec ?? fieldBatchCodecBuilder.build(codecOptions), codecOptions, args?.chunkCompressionStrategy, idCompressor);
    // TODO: If a branch is passed in, is it supposed to be local main?
    // Otherwise shouldn't we set `isSharedBranch` to false?
    const branch = args?.branch ??
        new SharedTreeBranch({
            change: changeFamily.rebaser.compose([]),
            revision: "root",
        }, changeFamily, () => idCompressor.generateCompressedId());
    return new TreeCheckout(branch, true, changeFamily, schema, forest, mintRevisionTag, revisionTagCodec, idCompressor, args?.removedRoots, args?.logger, breaker, args?.disposeForksAfterTransaction);
}
/**
 * Get the {@link TreeCheckout} associated with a given {@link TreeBranch}.
 */
function getCheckout(context) {
    if (context instanceof TreeCheckout) {
        return context;
    }
    if (context instanceof SchematizingSimpleTreeView) {
        return context.checkout;
    }
    throw new UsageError("Unsupported context implementation");
}
/**
 * Adds constraints to a `checkout`'s pending transaction.
 *
 * @param checkout - The checkout whose transaction will have the constraints added to it.
 * @param constraintsOnRevert - If true, use {@link ISharedTreeEditor.addNodeExistsConstraintOnRevert}.
 * @param constraints - The constraints to add to the transaction.
 *
 * @see {@link RunTransactionParams.preconditions}.
 */
export function addConstraintsToTransaction(checkout, constraintsOnRevert, constraints = []) {
    for (const constraint of constraints) {
        assertValidConstraint(constraint, constraintsOnRevert);
        const constraintType = constraint.type;
        switch (constraintType) {
            case "nodeInDocument": {
                const node = getInnerNode(constraint.node);
                assert(node.isHydrated(), 0xbc2 /* In document node must be hydrated. */);
                if (constraintsOnRevert) {
                    checkout.editor.addNodeExistsConstraintOnRevert(node.anchorNode);
                }
                else {
                    checkout.editor.addNodeExistsConstraint(node.anchorNode);
                }
                break;
            }
            case "noChange": {
                if (constraintsOnRevert) {
                    checkout.editor.addNoChangeConstraintOnRevert();
                }
                else {
                    checkout.editor.addNoChangeConstraint();
                }
                break;
            }
            default: {
                unreachableCase(constraintType);
            }
        }
    }
}
/**
 * Throws if the given {@link TransactionConstraintAlpha | transaction constraint} is not currently satisfied.
 */
export function assertValidConstraint(constraint, onRevert) {
    switch (constraint.type) {
        case "nodeInDocument": {
            const nodeStatus = getKernel(constraint.node).getStatus();
            if (nodeStatus !== TreeStatus.InDocument) {
                const revertText = onRevert ? " on revert" : "";
                throw new UsageError(`Attempted to add a "nodeInDocument" constraint${revertText}, but the node is not currently in the document. Node status: ${nodeStatus}`);
            }
            break;
        }
        case "noChange": {
            // This constraint is always satisfied at the time of checking, since it just requires that no changes have been made since the transaction callback returned.
            break;
        }
        default: {
            unreachableCase(constraint);
        }
    }
}
/**
 * An implementation of {@link ITreeCheckout}.
 */
let TreeCheckout = (() => {
    var _a, _TreeCheckout_events, _TreeCheckout_transaction, _TreeCheckout_validatedCommits;
    let _instanceExtraInitializers = [];
    let _exportVerbose_decorators;
    let _applySerializedChange_decorators;
    let _applyChange_decorators;
    let _runTransaction_decorators;
    let _runTransactionAsync_decorators;
    let _viewWith_decorators;
    let _fork_decorators;
    return _a = class TreeCheckout {
            constructor(branch, 
            /** True if and only if this checkout is for a branch which is persisted and shared with other clients. */
            isSharedBranch, changeFamily, storedSchema, forest, mintRevisionTag, revisionTagCodec, idCompressor, _removedRoots = makeDetachedFieldIndex("repair", revisionTagCodec, idCompressor), 
            /** Optional logger for telemetry. */
            logger, breaker = new Breakable("TreeCheckout"), disposeForksAfterTransaction = true) {
                this.isSharedBranch = (__runInitializers(this, _instanceExtraInitializers), isSharedBranch);
                this.changeFamily = changeFamily;
                this.storedSchema = storedSchema;
                this.forest = forest;
                this.mintRevisionTag = mintRevisionTag;
                this.revisionTagCodec = revisionTagCodec;
                this.idCompressor = idCompressor;
                this._removedRoots = _removedRoots;
                this.logger = logger;
                this.breaker = breaker;
                this.disposeForksAfterTransaction = disposeForksAfterTransaction;
                this.disposed = false;
                this.views = new Set();
                /**
                 * Set of revertibles maintained for automatic disposal
                 */
                this.revertibles = new Set();
                /**
                 * Each branch's head commit corresponds to a revertible commit.
                 * Maintaining a whole branch ensures the commit graph is not pruned in a way that would prevent the commit from
                 * being reverted.
                 */
                this.revertibleCommitBranches = new Map();
                _TreeCheckout_events.set(this, createEmitter());
                this.events = __classPrivateFieldGet(this, _TreeCheckout_events, "f");
                this.onAfterBranchChange = (event) => {
                    // The following logic allows revertibles to be generated for the change.
                    // Currently only appends (including merges and transaction commits) are supported.
                    if (event.type === "append") {
                        // TODO:#20949: When the SharedTree is detached, these commits will already have been garbage collected.
                        //       Figure out a way to generate revertibles before the commits are garbage collected.
                        for (const commit of event.newCommits) {
                            const kind = event.type === "append" ? event.kind : CommitKind.Default;
                            const { change, revision } = commit;
                            const getRevertible = hasSchemaChange(change)
                                ? undefined
                                : (onRevertibleDisposed) => {
                                    if (!withinEventContext) {
                                        throw new UsageError("Cannot get a revertible outside of the context of a changed event.");
                                    }
                                    if (this.revertibleCommitBranches.get(revision) !== undefined) {
                                        throw new UsageError("Cannot generate the same revertible more than once. Note that this can happen when multiple changed event listeners are registered.");
                                    }
                                    const revertible = this.createRevertible(revision, kind, this, onRevertibleDisposed);
                                    this.revertibleCommitBranches.set(revision, __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch.fork(commit));
                                    this.revertibles.add(revertible);
                                    return revertible;
                                };
                            let withinEventContext = true;
                            const metadata = {
                                kind,
                                isLocal: true,
                                getChange: () => {
                                    const context = {
                                        idCompressor: this.idCompressor,
                                        originatorId: this.idCompressor.localSessionId,
                                        revision,
                                        isSummary: false,
                                    };
                                    const encodedChange = this.changeFamily.codecs.resolve(4).encode(change, context);
                                    assert(commit.parent !== undefined, 0xca4 /* Expected applied commit to be parented */);
                                    return {
                                        version: 1,
                                        revision,
                                        originatorId: this.idCompressor.localSessionId,
                                        change: encodedChange,
                                    };
                                },
                                getRevertible: (onDisposed) => getRevertible?.(onDisposed),
                                label: this.labelTreeNode?.label,
                                labels: buildLabelsSet(this.labelTreeNode),
                            };
                            __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("changed", metadata, getRevertible);
                            withinEventContext = false;
                        }
                    }
                    else if (this.isRemoteChangeEvent(event)) {
                        // TODO: figure out how to plumb through commit kind info for remote changes
                        __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("changed", {
                            isLocal: false,
                            kind: CommitKind.Default,
                            labels: new Set(),
                        });
                    }
                };
                this.onAfterChange = (event) => {
                    this.editLock.lock();
                    __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("beforeBatch", event);
                    if (event.change !== undefined) {
                        const revision = event.type === "rebase"
                            ? __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch.getHead().revision
                            : event.change.revision;
                        this.applyInternalChange(event.change.change, revision);
                    }
                    __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("afterBatch");
                    this.editLock.unlock();
                    if (event.type === "append") {
                        for (const commit of event.newCommits) {
                            this.validateCommit(commit);
                        }
                    }
                };
                this.onAncestryTrimmed = (revisions) => {
                    // When the branch is trimmed, we can garbage collect any repair data whose latest relevant revision is one of the
                    // trimmed revisions.
                    this.withCombinedVisitor((visitor) => {
                        for (const revision of revisions) {
                            // get all the roots last created or used by the revision
                            const roots = this._removedRoots.getRootsLastTouchedByRevision(revision);
                            // get the detached field for the root and delete it from the removed roots
                            for (const root of roots) {
                                visitor.destroy(this._removedRoots.toFieldKey(root), 1);
                            }
                            this._removedRoots.deleteRootsLastTouchedByRevision(revision);
                        }
                    });
                };
                /**
                 * The {@link Transactor} for this checkout.
                 * @remarks In the context of a checkout, transactions allow edits to be batched into atomic units.
                 * Edits made during a transaction will update the local state of the tree immediately, but will be squashed into a single edit when the transaction is committed.
                 * If the transaction is aborted, the local state will be reset to what it was before the transaction began.
                 * Transactions may nest, meaning that a transaction may be started while a transaction is already ongoing.
                 *
                 * To avoid updating observers of the view state with intermediate results during a transaction,
                 * use {@link ITreeCheckout#fork} and {@link ISharedTreeFork#merge}.
                 */
                _TreeCheckout_transaction.set(this, void 0);
                // #region Commit Validation
                /** Used to maintain the contract of {@link onCommitValid}(). */
                _TreeCheckout_validatedCommits.set(this, new WeakMap());
                // #endregion Commit Validation
                // #region Enrichment
                /**
                 * Statistics about enrichment operations performed by this checkout.
                 * Used for testing. May be used for telemetry in the future.
                 * The performance cost of maintaining this information is negligible.
                 */
                this.enrichmentStats = { batches: 0, diffs: 0, commitsEnriched: 0, refreshers: 0, forks: 0, applied: 0 };
                __classPrivateFieldSet(this, _TreeCheckout_transaction, this.createTransactionStack(branch), "f");
                this.editLock = new EditLock(__classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranchEditor);
                this.registerForBranchEvents();
            }
            /**
             * Pushes a new label node for a transaction nesting level.
             *
             * @param label - The label for this transaction level.
             *
             * @remarks
             * Called at the start of each transaction (including nested ones).
             * Creates a new {@link LabelTree} node as a child of the current deepest node (if any),
             * or as the root if this is the outermost transaction.
             */
            pushLabelFrame(label) {
                const node = { label, sublabels: [] };
                if (this.labelTreeNode === undefined) {
                    this.labelTreeNode = node;
                }
                else {
                    const current = this.currentLabelNode();
                    assert(current !== undefined, 0xcdb /* Expected current label node to exist */);
                    current.sublabels.push(node);
                }
            }
            /**
             * Returns the deepest open node on the right side of the label tree, which corresponds
             * to the current (most recently pushed, not yet committed or aborted) transaction.
             * Returns `undefined` when {@link TreeCheckout.labelTreeNode} is not set.
             */
            currentLabelNode() {
                if (this.labelTreeNode === undefined) {
                    return undefined;
                }
                // Walk down the right spine of the tree. Only one closed node is ever reachable
                // during this traversal, so a single pointer (mostRecentlyClosedLabelNode) suffices
                // to stop descending past committed nodes.
                let node = this.labelTreeNode;
                while (node.sublabels.length > 0) {
                    const lastChild = node.sublabels[node.sublabels.length - 1];
                    assert(lastChild !== undefined, 0xcdc /* Expected label tree node to have children */);
                    if (lastChild === this.mostRecentlyClosedLabelNode) {
                        break;
                    }
                    node = lastChild;
                }
                return node;
            }
            /**
             * Pops the current label frame from the label tree.
             * @param aborted - If true, the node is removed from the tree (transaction was aborted).
             * If false, the node is kept in the tree but marked as closed (transaction was committed).
             */
            popLabelFrame(aborted) {
                const node = this.currentLabelNode();
                if (node === undefined) {
                    return;
                }
                if (aborted) {
                    if (node === this.labelTreeNode) {
                        this.labelTreeNode = undefined;
                        this.mostRecentlyClosedLabelNode = undefined;
                    }
                    else {
                        // Temporarily mark node as closed so currentLabelNode() returns its parent.
                        this.mostRecentlyClosedLabelNode = node;
                        const parent = this.currentLabelNode();
                        assert(parent !== undefined, 0xcdd /* Expected parent label node to exist */);
                        parent.sublabels.pop();
                        // Point to the parent's new last child (guaranteed closed if it exists),
                        // or undefined if the parent has no more children.
                        const newLastChild = parent.sublabels[parent.sublabels.length - 1];
                        this.mostRecentlyClosedLabelNode = newLastChild;
                    }
                }
                else {
                    this.mostRecentlyClosedLabelNode = node;
                }
            }
            /**
             * Helper method for {@link SchematizingSimpleTreeView.runTransaction} to properly clear transaction labels once the function completes.
             *
             * @remarks
             * The label is stored during the execution of the function and will be included in the {@link ChangeMetadata} of the transaction.
             *
             * Labels supplied to nested transactions are ignored - only the outermost transaction label is ever used.
             *
             * @param fn - The function to execute. It receives the user provided transaction label as an optional parameter.
             * @param label - The label to associate with the outermost transaction.
             * @returns The result of executing `fn`.
             */
            runWithTransactionLabel(fn, label) {
                try {
                    return fn(label);
                }
                finally {
                    // Only clear the label tree when the outermost transaction has completed.
                    // Inner transactions' commits don't fire the "changed" event, so the label tree
                    // must remain intact until the outermost commit reads it.
                    if (this.transaction.size === 0) {
                        this.labelTreeNode = undefined;
                        this.mostRecentlyClosedLabelNode = undefined;
                    }
                }
            }
            get removedRoots() {
                return this._removedRoots;
            }
            registerForBranchEvents() {
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch.events.on("afterChange", this.onAfterBranchChange);
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranchEvents.on("afterChange", this.onAfterChange);
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranchEvents.on("ancestryTrimmed", this.onAncestryTrimmed);
            }
            unregisterFromBranchEvents() {
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch.events.off("afterChange", this.onAfterBranchChange);
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranchEvents.off("afterChange", this.onAfterChange);
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranchEvents.off("ancestryTrimmed", this.onAncestryTrimmed);
            }
            createTransactionStack(branch) {
                return new SquashingTransactionStack(branch, this.mintRevisionTag, () => {
                    const disposeForks = this.disposeForksAfterTransaction
                        ? trackForksForDisposal(this)
                        : undefined;
                    // When each transaction is started, make a restorable checkpoint of the current state of removed roots
                    const restoreRemovedRoots = this._removedRoots.createCheckpoint();
                    return (result, viewUpdate) => {
                        const newHead = __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch.getHead();
                        switch (result) {
                            case InternalTransactionResult.Abort: {
                                restoreRemovedRoots();
                                if (viewUpdate !== undefined) {
                                    this.applyInternalChange(viewUpdate, newHead.revision);
                                }
                                break;
                            }
                            case InternalTransactionResult.Commit: {
                                if (viewUpdate !== undefined) {
                                    this.applyInternalChange(viewUpdate, newHead.revision);
                                }
                                if (this.transaction.size === 0) {
                                    // The changes in a transaction squash commit have already applied to the checkout and are known to be valid, so we can validate the squash commit automatically.
                                    this.validateCommit(newHead);
                                }
                                break;
                            }
                            default: {
                                unreachableCase(result);
                            }
                        }
                        disposeForks?.();
                    };
                });
            }
            exportVerbose() {
                const cursor = this.forest.allocateCursor("contentSnapshot");
                try {
                    moveToDetachedField(this.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();
                }
            }
            /**
             * Applies the given serialized change (as was produced via a `"changed"` event of another checkout) to this checkout.
             */
            applySerializedChange(serializedChange) {
                if (!isSerializedChange(serializedChange)) {
                    throw new UsageError(`Cannot apply change. Invalid serialized change format.`);
                }
                const { revision, originatorId, change } = serializedChange;
                if (originatorId !== this.idCompressor.localSessionId) {
                    throw new UsageError(`Cannot apply change. A serialized changed must be applied to the same SharedTree as it was created from.`);
                }
                const context = {
                    idCompressor: this.idCompressor,
                    originatorId: this.idCompressor.localSessionId,
                    revision,
                    isSummary: false,
                };
                const decodedChange = this.changeFamily.codecs.resolve(4).decode(change, context);
                // Apply the change to the branch, but _not_ the `activeBranch` - we do not support squashing serialized commits in a transaction.
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch.apply(tagChange(decodedChange, revision));
            }
            // #region TreeBranchAlpha
            applyChange(change) {
                this.applySerializedChange(change);
            }
            isBranch() {
                return true;
            }
            hasRootSchema(_schema) {
                return false;
            }
            runTransaction(transaction, params) {
                this.mountTransaction(params, false);
                const transactionCallbackStatus = transaction();
                return this.unmountTransaction(transactionCallbackStatus, params);
            }
            async runTransactionAsync(transaction, params) {
                this.mountTransaction(params, true);
                const transactionCallbackStatus = await transaction();
                return this.unmountTransaction(transactionCallbackStatus, params);
            }
            mountTransaction(params, isAsync) {
                this.checkNotDisposed();
                if (isAsync && this.transaction.size > 0) {
                    throw new UsageError("An asynchronous transaction cannot be started while another transaction is already in progress.");
                }
                this.pushLabelFrame(params?.label);
                this.transaction.start();
                addConstraintsToTransaction(this, false, params?.preconditions);
            }
            unmountTransaction(transactionCallbackStatus, params) {
                this.checkNotDisposed();
                const rollback = transactionCallbackStatus?.rollback;
                const value = transactionCallbackStatus?.value;
                if (rollback === true) {
                    this.popLabelFrame(true);
                    this.transaction.abort();
                    return value === undefined
                        ? { success: false }
                        : { success: false, value: value };
                }
                addConstraintsToTransaction(this, true, transactionCallbackStatus?.preconditionsOnRevert);
                this.popLabelFrame(false);
                this.runWithTransactionLabel(() => {
                    this.transaction.commit();
                }, params?.label);
                return value === undefined
                    ? { success: true }
                    : { success: true, value: value };
            }
            // #endregion TreeBranchAlpha
            // Revision is the revision of the commit, if any, which caused this change.
            applyInternalChange(change, revision) {
                // Conflicts due to schema will be empty and thus are not applied.
                for (const innerChange of change.changes) {
                    if (innerChange.type === "data") {
                        const delta = intoDelta(tagChange(innerChange.innerChange, revision));
                        this.withCombinedVisitor((visitor) => {
                            visitDelta(delta, visitor, this._removedRoots, revision);
                        });
                    }
                    else if (innerChange.type === "schema") {
                        // Schema changes from a current to a new schema are expected to be backwards compatible.
                        // This guarantees that all data in the forest (which is valid before the schema change)
                        // is also valid under the new schema.
                        // Note however, that such schema changes may in some cases be rolled back:
                        // Case 1: A transaction with a schema change may be aborted.
                        // The transaction may have made some data changes that would render some trees invalid
                        // under the old schema, but these changes will also be rolled back, thereby putting the forest
                        // back in the state before the transaction, which is valid under the original (reinstated) schema.
                        // Case 2: A branch with a schema change may be rebased such that the schema change (because
                        // of a constraint) is no longer applied.
                        // Such a branch may contain data changes that would render some trees invalid under the
                        // original schema. These data changes may not necessarily be rolled back.
                        // They will however be rebased over the rollback of the schema change. This rebasing will
                        // ensure that these data changes are muted if they would render some trees invalid under the
                        // original (reinstated) schema.
                        this.storedSchema.apply(innerChange.innerChange.schema.new);
                    }
                    else {
                        fail(0xad1 /* Unknown Shared Tree change type. */);
                    }
                }
            }
            withCombinedVisitor(fn) {
                const anchorVisitor = this.forest.anchors.acquireVisitor();
                const combinedVisitor = combineVisitors([this.forest.acquireVisitor(), anchorVisitor]);
                fn(combinedVisitor);
                combinedVisitor.free();
            }
            checkNotDisposed(usageError) {
                this.breaker.use();
                if (this.disposed) {
                    if (usageError !== undefined) {
                        throw new UsageError(usageError);
                    }
                    assert(false, 0x911 /* Invalid operation on a disposed TreeCheckout */);
                }
            }
            /**
             * Creates a {@link RevertibleAlpha} object that can undo a specific change in the tree's history.
             * Revision must exist in the given {@link TreeCheckout}'s branch.
             *
             * @param revision - The revision tag identifying the change to be made revertible.
             * @param kind - The {@link CommitKind} that produced this revertible (e.g., Default, Undo, Redo).
             * @param checkout - The {@link TreeCheckout} instance this revertible belongs to.
             * @param onRevertibleDisposed - Callback function that will be called when the revertible is disposed.
             * @returns A {@link RevertibleAlpha} object.
             */
            createRevertible(revision, kind, checkout, onRevertibleDisposed) {
                const commitBranches = checkout.revertibleCommitBranches;
                const revertible = {
                    get status() {
                        const revertibleCommit = commitBranches.get(revision);
                        return revertibleCommit === undefined
                            ? RevertibleStatus.Disposed
                            : RevertibleStatus.Valid;
                    },
                    revert: (release = true) => {
                        if (revertible.status === RevertibleStatus.Disposed) {
                            throw new UsageError("Unable to revert a revertible that has been disposed.");
                        }
                        const revertMetrics = checkout.revertRevertible(revision, kind);
                        checkout.logger?.sendTelemetryEvent({
                            eventName: _a.revertTelemetryEventName,
                            ...revertMetrics,
                        });
                        if (release) {
                            revertible.dispose();
                        }
                    },
                    clone: (targetBranch) => {
                        // TODO:#23442: When a revertible is cloned for a forked branch, optimize to create a fork of a revertible branch once per revision NOT once per revision per checkout.
                        const targetCheckout = getCheckout(targetBranch);
                        const revertibleBranch = this.revertibleCommitBranches.get(revision);
                        if (revertibleBranch === undefined) {
                            throw new UsageError("Unable to clone a revertible that has been disposed.");
                        }
                        const commitToRevert = revertibleBranch.getHead();
                        const activeBranchHead = __classPrivateFieldGet(targetCheckout, _TreeCheckout_transaction, "f").activeBranch.getHead();
                        if (isAncestor(commitToRevert, activeBranchHead, true) === false) {
                            throw new UsageError("Cannot clone revertible for a commit that is not present on the given branch.");
                        }
                        targetCheckout.revertibleCommitBranches.set(revision, revertibleBranch.fork());
                        return this.createRevertible(revision, kind, targetCheckout, onRevertibleDisposed);
                    },
                    dispose: () => {
                        if (revertible.status === RevertibleStatus.Disposed) {
                            throw new UsageError("Unable to dispose a revertible that has already been disposed.");
                        }
                        checkout.disposeRevertible(revertible, revision);
                        onRevertibleDisposed?.(revertible);
                    },
                };
                return revertible;
            }
            viewWith(config) {
                const view = new SchematizingSimpleTreeView(this, config, createNodeIdentifierManager(this.idCompressor), () => {
                    this.views.delete(view);
                });
                this.views.add(view);
                return view;
            }
            get rootEvents() {
                return this.forest.anchors.events;
            }
            get editor() {
                this.checkNotDisposed();
                return this.editLock.editor;
            }
            locate(anchor) {
                this.checkNotDisposed();
                return this.forest.anchors.locate(anchor);
            }
            get transaction() {
                return __classPrivateFieldGet(this, _TreeCheckout_transaction, "f");
            }
            fork() {
                this.checkNotDisposed("The parent branch has already been disposed and can no longer create new branches.");
                // Branching after an unfinished transaction would expose the application to a state where its invariants may be violated.
                if (this.transaction.size > 0) {
                    throw new UsageError("A view cannot be forked while it has a pending transaction.");
                }
                this.editLock.checkUnlocked("Branching");
                const branch = __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch.fork();
                const storedSchema = this.storedSchema.clone();
                const forkBreaker = new Breakable("TreeCheckout");
                const forest = this.forest.clone(storedSchema, forkBreaker);
                const checkout = new _a(branch, false, this.changeFamily, storedSchema, forest, this.mintRevisionTag, this.revisionTagCodec, this.idCompressor, this._removedRoots.clone(), this.logger, forkBreaker, this.disposeForksAfterTransaction);
                __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("fork", checkout);
                return checkout;
            }
            switchBranch(branch) {
                // TODO: Dispose old branch, if necessary
                assert(__classPrivateFieldGet(this, _TreeCheckout_transaction, "f").size === 0, 0xc55 /* Cannot switch branches during a transaction */);
                const diff = diffHistories(this.changeFamily.rebaser, __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch.getHead(), branch.getHead(), this.mintRevisionTag);
                this.unregisterFromBranchEvents();
                __classPrivateFieldSet(this, _TreeCheckout_transaction, this.createTransactionStack(branch), "f");
                this.editLock = new EditLock(__classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranchEditor);
                this.registerForBranchEvents();
                // TODO: Rework eventing
                this.applyInternalChange(diff);
                __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("afterBatch");
            }
            rebase(branch) {
                const checkout = getCheckout(branch);
                this.checkNotDisposed("The target branch of the rebase has been disposed and cannot be rebased.");
                checkout.checkNotDisposed("The source branch of the rebase has been disposed and cannot be rebased.");
                this.editLock.checkUnlocked("Rebasing");
                if (this.transaction.size > 0) {
                    throw new UsageError("Views cannot be rebased onto a view that has a pending transaction.");
                }
                if (checkout.transaction.size > 0) {
                    throw new UsageError("A view cannot be rebased while it has a pending transaction.");
                }
                assert(!checkout.isSharedBranch, 0xa5d /* Shared branches cannot be rebased onto another branch. */);
                __classPrivateFieldGet(checkout, _TreeCheckout_transaction, "f").activeBranch.rebaseOnto(__classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch);
            }
            rebaseOnto(branch) {
                getCheckout(branch).rebase(this);
            }
            merge(branch, disposeMerged = true) {
                const checkout = getCheckout(branch);
                this.checkNotDisposed("The target branch of the merge has been disposed and cannot be merged.");
                checkout.checkNotDisposed("The source branch of the merge has been disposed and cannot be merged.");
                this.editLock.checkUnlocked("Merging");
                if (this.transaction.size > 0) {
                    throw new UsageError("Views cannot be merged into a view while it has a pending transaction.");
                }
                if (checkout.transaction.size > 0) {
                    throw new UsageError("Views with an open transaction cannot be merged into another view.");
                }
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch.merge(__classPrivateFieldGet(checkout, _TreeCheckout_transaction, "f").activeBranch);
                if (disposeMerged && !checkout.isSharedBranch) {
                    // Dispose the merged checkout unless it is a shared branch.
                    checkout[disposeSymbol]();
                }
            }
            updateSchema(newSchema, allowNonSupersetSchema) {
                this.checkNotDisposed();
                if (allowNonSupersetSchema !== true) {
                    assert(allowsRepoSuperset(defaultSchemaPolicy, this.storedSchema.clone(), newSchema), 0xbe6 /* New schema must allow all documents allowed by old schema */);
                }
                this.editor.schema.setStoredSchema(this.storedSchema.clone(), newSchema);
            }
            dispose() {
                this.editLock.checkUnlocked("Disposing a view");
                this[disposeSymbol]();
            }
            [(_TreeCheckout_events = new WeakMap(), _TreeCheckout_transaction = new WeakMap(), _TreeCheckout_validatedCommits = new WeakMap(), _exportVerbose_decorators = [throwIfBroken], _applySerializedChange_decorators = [throwIfBroken], _applyChange_decorators = [throwIfBroken], _runTransaction_decorators = [breakingMethod], _runTransactionAsync_decorators = [breakingMethod], _viewWith_decorators = [throwIfBroken], _fork_decorators = [throwIfBroken], disposeSymbol)]() {
                this.checkNotDisposed("The branch has already been disposed and cannot be disposed again.");
                this.disposed = true;
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch.dispose();
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").dispose();
                this.purgeRevertibles();
                for (const view of this.views) {
                    view.dispose();
                }
                __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("dispose");
            }
            getRemovedRoots() {
                this.assertNoUntrackedRoots();
                const trees = [];
                const cursor = this.forest.allocateCursor("getRemovedRoots");
                for (const { id, root } of this._removedRoots.entries()) {
                    const parentField = this._removedRoots.toFieldKey(root);
                    this.forest.moveCursorToPath({ parent: undefined, parentField, parentIndex: 0 }, cursor);
                    const tree = jsonableTreeFromCursor(cursor);
                    // This method is used for tree consistency comparison.
                    const { major, minor } = id;
                    const finalizedMajor = major === undefined ? major : this.revisionTagCodec.encode(major);
                    trees.push([finalizedMajor, minor, tree]);
                }
                cursor.free();
                return trees;
            }
            /**
             * This must be called on the root/main checkout after loading from a summary.
             * @remarks This pattern is necessary because the EditManager skips the normal process of applying commits to branches when loading a summary.
             * Instead, it simply {@link SharedTreeBranch#setHead | mutates} the branches directly which does not propagate the typical events throughout the rest of the system.
             */
            load() {
                // Set the tip revision as the latest relevant revision for any removed roots that are loaded from a summary - this allows them to be garbage collected later.
                // When a load happens, the head of the trunk and the head of the local/main branch must be the same (this is enforced by SharedTree).
                this._removedRoots.setRevisionsForLoadedData(__classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch.getHead().revision);
                // The content of the checkout (e.g. the forest) has (maybe) changed, so fire an afterBatch event.
                __classPrivateFieldGet(this, _TreeCheckout_events, "f").emit("afterBatch");
            }
            purgeRevertibles() {
                for (const revertible of this.revertibles) {
                    revertible.dispose();
                }
            }
            disposeRevertible(revertible, revision) {
                this.revertibleCommitBranches.get(revision)?.dispose();
                this.revertibleCommitBranches.delete(revision);
                this.revertibles.delete(revertible);
            }
            revertRevertible(revision, kind) {
                this.editLock.checkUnlocked("Reverting a commit");
                if (this.transaction.size > 0) {
                    throw new UsageError("Undo is not yet supported during transactions.");
                }
                const revertibleBranch = this.revertibleCommitBranches.get(revision);
                assert(revertibleBranch !== undefined, 0x7cc /* expected to find a revertible commit */);
                const commitToRevert = revertibleBranch.getHead();
                const revisionForInvert = this.mintRevisionTag();
                let change = tagChange(this.changeFamily.rebaser.invert(commitToRevert, false, revisionForInvert), revisionForInvert);
                const headCommit = __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch.getHead();
                // Rebase the inverted change onto any commits that occurred after the undoable commits.
                if (commitToRevert !== headCommit) {
                    // The inverse may be rebased over newer commits which (despite being present in the history)
                    // have no net effect on the document state (e.g. an edit plus its undo).
                    // In that case, a no-change constraint should not be violated during rebase.
                    const diff = diffHistories(this.changeFamily.rebaser, commitToRevert, headCommit, this.mintRevisionTag);
                    const ignoreNoChangeViolation = !sharedTreeChangeHasVisibleChanges(diff);
                    change = tagChange(rebaseChange(this.changeFamily.rebaser, change, commitToRevert, headCommit, this.mintRevisionTag, ignoreNoChangeViolation).change, revisionForInvert);
                }
                __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch.apply(change, kind === CommitKind.Default || kind === CommitKind.Redo
                    ? CommitKind.Undo
                    : CommitKind.Redo);
                // Derive some stats about the reversion to return to the caller.
                let revertAge = 0;
                let currentCommit = headCommit;
                while (commitToRevert.revision !== currentCommit.revision) {
                    revertAge++;
                    const parentCommit = currentCommit.parent;
                    assert(parentCommit !== undefined, 0x9a9 /* expected to find a parent commit */);
                    currentCommit = parentCommit;
                }
                return { age: revertAge };
            }
            assertNoUntrackedRoots() {
                const cursor = this.forest.getCursorAboveDetachedFields();
                const rootFields = new Set([rootFieldKey]);
                for (const { root } of this._removedRoots.entries()) {
                    rootFields.add(this._removedRoots.toFieldKey(root));
                }
                if (!cursor.firstField()) {
                    return;
                }
                do {
                    const field = cursor.getFieldKey();
                    assert(rootFields.has(field), 0xa22 /* Forest has a root field which is unknown to the detached field index */);
                    rootFields.delete(field);
                } while (cursor.nextField());
            }
            /**
             * `true` iff the given branch change event is due to a remote change
             */
            isRemoteChangeEvent(event) {
                return (
                // Remote changes are only ever applied to shared branches
                this.isSharedBranch &&
                    // Remote changes are applied to the branch by rebasing it onto the trunk.
                    // No other rebases are allowed on shared branches, so we can use this to detect remote changes.
                    event.type === "rebase");
            }
            /**
             * Registers a function to be called when the given commit is validated.
             * @remarks A commit is validated by the checkout after it has been applied to the checkout's state (e.g. it has an effect on the forest).
             * If the commit applies successfully (i.e. it does not raise any unexpected errors), the commit is considered valid and the registered function is called.
             * If the commit does not apply successfully (because it causes an unexpected error), the function is not called (and the checkout will left in an error state).
             *
             * If the commit has already been validated when this function is called, the function is called immediately and this function returns `true`.
             * Otherwise, the function is registered to be called later and this function returns `false`.
             */
            onCommitValid(commit, fn) {
                const validated = getOrCreate(__classPrivateFieldGet(this, _TreeCheckout_validatedCommits, "f"), commit, () => []);
                if (validated === true) {
                    fn(commit);
                    return true;
                }
                validated.push(fn);
                return false;
            }
            /** Mark the given commit as "validated" according to the contract of {@link onCommitValid}(). */
            validateCommit(commit) {
                const validated = getOrCreate(__classPrivateFieldGet(this, _TreeCheckout_validatedCommits, "f"), commit, () => []);
                if (validated !== true) {
                    for (const fn of validated) {
                        fn(commit);
                    }
                    __classPrivateFieldGet(this, _TreeCheckout_validatedCommits, "f").set(commit, true);
                }
            }
            resetEnrichmentStats() {
                this.enrichmentStats.batches = 0;
                this.enrichmentStats.diffs = 0;
                this.enrichmentStats.commitsEnriched = 0;
                this.enrichmentStats.refreshers = 0;
                this.enrichmentStats.forks = 0;
                this.enrichmentStats.applied = 0;
            }
            getEnrichmentStats() {
                return { ...this.enrichmentStats };
            }
            enrich(context, changes) {
                if (!hasSome(changes)) {
                    return [];
                }
                this.enrichmentStats.batches += 1;
                const enricher = new SharedTreeChangeEnricher(this.forest, this._removedRoots, this.storedSchema, this.idCompressor, () => (this.enrichmentStats.commitsEnriched += 1), () => (this.enrichmentStats.refreshers += 1), () => (this.enrichmentStats.forks += 1), () => (this.enrichmentStats.applied += 1));
                // This `lastCommitApplied` may be on the main branch or on a transaction branch.
                // In either case, it is crucial that the state of the forest & detached field index reflects all changes up to and including this commit.
                const lastCommitApplied = __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").activeBranch.getHead();
                if (context !== lastCommitApplied) {
                    enricher.enqueueChange(() => {
                        this.enrichmentStats.diffs += 1;
                        const diff = diffHistories(this.changeFamily.rebaser, lastCommitApplied, context, this.mintRevisionTag);
                        return makeAnonChange(diff);
                    });
                }
                const enriched = [];
                for (const change of changes) {
                    enriched.push(enricher.enrich(change.change));
                    enricher.enqueueChange(change);
                }
                enricher[disposeSymbol]();
                return enriched;
            }
            get mainBranch() {
                return __classPrivateFieldGet(this, _TreeCheckout_transaction, "f").branch;
            }
        },
        (() => {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            __esDecorate(_a, null, _exportVerbose_decorators, { kind: "method", name: "exportVerbose", static: false, private: false, access: { has: obj => "exportVerbose" in obj, get: obj => obj.exportVerbose }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(_a, null, _applySerializedChange_decorators, { kind: "method", name: "applySerializedChange", static: false, private: false, access: { has: obj => "applySerializedChange" in obj, get: obj => obj.applySerializedChange }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(_a, null, _applyChange_decorators, { kind: "method", name: "applyChange", static: false, private: false, access: { has: obj => "applyChange" in obj, get: obj => obj.applyChange }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(_a, null, _runTransaction_decorators, { kind: "method", name: "runTransaction", static: false, private: false, access: { has: obj => "runTransaction" in obj, get: obj => obj.runTransaction }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(_a, null, _runTransactionAsync_decorators, { kind: "method", name: "runTransactionAsync", static: false, private: false, access: { has: obj => "runTransactionAsync" in obj, get: obj => obj.runTransactionAsync }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(_a, null, _viewWith_decorators, { kind: "method", name: "viewWith", static: false, private: false, access: { has: obj => "viewWith" in obj, get: obj => obj.viewWith }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(_a, null, _fork_decorators, { kind: "method", name: "fork", static: false, private: false, access: { has: obj => "fork" in obj, get: obj => obj.fork }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        })(),
        /**
         * The name of the telemetry event logged for calls to {@link TreeCheckout.revertRevertible}.
         * @privateRemarks Exposed for testing purposes.
         */
        _a.revertTelemetryEventName = "RevertRevertible",
        _a;
})();
export { TreeCheckout };
/**
 * A helper class that assists {@link TreeCheckout} in preventing functionality from being used while the tree is in the middle of being edited.
 */
class EditLock {
    /**
     * @param editor - an editor which will be used to create a new editor that is monitored to determine if any changes are happening to the tree.
     * Use {@link EditLock.editor} in place of the original editor to ensure that changes are monitored.
     */
    constructor(editor) {
        this.locked = false;
        const checkLock = () => this.checkUnlocked("Editing the tree");
        this.editor = {
            get schema() {
                return editor.schema;
            },
            valueField(...fieldArgs) {
                const valueField = editor.valueField(...fieldArgs);
                return {
                    set(...editArgs) {
                        checkLock();
                        valueField.set(...editArgs);
                    },
                };
            },
            optionalField(...fieldArgs) {
                const optionalField = editor.optionalField(...fieldArgs);
                return {
                    set(...editArgs) {
                        checkLock();
                        optionalField.set(...editArgs);
                    },
                };
            },
            sequenceField(...fieldArgs) {
                const sequenceField = editor.sequenceField(...fieldArgs);
                return {
                    insert(...editArgs) {
                        checkLock();
                        sequenceField.insert(...editArgs);
                    },
                    remove(...editArgs) {
                        checkLock();
                        sequenceField.remove(...editArgs);
                    },
                };
            },
            move(...moveArgs) {
                checkLock();
                editor.move(...moveArgs);
            },
            addNodeExistsConstraint(path) {
                editor.addNodeExistsConstraint(path);
            },
            addNodeExistsConstraintOnRevert(path) {
                editor.addNodeExistsConstraintOnRevert(path);
            },
            addNoChangeConstraint() {
                editor.addNoChangeConstraint();
            },
            addNoChangeConstraintOnRevert() {
                editor.addNoChangeConstraintOnRevert();
            },
        };
    }
    /**
     * Prevent further changes from being made to {@link EditLock.editor} until {@link EditLock.unlock} is called.
     * @remarks May only be called when the lock is not already locked.
     */
    lock() {
        if (this.locked) {
            debugger;
        }
        assert(!this.locked, 0xaa7 /* Checkout has already been locked */);
        this.locked = true;
    }
    /**
     * Throws an error if the lock is currently locked.
     * @param action - The current action being performed by the user.
     * This must start with a capital letter, as it shows up as the first part of the error message and we want it to look nice.
     */
    checkUnlocked(action) {
        if (this.locked) {
            // These type assertions ensure that the event name strings used here match the actual event names
            const nodeChanged = "nodeChanged";
            const treeChanged = "treeChanged";
            throw new UsageError(`${action} is forbidden during a ${nodeChanged} or ${treeChanged} event`);
        }
    }
    /**
     * Allow changes to be made to {@link EditLock.editor} again.
     * @remarks May only be called when the lock is currently locked.
     */
    unlock() {
        assert(this.locked, 0xaa8 /* Checkout has not been locked */);
        this.locked = false;
    }
}
/**
 * Keeps track of all new forks created until the returned function is invoked, which will dispose all of those for.
 * The returned function may only be called once.
 *
 * @param checkout - The tree checkout for which you want to monitor forks for disposal.
 * @returns a function which can be called to dispose all of the tracked forks.
 */
function trackForksForDisposal(checkout) {
    const forks = new Set();
    const onDisposeUnSubscribes = [];
    const onForkUnSubscribe = onForkTransitive(checkout, (fork) => {
        forks.add(fork);
        onDisposeUnSubscribes.push(fork.events.on("dispose", () => forks.delete(fork)));
    });
    let disposed = false;
    return () => {
        assert(!disposed, 0xaa9 /* Forks may only be disposed once */);
        for (const fork of forks) {
            fork.dispose();
        }
        for (const unsubscribe of onDisposeUnSubscribes) {
            unsubscribe();
        }
        onForkUnSubscribe();
        disposed = 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 isSerializedChange(value) {
    if (typeof value !== "object" || value === null) {
        return false;
    }
    const change = value;
    return (change.version === 1 &&
        (change.revision === "root" || typeof change.revision === "number") &&
        typeof change.originatorId === "string" &&
        isStableId(change.originatorId) &&
        change.change !== undefined);
}
/**
 * Enumerates through a shared tree change, looking for schema change and field changes that result in visible changes to the tree (e.g. an insert, move, delete).
 * This function also considers changes to detached roots to be visible changes, but not renames of roots or builds of new roots.
 *
 * @param change - The change to analyze.
 * @returns True if the change contains any schema changes or any field changes that result in visible changes to the tree, false otherwise.
 */
function sharedTreeChangeHasVisibleChanges(change) {
    for (const inner of change.changes) {
        if (inner.type === "schema") {
            return true;
        }
        const delta = intoDelta(tagChange(inner.innerChange, undefined));
        if (deltaFieldMapHasVisibleChanges(delta.fields)) {
            return true;
        }
    }
    return false;
}
//# sourceMappingURL=treeCheckout.js.map