# Problem Description
This is a binary tree transformation problem where we need to modify certain node values in the tree according to specific rules. The function takes a binary tree's root node as input and should return the transformed tree's root node. The transformation needs to be done in-place by manipulating the existing nodes rather than creating a new tree.

# Visual Facts
1. The image shows three different examples of binary tree transformations (before and after).
2. The nodes that change are highlighted in orange in the result trees.
3. Values in the nodes can be:
   - Positive integers (e.g., 3, 4)
   - Negative integers (e.g., -2, -4)
   - Special value "null"
4. The tree structure (topology) remains unchanged after transformation.
5. The root node value remains unchanged in all examples (3).

# Visual Patterns
1. Value Transformation Pattern:
   - change the value of all left child nodes to the negative of their current value.
   - keep all the right child nodes' values unchanged.
   - no other changes are made to the tree.

2. Special Case Handling:
   - "null" values remain "null" even after transformation
   - The root node is never transformed

# Solution
class Node:
    def __init__(self, val = 0, left = None, right = None):
        self.left = left
        self.right = right
        self.val = val

def solution(root: Node) -> Node:
    """
    Transforms a binary tree according to the illustrated rule by manipulating its nodes.

    Parameters:
        root (Node): The root node of the input binary tree.

    Returns:
        Node: The root node of the transformed binary tree.
    """
    if root is None:
        return None
    if root.left:
        root.left.val = -root.left.val
        solution(root.left) 
    if root.right:
        solution(root.right) 
        return root

    return root 


# Test Script
class Node:
    def __init__(self, val = 0, left = None, right = None):
        self.left = left
        self.right = right
        self.val = val

def tree_to_list(root):
    def maxDepth(root):
        if not root:
            return 0
        return 1 + max(maxDepth(root.left), maxDepth(root.right))
    li_res = [None for _ in range( 2 ** maxDepth(root) - 1)]
    if not root:
        return []
    def dfs(node, idx):
        if not node:
            return
        li_res[idx] = node.val
        dfs(node.left, 2*idx+1)
        dfs(node.right, 2*idx+2)
    dfs(root, 0)
    return li_res
    

def list_to_tree(lst):
    """
    Converts a list into a binary tree structure and returns the root node.
    Args:
        lst (list): List of elements to form the binary tree.
                   Use `None` for empty nodes.
    Returns:
        Node: The root of the binary tree.
    """
    if not lst:  # If the list is empty, return None
        return None

    # Create a list of nodes, where None in the list corresponds to None in the tree
    nodes = [Node(val = value) if value is not None else None for value in lst]
    
    # Link the nodes to form the tree
    for i in range(len(lst)):
        if nodes[i] is not None:  # Skip None nodes
            left_index = 2 * i + 1  # Left child index
            right_index = 2 * i + 2  # Right child index
            if left_index < len(lst):  # Check if left child exists
                nodes[i].left = nodes[left_index]
            if right_index < len(lst):  # Check if right child exists
                nodes[i].right = nodes[right_index]

    # Return the root node
    return nodes[0]

def is_same_tree(tree1, tree2):
    """
    Checks if two binary trees are identical in structure and values.
    
    Args:
        tree1 (Node): Root of the first binary tree.
        tree2 (Node): Root of the second binary tree.
    
    Returns:
        bool: True if the trees are identical, False otherwise.
    """
    # Both trees are empty
    if not tree1 and not tree2:
        return True
    
    # One of the trees is empty, the other is not
    if not tree1 or not tree2:
        return False
    
    # Compare current node values and recursively check left and right subtrees
    return (tree1.val == tree2.val and
            is_same_tree(tree1.left, tree2.left) and
            is_same_tree(tree1.right, tree2.right))

def test():
    test_cases = [
        ([3,4,6,1,3,5,6,2,5], [3,-4,6,-1,3,-5,6,-2,5]),
        ([1, 2, 3, None, 5, None, 7],  [1, -2, 3, None, 5, None, 7]),
        ([1, 2, None, 3, None, None, None],  [1, -2, None, -3]),
        ([1, None, 2, None, None, 4, 5], [1, None, 2, None, None, -4, 5]),
        ([1, 2, 3, None, None, 4, None, None, None, None, None, 5],[1, -2, 3, None, None, -4, None, None, None, None, None, -5]),
        ([1, 2, 5, None, None, 3, 4, None, None, None, None, None, 5],[1, -2, 5, None, None, -3, 4, None, None, None, None, None, 5]),
    ]

    for input_list, expected_list in test_cases:
        try:
            root = list_to_tree(input_list)
            expected = list_to_tree(expected_list)
            output = solution(root)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`root`: Binary Tree {input_list}\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

        if not is_same_tree(output, expected):
            error_message = (
                f"The following Test Case failed:\n"
                f"`root`: Binary Tree {input_list}\n\n"
                f"Actual Output Tree: {tree_to_list(output)}\n"
                f"Expected Output Tree: {tree_to_list(expected)}"
            )
            assert False, error_message

test()