# Problem Description
This is a binary tree classification problem where we need to determine if a given binary tree belongs to Type 1 or Type 2. The main task is to determine if a binary tree has mirror symmetry - where the left subtree is a mirror reflection of the right subtree. Type 1 represents symmetrical trees, while Type 2 represents non-symmetrical trees.

# Visual Facts
1. Both examples show multiple binary trees for each type
2. Type 1 trees:
   - Shows 4 example trees
   - The left and right subtrees of the root node are perfectly symmetrical along a vertical axis through the root node (shown with green dashed line)
   
3. Type 2 trees:
   - Shows 4 example trees
   - Each tree has a red dashed line with an X mark
   - Trees lack mirror symmetry in either structure or values

# Visual Patterns
1. Symmetry Rules for Type 1:
   - The root value can be any number
   - Left and right subtrees of the root node must be mirror images of each other
   - null nodes are also considered as regular nodes for symmetry comparison
   
2. Asymmetry Patterns for Type 2:
   - Trees become Type 2 if either:
     * The structure is not symmetrical (different number of nodes on left vs right)
     * Any single violation of symmetry makes it Type 2


# Solution
from typing import Optional

class TreeNode:
    """
    This class represents a node in a tree. Each node contains a value and a list of children.
    """

    def __init__(self, value: int, children=[None, None]):
        self.value = value
        self.children = children

def is_mirror(left: Optional[TreeNode], right: Optional[TreeNode]) -> bool:
    """
    Helper function to determine if two trees are mirror images of each other.

    Parameters:
        left (TreeNode): The root node of the left subtree.
        right (TreeNode): The root node of the right subtree.

    Returns:
        bool: True if the trees are mirror images, False otherwise.   
    """
    if left is None and right is None:
        return True
    if left is None or right is None:
        return False
    if left.value != right.value:
        return False
    return is_mirror(left.children[0], right.children[1]) and is_mirror(left.children[1], right.children[0])

def solution(root: Optional[TreeNode]) -> int:
    """
    Determine which type the binary tree belongs to.

    Parameters:
        root (TreeNode): The root node of a binary tree.

    Returns:
        int: Type 1 if the tree is symmetrical, Type 2 otherwise.     
    """
    if root is None:
        return 1
    return 1 if is_mirror(root.children[0], root.children[1]) else 2


# Test Script
def tree_to_string(node: TreeNode, level: int = 0) -> str:
    """Helper function to convert a tree to a readable string format"""
    if node is None:
        return "None"
    
    indent = "  " * level
    result = f"TreeNode({node.value}"
    
    if node.children:
        result += ", [\n"
        child_strings = []
        for child in node.children:
            child_str = tree_to_string(child, level + 1)
            child_strings.append(indent + "  " + child_str)
        result += ",\n".join(child_strings)
        result += f"\n{indent}])"
    else:
        result += ")"
    
    return result

def test():
    Tree1 = TreeNode(1, [
        TreeNode(1),
        TreeNode(2)
    ]) # 2
    Tree2 = TreeNode(1, [
        TreeNode(2),
        TreeNode(2)
    ]) # 1
    Tree3 = TreeNode(3, [
        TreeNode(3), 
        None
    ]) # 2
    Tree4 = TreeNode(2, [
        TreeNode(2), 
        TreeNode(2, [
            TreeNode(2),
            None
        ])
    ]) # 2
    Tree5 = TreeNode(2, [
        TreeNode(2, [
            TreeNode(2),
            None
        ]), 
        TreeNode(2, [
            None,
            TreeNode(2)
        ])
    ]) # 1
    Tree6 = TreeNode(2, [
        TreeNode(2, [
            None,
            TreeNode(2)
        ]), 
        TreeNode(2, [
            None,
            TreeNode(2)
        ])
    ]) # 2
    Tree7 = TreeNode(2, [
        TreeNode(1, [TreeNode(2), None]), 
        TreeNode(2, [TreeNode(3), None])
    ]) # 2
    Tree8 = TreeNode(2, [
        TreeNode(1, [
            TreeNode(2, [TreeNode(2), TreeNode(3)]),
            TreeNode(3, [TreeNode(3), TreeNode(2)])
        ]), 
        TreeNode(1, [
            TreeNode(3, [TreeNode(2), TreeNode(3)]),
            TreeNode(2, [TreeNode(3), TreeNode(2)])
        ])
    ]) # 1

    test_cases = [
        (Tree1, 2),
        (Tree2, 1),
        (Tree3, 2),
        (Tree4, 2),
        (Tree5, 1),
        (Tree6, 2),
        (Tree7, 2),
        (Tree8, 1)
    ]

    for i, (root, expected_output) in enumerate(test_cases):
        try:
            output = solution(root)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`root`: Tree{i+1}\n"
                f"Tree structure:\n{tree_to_string(root)}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

        if output != expected_output:
            error_message = (
                f"The following Test Case failed:\n"
                f"`root`: Tree{i+1}\n"
                f"Tree structure:\n{tree_to_string(root)}\n\n"
                f"Actual Output: {output}\n"
                f"Expected Output: {expected_output}"
            )
            assert False, error_message

test()