///|
/// Node in the Rope tree structure
/// Can be either a leaf node (containing text) or internal node (containing children)
pub enum Node {
  /// Leaf node containing actual text
  Leaf(String)
  /// Internal node containing child nodes
  Internal(NodeChildren)
}

///|
/// Create a new empty leaf node
pub fn Node::new() -> Node {
  Leaf("")
}

///|
/// Create a new empty node (for placeholder purposes)
pub fn Node::new_empty() -> Node {
  Leaf("")
}

///|
/// Create a new leaf node with text
pub fn Node::new_leaf(text : String) -> Node {
  Leaf(text)
}

///|
/// Create a new internal node with children
pub fn Node::new_internal(children : NodeChildren) -> Node {
  Internal(children)
}

///|
/// Check if this is a leaf node
pub fn Node::is_leaf(self : Node) -> Bool {
  match self {
    Leaf(_) => true
    Internal(_) => false
  }
}

///|
/// Check if this is an internal node
pub fn Node::is_internal(self : Node) -> Bool {
  match self {
    Leaf(_) => false
    Internal(_) => true
  }
}

///|
/// Get the text info for this node
pub fn Node::text_info(self : Node) -> TextInfo {
  match self {
    Leaf(text) => TextInfo::from_string(text)
    Internal(children) => children.total_info()
  }
}

///|
/// Get the number of UTF-16 code units in this node
pub fn Node::utf16_cu_count(self : Node) -> UInt64 {
  self.text_info().utf16_cu
}

///|
/// Get the number of characters in this node
pub fn Node::char_count(self : Node) -> UInt64 {
  self.text_info().chars
}

///|
/// Get the number of line breaks in this node
pub fn Node::line_break_count(self : Node) -> UInt64 {
  self.text_info().line_breaks
}

///|
/// Get the depth of this node in the tree
pub fn Node::depth(self : Node) -> Int {
  match self {
    Leaf(_) => 0
    Internal(children) => {
      let mut max_depth = 0
      for i = 0; i < children.length(); i = i + 1 {
        let child_depth = children.get_node(i).depth()
        if child_depth > max_depth {
          max_depth = child_depth
        }
      }
      max_depth + 1
    }
  }
}

///|
/// Get the number of children (0 for leaf nodes)
pub fn Node::child_count(self : Node) -> Int {
  match self {
    Leaf(_) => 0
    Internal(children) => children.length()
  }
}

///|
/// Get chunk at character index
/// Returns (text_chunk, accumulated_info_before_chunk)
pub fn Node::get_chunk_at_char(
  self : Node,
  char_idx : UInt64,
) -> (String, TextInfo) {
  match self {
    Leaf(text) => (text, TextInfo::new())
    Internal(children) => {
      let (child_idx, acc_info) = children.find_child_at_char(char_idx)
      if child_idx < children.length() {
        let child = children.get_node(child_idx)
        let (chunk, child_acc_info) = child.get_chunk_at_char(
          char_idx - acc_info.chars,
        )
        (chunk, acc_info + child_acc_info)
        // Return last chunk if at end
      } else if children.length() > 0 {
        let last_child = children.get_node(children.length() - 1)
        last_child.get_chunk_at_char(0UL)
      } else {
        ("", TextInfo::new())
      }
    }
  }
}

///|
/// Get chunk at UTF-16 code unit index
pub fn Node::get_chunk_at_utf16_cu(
  self : Node,
  utf16_idx : UInt64,
) -> (String, TextInfo) {
  match self {
    Leaf(text) => (text, TextInfo::new())
    Internal(children) => {
      let (child_idx, acc_info) = children.find_child_at_utf16_cu(utf16_idx)
      if child_idx < children.length() {
        let child = children.get_node(child_idx)
        let (chunk, child_acc_info) = child.get_chunk_at_utf16_cu(
          utf16_idx - acc_info.utf16_cu,
        )
        (chunk, acc_info + child_acc_info)
        // Return last chunk if at end
      } else if children.length() > 0 {
        let last_child = children.get_node(children.length() - 1)
        last_child.get_chunk_at_utf16_cu(0UL)
      } else {
        ("", TextInfo::new())
      }
    }
  }
}

///|
/// Get chunk at line break index
pub fn Node::get_chunk_at_line_break(
  self : Node,
  line_idx : UInt64,
) -> (String, TextInfo) {
  match self {
    Leaf(text) => (text, TextInfo::new())
    Internal(children) => {
      let (child_idx, acc_info) = children.find_child_at_line_break(line_idx)
      if child_idx < children.length() {
        let child = children.get_node(child_idx)
        let (chunk, child_acc_info) = child.get_chunk_at_line_break(
          line_idx - acc_info.line_breaks,
        )
        (chunk, acc_info + child_acc_info)
        // Return last chunk if at end
      } else if children.length() > 0 {
        let last_child = children.get_node(children.length() - 1)
        last_child.get_chunk_at_line_break(0UL)
      } else {
        ("", TextInfo::new())
      }
    }
  }
}

///|
/// Insert text at the given character index
pub fn Node::insert_text(self : Node, char_idx : UInt64, text : String) -> Node {
  match self {
    Leaf(existing_text) =>
      if existing_text.length() == 0 {
        // Empty leaf, just replace with new text
        Leaf(text)
      } else {
        // Convert char index to UTF-16 index for insertion
        let utf16_idx = char_to_utf16_cu_idx(existing_text, char_idx.to_int())
        let before = existing_text.substring(start=0, end=utf16_idx)
        let after = existing_text.substring(
          start=utf16_idx,
          end=existing_text.length(),
        )
        let new_text = before + text + after

        // Check if we need to split this leaf
        if new_text.length() > MAX_BYTES {
          // Split into multiple leaves
          let mid = new_text.length() / 2
          let left_text = new_text.substring(start=0, end=mid)
          let right_text = new_text.substring(start=mid, end=new_text.length())
          let children = NodeChildren::new()
          children.push(TextInfo::from_string(left_text), Leaf(left_text))
          children.push(TextInfo::from_string(right_text), Leaf(right_text))
          Internal(children)
        } else {
          Leaf(new_text)
        }
      }
    Internal(children) => {
      let (child_idx, acc_info) = children.find_child_at_char(char_idx)
      if child_idx < children.length() {
        let child = children.get_node(child_idx)
        let new_child = child.insert_text(char_idx - acc_info.chars, text)

        // Update the child in place
        children.set(child_idx, new_child.text_info(), new_child)
        Internal(children)
        // Insert at the end
      } else if children.length() > 0 {
        let last_idx = children.length() - 1
        let last_child = children.get_node(last_idx)
        let new_last_child = last_child.insert_text(
          last_child.char_count(),
          text,
        )
        children.set(last_idx, new_last_child.text_info(), new_last_child)
        Internal(children)
      } else {
        // Empty internal node, add new leaf
        children.push(TextInfo::from_string(text), Leaf(text))
        Internal(children)
      }
    }
  }
}

///|
/// Split this node at the given character index
/// Returns the right part of the split
pub fn Node::split(self : Node, char_idx : UInt64) -> Node {
  match self {
    Leaf(text) => {
      let utf16_idx = char_to_utf16_cu_idx(text, char_idx.to_int())
      let right_text = text.substring(start=utf16_idx, end=text.length())
      Leaf(right_text)
    }
    Internal(children) => {
      let (child_idx, acc_info) = children.find_child_at_char(char_idx)
      if child_idx < children.length() {
        let child = children.get_node(child_idx)
        let right_child = child.split(char_idx - acc_info.chars)

        // Create new internal node with right part
        let right_children = NodeChildren::new()

        // Add the split right child if it's not empty
        if right_child.char_count() > 0UL {
          right_children.push(right_child.text_info(), right_child)
        }

        // Add all children to the right of the split point
        for i = child_idx + 1; i < children.length(); i = i + 1 {
          let (info, node) = children.get(i)
          right_children.push(info, node)
        }
        Internal(right_children)
      } else {
        // Split point is beyond all children, return empty node
        Internal(NodeChildren::new())
      }
    }
  }
}

///|
/// Append another node to this one
pub fn Node::node_append(self : Node, other : Node) -> Node {
  match (self, other) {
    (Leaf(text1), Leaf(text2)) => {
      let combined = text1 + text2
      if combined.length() <= MAX_BYTES {
        Leaf(combined)
      } else {
        // Need to split into internal node
        let children = NodeChildren::new()
        children.push(TextInfo::from_string(text1), Leaf(text1))
        children.push(TextInfo::from_string(text2), Leaf(text2))
        Internal(children)
      }
    }
    (Leaf(text), Internal(other_children)) => {
      let children = NodeChildren::new()
      children.push(TextInfo::from_string(text), Leaf(text))

      // Add all children from other
      for i = 0; i < other_children.length(); i = i + 1 {
        let (info, node) = other_children.get(i)
        children.push(info, node)
      }
      Internal(children)
    }
    (Internal(self_children), Leaf(text)) => {
      let children = NodeChildren::new()

      // Add all children from self
      for i = 0; i < self_children.length(); i = i + 1 {
        let (info, node) = self_children.get(i)
        children.push(info, node)
      }
      children.push(TextInfo::from_string(text), Leaf(text))
      Internal(children)
    }
    (Internal(self_children), Internal(other_children)) => {
      let children = NodeChildren::new()

      // Add all children from self
      for i = 0; i < self_children.length(); i = i + 1 {
        let (info, node) = self_children.get(i)
        children.push(info, node)
      }

      // Add all children from other
      for i = 0; i < other_children.length(); i = i + 1 {
        let (info, node) = other_children.get(i)
        children.push(info, node)
      }
      Internal(children)
    }
  }
}

///|
/// Convert node to string representation
pub fn Node::node_to_string(self : Node) -> String {
  match self {
    Leaf(text) => text
    Internal(children) => {
      let mut result = ""
      for i = 0; i < children.length(); i = i + 1 {
        result = result + children.get_node(i).node_to_string()
      }
      result
    }
  }
}