/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { unreachableCase } from "@fluidframework/core-utils/internal";
import { UsageError } from "@fluidframework/telemetry-utils/internal";
import { TreeStatus } from "../feature-libraries/index.js";
import { getOrCreateInnerNode, treeNodeApi, } from "../simple-tree/index.js";
import { SchematizingSimpleTreeView } from "./schematizingTreeView.js";
import { getCheckoutFlexTreeView } from "./checkoutFlexTreeView.js";
import { rollback } from "./transactionTypes.js";
/**
 * The `Tree` object holds various functions for interacting with {@link TreeNode}s.
 * @public
 */
export const treeApi = {
    ...treeNodeApi,
    runTransaction: createRunTransaction(),
    contains(parent, child) {
        let toCheck = child;
        while (toCheck !== undefined) {
            if (toCheck === parent) {
                return true;
            }
            toCheck = treeApi.parent(toCheck);
        }
        return false;
    },
};
// TODO: Add more constraint types here
/** Creates a copy of `runTransaction` with the `rollback` property added so as to satisfy the `RunTransaction` interface. */
function createRunTransaction() {
    /** A type-safe helper to add a "rollback" property (as required by the `RunTransaction` interface) to a given object */
    function defineRollbackProperty(target) {
        Reflect.defineProperty(target, "rollback", { value: rollback });
        return target;
    }
    return defineRollbackProperty(runTransaction.bind({}));
}
/**
 * Run the given transaction.
 * @remarks
 * This API is not publicly exported but is exported outside of this module so that test code may unit test the `Tree.runTransaction` function directly without being restricted to its public API overloads.
 */
export function runTransaction(treeOrNode, transaction, preconditions = []) {
    if (treeOrNode instanceof SchematizingSimpleTreeView) {
        const t = transaction;
        return runTransactionInCheckout(treeOrNode.checkout, () => t(treeOrNode.root), preconditions);
    }
    else {
        const node = treeOrNode;
        const t = transaction;
        const context = getOrCreateInnerNode(node).context;
        if (context.isHydrated() === false) {
            throw new UsageError("Transactions cannot be run on Unhydrated nodes. Transactions apply to a TreeView and Unhydrated nodes are not part of a TreeView.");
        }
        const treeView = getCheckoutFlexTreeView(context);
        return runTransactionInCheckout(treeView.checkout, () => t(node), preconditions);
    }
}
function runTransactionInCheckout(checkout, transaction, preconditions) {
    checkout.transaction.start();
    for (const constraint of preconditions) {
        switch (constraint.type) {
            case "nodeInDocument": {
                const node = getOrCreateInnerNode(constraint.node);
                const nodeStatus = treeApi.status(constraint.node);
                if (nodeStatus !== TreeStatus.InDocument) {
                    throw new UsageError(`Attempted to add a "nodeInDocument" constraint, but the node is not currently in the document. Node status: ${nodeStatus}`);
                }
                checkout.editor.addNodeExistsConstraint(node.anchorNode);
                break;
            }
            default:
                unreachableCase(constraint.type);
        }
    }
    let result;
    try {
        result = transaction();
    }
    catch (error) {
        // If the transaction has an unhandled error, abort and rollback the transaction but continue to propagate the error.
        checkout.transaction.abort();
        throw error;
    }
    if (result === rollback) {
        checkout.transaction.abort();
    }
    else {
        checkout.transaction.commit();
    }
    return result;
}
//# sourceMappingURL=treeApi.js.map