export class DecisionTree {
    root;
    createDecision(name, condition) {
        assertName(name);
        return { type: "decision", name, condition };
    }
    createAction(name, action = () => undefined) {
        assertName(name);
        return { type: "action", name, action };
    }
    setBranches(node, trueBranch, falseBranch) {
        if (node.type !== "decision")
            throw new Error(`Cannot set branches on action node: ${node.name}`);
        node.trueBranch = trueBranch;
        node.falseBranch = falseBranch;
        trueBranch.parent = node;
        falseBranch.parent = node;
    }
    setRoot(node) {
        this.root = node;
    }
    getRoot() {
        return this.root;
    }
    decide(context, maxDepth = 32) {
        const action = this.traverse(context, maxDepth);
        if (!action?.action)
            return { action: "none", path: this.getTraversalPath(context, maxDepth), executed: false };
        action.action(context);
        return { action: action.name, path: this.getTraversalPath(context, maxDepth), executed: true };
    }
    traverse(context, maxDepth = 32) {
        if (!this.root || maxDepth < 1)
            return undefined;
        let current = this.root;
        let depth = 0;
        while (current && depth < maxDepth) {
            depth += 1;
            if (current.type === "action")
                return current;
            if (!current.condition)
                return undefined;
            current = current.condition(context) ? current.trueBranch : current.falseBranch;
        }
        return undefined;
    }
    getTraversalPath(context, maxDepth = 32) {
        if (!this.root || maxDepth < 1)
            return [];
        const path = [];
        let current = this.root;
        let depth = 0;
        while (current && depth < maxDepth) {
            depth += 1;
            path.push(current.name);
            if (current.type === "action")
                break;
            if (!current.condition)
                break;
            current = current.condition(context) ? current.trueBranch : current.falseBranch;
        }
        return path;
    }
    validate() {
        return this.root ? validateNode(this.root) : false;
    }
    getStats() {
        if (!this.root)
            return { totalNodes: 0, decisionNodes: 0, actionNodes: 0, maxDepth: 0, averageActionDepth: 0 };
        const stats = { totalNodes: 0, decisionNodes: 0, actionNodes: 0, actionDepths: [] };
        collectStats(this.root, 0, stats);
        const maxDepth = stats.actionDepths.length > 0 ? Math.max(...stats.actionDepths) : 0;
        const averageActionDepth = stats.actionDepths.length > 0
            ? stats.actionDepths.reduce((sum, depth) => sum + depth, 0) / stats.actionDepths.length
            : 0;
        return {
            totalNodes: stats.totalNodes,
            decisionNodes: stats.decisionNodes,
            actionNodes: stats.actionNodes,
            maxDepth,
            averageActionDepth: Number(averageActionDepth.toFixed(3))
        };
    }
    clone() {
        const tree = new DecisionTree();
        if (this.root)
            tree.setRoot(cloneNode(this.root));
        return tree;
    }
}
function validateNode(node) {
    if (node.type === "action")
        return typeof node.action === "function";
    return typeof node.condition === "function" && Boolean(node.trueBranch) && Boolean(node.falseBranch) && validateNode(node.trueBranch) && validateNode(node.falseBranch);
}
function collectStats(node, depth, stats) {
    stats.totalNodes += 1;
    if (node.type === "action") {
        stats.actionNodes += 1;
        stats.actionDepths.push(depth);
        return;
    }
    stats.decisionNodes += 1;
    if (node.trueBranch)
        collectStats(node.trueBranch, depth + 1, stats);
    if (node.falseBranch)
        collectStats(node.falseBranch, depth + 1, stats);
}
function cloneNode(node) {
    const cloned = { type: node.type, name: node.name, condition: node.condition, action: node.action };
    if (node.trueBranch) {
        cloned.trueBranch = cloneNode(node.trueBranch);
        cloned.trueBranch.parent = cloned;
    }
    if (node.falseBranch) {
        cloned.falseBranch = cloneNode(node.falseBranch);
        cloned.falseBranch.parent = cloned;
    }
    return cloned;
}
function assertName(name) {
    if (name.trim().length === 0)
        throw new Error("Decision tree node name cannot be empty.");
}
//# sourceMappingURL=DecisionTree.js.map