/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
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);
};
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 _TransactionStack_stack, _TransactionStack_onPush, _TransactionStack_events, _TransactionStack_disposed, _SquashingTransactionStack_transactionBranch, _SquashingTransactionStack_activeBranchEvents;
import { assert, unreachableCase } from "@fluidframework/core-utils/internal";
import { createEmitter } from "@fluid-internal/client-utils";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { findAncestor, } from "../core/index.js";
import { getOrCreate } from "../util/index.js";
/**
 * Describes the result of a transaction.
 * Transactions may either succeed and commit, or fail and abort.
 */
export var TransactionResult;
(function (TransactionResult) {
    /**
     * Indicates the transaction failed.
     */
    TransactionResult[TransactionResult["Abort"] = 0] = "Abort";
    /**
     * Indicates the transaction succeeded.
     */
    TransactionResult[TransactionResult["Commit"] = 1] = "Commit";
})(TransactionResult || (TransactionResult = {}));
/**
 * An implementation of {@link Transactor} that uses a stack to manage transactions.
 * @remarks Using a stack allows transactions to nest - i.e. an inner transaction may be started while an outer transaction is already in progress.
 */
export class TransactionStack {
    get events() {
        return __classPrivateFieldGet(this, _TransactionStack_events, "f");
    }
    get disposed() {
        return __classPrivateFieldGet(this, _TransactionStack_disposed, "f");
    }
    /**
     * Construct a new {@link TransactionStack}.
     * @param onPush - A {@link OnPush | function} that will be called when a transaction begins.
     */
    constructor(onPush) {
        _TransactionStack_stack.set(this, []);
        _TransactionStack_onPush.set(this, void 0);
        _TransactionStack_events.set(this, createEmitter());
        _TransactionStack_disposed.set(this, false);
        __classPrivateFieldSet(this, _TransactionStack_onPush, onPush, "f");
    }
    isInProgress() {
        this.ensureNotDisposed();
        return __classPrivateFieldGet(this, _TransactionStack_stack, "f").length > 0;
    }
    start() {
        this.ensureNotDisposed();
        __classPrivateFieldGet(this, _TransactionStack_stack, "f").push(__classPrivateFieldGet(this, _TransactionStack_onPush, "f")?.call(this));
        __classPrivateFieldGet(this, _TransactionStack_events, "f").emit("started");
    }
    commit() {
        this.ensureNotDisposed();
        if (!this.isInProgress()) {
            throw new UsageError("No transaction to commit");
        }
        __classPrivateFieldGet(this, _TransactionStack_events, "f").emit("committing");
        __classPrivateFieldGet(this, _TransactionStack_stack, "f").pop()?.(TransactionResult.Commit);
    }
    abort() {
        this.ensureNotDisposed();
        if (!this.isInProgress()) {
            throw new UsageError("No transaction to abort");
        }
        __classPrivateFieldGet(this, _TransactionStack_events, "f").emit("aborting");
        __classPrivateFieldGet(this, _TransactionStack_stack, "f").pop()?.(TransactionResult.Abort);
    }
    dispose() {
        this.ensureNotDisposed();
        while (this.isInProgress()) {
            this.abort();
        }
        __classPrivateFieldSet(this, _TransactionStack_disposed, true, "f");
    }
    ensureNotDisposed() {
        if (this.disposed) {
            throw new UsageError("Transactor is disposed");
        }
    }
}
_TransactionStack_stack = new WeakMap(), _TransactionStack_onPush = new WeakMap(), _TransactionStack_events = new WeakMap(), _TransactionStack_disposed = new WeakMap();
/**
 * An implementation of {@link Transactor} that {@link TransactionStack | uses a stack} and a {@link SharedTreeBranch | branch} to manage transactions.
 * @remarks Given a branch, this class will fork the branch when a transaction begins and squash the forked branch back into the original branch when the transaction ends.
 * This class provides conveniences for interacting with the {@link SquashingTransactionStack.activeBranch | active branch} in a way that is stable across transaction boundaries.
 * For example, see {@link SquashingTransactionStack.activeBranchEditor | activeBranchEditor} and {@link SquashingTransactionStack.activeBranchEvents | activeBranchEvents}.
 */
export class SquashingTransactionStack extends TransactionStack {
    /**
     * Get the "active branch" for this transactor - either the transaction branch if a transaction is in progress, or the original branch otherwise.
     */
    get activeBranch() {
        return __classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f") ?? this.branch;
    }
    /**
     * Provides events for changes in the {@link SquashingTransactionStack.activeBranch | active branch}.
     * @remarks When the active branch changes, the listeners for these events will automatically be transferred to the new active branch.
     * In contrast, binding an event to the {@link SquashingTransactionStack.activeBranch | active branch} directly will not automatically transfer the listener when the active branch changes.
     */
    get activeBranchEvents() {
        const off = (eventName, listener) => {
            this.activeBranch.events.off(eventName, listener);
            const listeners = __classPrivateFieldGet(this, _SquashingTransactionStack_activeBranchEvents, "f").get(eventName);
            if (listeners?.delete(listener) === true && listeners.size === 0) {
                __classPrivateFieldGet(this, _SquashingTransactionStack_activeBranchEvents, "f").delete(eventName);
            }
        };
        return {
            on: (eventName, listener) => {
                const listeners = getOrCreate(__classPrivateFieldGet(this, _SquashingTransactionStack_activeBranchEvents, "f"), eventName, () => new Set());
                listeners.add(listener);
                this.activeBranch.events.on(eventName, listener);
                return () => off(eventName, listener);
            },
            off,
        };
    }
    /**
     * Construct a new {@link SquashingTransactionStack}.
     * @param branch - The {@link SquashingTransactionStack.branch | branch} that will be forked off of when a transaction begins.
     * @param squash - Called once when the outer-most transaction is committed to produce a single squashed change from the transaction's commits.
     * The change will be applied to the original {@link SquashingTransactionStack.branch | branch}.
     * @param onPush - {@link OnPush | A function} that will be called when a transaction is pushed to the {@link TransactionStack | stack}.
     */
    constructor(branch, squash, onPush) {
        super(() => {
            // Keep track of the commit that each transaction was on when it started
            // TODO:#8603: This may need to be computed differently if we allow rebasing during a transaction.
            const startHead = this.activeBranch.getHead();
            const onPop = onPush?.();
            const transactionBranch = __classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f") ?? this.branch.fork();
            this.setTransactionBranch(transactionBranch);
            transactionBranch.editor.enterTransaction();
            return (result) => {
                assert(__classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f") !== undefined, 0xa98 /* Expected transaction branch */);
                __classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f").editor.exitTransaction();
                switch (result) {
                    case TransactionResult.Abort:
                        // When a transaction is aborted, roll back all the transaction's changes on the current branch
                        __classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f").removeAfter(startHead);
                        break;
                    case TransactionResult.Commit:
                        // If this was the outermost transaction closing...
                        if (!this.isInProgress()) {
                            if (__classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f").getHead() !== startHead) {
                                // ...squash all the new commits on the transaction branch into a new commit on the original branch
                                const removedCommits = [];
                                findAncestor([__classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f").getHead(), removedCommits], (c) => c === startHead);
                                branch.apply(squash(removedCommits));
                            }
                        }
                        break;
                    default:
                        unreachableCase(result);
                }
                if (!this.isInProgress()) {
                    __classPrivateFieldGet(this, _SquashingTransactionStack_transactionBranch, "f").dispose();
                    this.setTransactionBranch(undefined);
                }
                onPop?.(result);
            };
        });
        _SquashingTransactionStack_transactionBranch.set(this, void 0);
        /**
         * An editor for whichever branch is currently the {@link SquashingTransactionStack.activeBranch | active branch}.
         * @remarks This editor can safely be held on to across transaction boundaries, as it will properly delegate to the correct branch.
         * In contrast, it is not safe to hold onto e.g. `activeBranch.editor` across transaction boundaries, since the active branch may change.
         */
        this.activeBranchEditor = new Proxy({}, {
            get: (_, p, receiver) => {
                return Reflect.get(this.activeBranch.editor, p, receiver);
            },
        });
        _SquashingTransactionStack_activeBranchEvents.set(this, new Map());
        this.branch = branch;
    }
    /** Updates the transaction branch (and therefore the active branch) and rebinds the branch events. */
    setTransactionBranch(transactionBranch) {
        const oldActiveBranch = this.activeBranch;
        __classPrivateFieldSet(this, _SquashingTransactionStack_transactionBranch, transactionBranch, "f");
        for (const [eventName, listeners] of __classPrivateFieldGet(this, _SquashingTransactionStack_activeBranchEvents, "f")) {
            for (const listener of listeners) {
                oldActiveBranch.events.off(eventName, listener);
                this.activeBranch.events.on(eventName, listener);
            }
        }
    }
}
_SquashingTransactionStack_transactionBranch = new WeakMap(), _SquashingTransactionStack_activeBranchEvents = new WeakMap();
//# sourceMappingURL=transaction.js.map