# Path: bartpy/mutation.py
# class TreeMutation(object):
#     """
#     An encapsulation of a change to be made to the tree.
#     Constructed of three components
#       - the node to be changed
#       - what it should be changed to
#       - a string name of the kind of change (normally grow or prune)
#     """
# 
#     def __init__(self, kind: str, existing_node: TreeNode, updated_node: TreeNode):
#         self.kind = kind
#         self.existing_node = existing_node
#         self.updated_node = updated_node
# 
#     def __str__(self):
#         return "{} - {} => {}".format(self.kind, self.existing_node, self.updated_node)
# 
# Path: bartpy/node.py
# class TreeNode(object):
#     """
#     A representation of a node in the Tree
#     Contains two main types of information:
#         - Data relevant for the node
#         - Links to children nodes
#     """
#     def __init__(self, split: Split, depth: int, left_child: 'TreeNode'=None, right_child: 'TreeNode'=None):
#         self.depth = depth
#         self._split = split
#         self._left_child = left_child
#         self._right_child = right_child
# 
#     @property
#     def data(self) -> Data:
#         return self._split.data
# 
#     @property
#     def left_child(self) -> 'TreeNode':
#         return self._left_child
# 
#     @property
#     def right_child(self) -> 'TreeNode':
#         return self._right_child
# 
#     @property
#     def split(self):
#         return self._split
# 
#     def update_y(self, y):
#         self.data.update_y(y)
#         if self.left_child is not None:
#             self.left_child.update_y(y)
#             self.right_child.update_y(y)
# 
# class LeafNode(TreeNode):
#     """
#     A representation of a leaf node in the tree
#     In addition to the normal work of a `Node`, a `LeafNode` is responsible for:
#         - Interacting with `Data`
#         - Making predictions
#     """
# 
#     def __init__(self, split: Split, depth=0, value=0.0):
#         self._value = value
#         super().__init__(split, depth, None, None)
# 
#     def set_value(self, value: float) -> None:
#         self._value = value
# 
#     @property
#     def current_value(self):
#         return self._value
# 
#     def predict(self) -> float:
#         return self.current_value
# 
#     def is_splittable(self) -> bool:
#         return self.data.X.is_at_least_one_splittable_variable()
# 
# class DecisionNode(TreeNode):
#     """
#     A `DecisionNode` encapsulates internal node in the tree
#     Unlike a `LeafNode`, it contains very little actual logic beyond tying the tree together
#     """
# 
#     def __init__(self, split: Split, left_child_node: TreeNode, right_child_node: TreeNode, depth=0):
#         super().__init__(split, depth, left_child_node, right_child_node)
# 
#     def is_prunable(self) -> bool:
#         return type(self.left_child) == LeafNode and type(self.right_child) == LeafNode
# 
#     def most_recent_split_condition(self) -> SplitCondition:
#         return self.left_child.split.most_recent_split_condition()
# 
# def deep_copy_node(node: TreeNode):
#     if type(node) == LeafNode:
#         node: LeafNode = node
#         return LeafNode(node.split.out_of_sample_conditioner(), value=node.current_value, depth=node.depth)
#     elif type(node) == DecisionNode:
#         node: DecisionNode = node
#         return DecisionNode(node.split.out_of_sample_conditioner(), node.left_child, node.right_child, depth=node.depth)
#     else:
#         raise TypeError("Unsupported node type")


from typing import List
from bartpy.mutation import TreeMutation
from bartpy.node import TreeNode, LeafNode, DecisionNode, deep_copy_node
import numpy as np
        tree.remove_node(mutation.existing_node.right_child)
        tree.add_node(mutation.updated_node)

    if mutation.kind == "grow":
        tree.remove_node(mutation.existing_node)
        tree.add_node(mutation.updated_node.left_child)
        tree.add_node(mutation.updated_node.right_child)
        tree.add_node(mutation.updated_node)

    for node in tree.nodes:
        if node.right_child == mutation.existing_node:
            node._right_child = mutation.updated_node
        if node.left_child == mutation.existing_node:
            node._left_child = mutation.updated_node


def deep_copy_tree(tree: Tree):
    """
    Efficiently create a copy of the tree for storage
    Creates a memory-light version of the tree with access to important information
    Parameters
    ----------
    tree: Tree
        Tree to copy

    Returns
    -------
    Tree
        Version of the tree optimized to be low memory
    """

# Path: bartpy/mutation.py
# class TreeMutation(object):
#     """
#     An encapsulation of a change to be made to the tree.
#     Constructed of three components
#       - the node to be changed
#       - what it should be changed to
#       - a string name of the kind of change (normally grow or prune)
#     """
# 
#     def __init__(self, kind: str, existing_node: TreeNode, updated_node: TreeNode):
#         self.kind = kind
#         self.existing_node = existing_node
#         self.updated_node = updated_node
# 
#     def __str__(self):
#         return "{} - {} => {}".format(self.kind, self.existing_node, self.updated_node)
# 
# Path: bartpy/node.py
# class TreeNode(object):
#     """
#     A representation of a node in the Tree
#     Contains two main types of information:
#         - Data relevant for the node
#         - Links to children nodes
#     """
#     def __init__(self, split: Split, depth: int, left_child: 'TreeNode'=None, right_child: 'TreeNode'=None):
#         self.depth = depth
#         self._split = split
#         self._left_child = left_child
#         self._right_child = right_child
# 
#     @property
#     def data(self) -> Data:
#         return self._split.data
# 
#     @property
#     def left_child(self) -> 'TreeNode':
#         return self._left_child
# 
#     @property
#     def right_child(self) -> 'TreeNode':
#         return self._right_child
# 
#     @property
#     def split(self):
#         return self._split
# 
#     def update_y(self, y):
#         self.data.update_y(y)
#         if self.left_child is not None:
#             self.left_child.update_y(y)
#             self.right_child.update_y(y)
# 
# class LeafNode(TreeNode):
#     """
#     A representation of a leaf node in the tree
#     In addition to the normal work of a `Node`, a `LeafNode` is responsible for:
#         - Interacting with `Data`
#         - Making predictions
#     """
# 
#     def __init__(self, split: Split, depth=0, value=0.0):
#         self._value = value
#         super().__init__(split, depth, None, None)
# 
#     def set_value(self, value: float) -> None:
#         self._value = value
# 
#     @property
#     def current_value(self):
#         return self._value
# 
#     def predict(self) -> float:
#         return self.current_value
# 
#     def is_splittable(self) -> bool:
#         return self.data.X.is_at_least_one_splittable_variable()
# 
# class DecisionNode(TreeNode):
#     """
#     A `DecisionNode` encapsulates internal node in the tree
#     Unlike a `LeafNode`, it contains very little actual logic beyond tying the tree together
#     """
# 
#     def __init__(self, split: Split, left_child_node: TreeNode, right_child_node: TreeNode, depth=0):
#         super().__init__(split, depth, left_child_node, right_child_node)
# 
#     def is_prunable(self) -> bool:
#         return type(self.left_child) == LeafNode and type(self.right_child) == LeafNode
# 
#     def most_recent_split_condition(self) -> SplitCondition:
#         return self.left_child.split.most_recent_split_condition()
# 
# def deep_copy_node(node: TreeNode):
#     if type(node) == LeafNode:
#         node: LeafNode = node
#         return LeafNode(node.split.out_of_sample_conditioner(), value=node.current_value, depth=node.depth)
#     elif type(node) == DecisionNode:
#         node: DecisionNode = node
#         return DecisionNode(node.split.out_of_sample_conditioner(), node.left_child, node.right_child, depth=node.depth)
#     else:
#         raise TypeError("Unsupported node type")


# Path: bartpy/tree.py
from typing import List
from bartpy.mutation import TreeMutation
from bartpy.node import TreeNode, LeafNode, DecisionNode, deep_copy_node
import numpy as np

        tree.remove_node(mutation.existing_node.right_child)
        tree.add_node(mutation.updated_node)

    if mutation.kind == "grow":
        tree.remove_node(mutation.existing_node)
        tree.add_node(mutation.updated_node.left_child)
        tree.add_node(mutation.updated_node.right_child)
        tree.add_node(mutation.updated_node)

    for node in tree.nodes:
        if node.right_child == mutation.existing_node:
            node._right_child = mutation.updated_node
        if node.left_child == mutation.existing_node:
            node._left_child = mutation.updated_node


def deep_copy_tree(tree: Tree):
    """
    Efficiently create a copy of the tree for storage
    Creates a memory-light version of the tree with access to important information
    Parameters
    ----------
    tree: Tree
        Tree to copy

    Returns
    -------
    Tree
        Version of the tree optimized to be low memory
    """

    return Tree([deep_copy_node(x) for x in tree.nodes])