///|
/// Core DOM Primitives
///
/// These are the fundamental DOM operations that all higher layers use.
/// No serialization, direct function calls only.
///
/// Design principles:
/// 1. Each operation is atomic and stateless
/// 2. No implicit side effects
/// 3. Suitable for WASM export
/// 4. Can be batched by higher layers

///|
/// DOM Tree - the core document structure
/// This is the single source of truth for all DOM operations
pub struct DomTree {
  /// Next available node ID
  mut next_id : Int
  /// All nodes indexed by ID
  nodes : Map[Int, DomNode]
  /// Root document node ID
  mut root_id : Int
  /// Currently focused node (for input)
  mut focused_node : Int?
  /// Mutation queue for batching changes
  mutations : MutationQueue
}

///|
/// Internal DOM node representation
struct DomNode {
  node_type : NodeType
  tag_name : String
  mut text_content : String
  attributes : Map[String, String]
  mut parent_id : Int?
  children : Array[Int]
  /// For elements: computed style cache
  mut cached_rect : Rect?
}

///|
fn DomNode::new(
  node_type : NodeType,
  tag_name : String,
  text_content? : String = "",
) -> DomNode {
  {
    node_type,
    tag_name,
    text_content,
    attributes: {},
    parent_id: None,
    children: [],
    cached_rect: None,
  }
}

///|
/// Create a new empty DOM tree
pub fn DomTree::new() -> DomTree {
  let tree : DomTree = {
    next_id: 1,
    nodes: {},
    root_id: 0,
    focused_node: None,
    mutations: MutationQueue::new(),
  }
  tree.nodes[0] = DomNode::new(Document, "#document")
  tree.root_id = 0
  tree
}

// =============================================================================
// Node Creation
// =============================================================================

///|
/// Create an element node
pub fn DomTree::create_element(self : DomTree, tag_name : String) -> NodeId {
  self.allocate_node(DomNode::new(Element, tag_name))
}

///|
/// Create a text node
pub fn DomTree::create_text(self : DomTree, content : String) -> NodeId {
  self.allocate_node(DomNode::new(Text, "#text", text_content=content))
}

///|
/// Create a comment node
pub fn DomTree::create_comment(self : DomTree, content : String) -> NodeId {
  self.allocate_node(DomNode::new(Comment, "#comment", text_content=content))
}

///|
fn DomTree::allocate_node(self : DomTree, node : DomNode) -> NodeId {
  let id = self.next_id
  self.next_id += 1
  self.nodes[id] = node
  NodeId(id)
}

// =============================================================================
// Tree Manipulation
// =============================================================================

///|
/// Append child to parent
pub fn DomTree::append_child(
  self : DomTree,
  parent : NodeId,
  child : NodeId,
) -> Result[Unit, CoreError] {
  let parent_id = parent.to_int()
  let child_id = child.to_int()
  match (self.nodes.get(parent_id), self.nodes.get(child_id)) {
    (Some(p), Some(c)) => {
      // Remove from old parent if any
      match c.parent_id {
        Some(old_parent_id) =>
          match self.nodes.get(old_parent_id) {
            Some(old_parent) => {
              let idx = old_parent.children.search(child_id)
              match idx {
                Some(i) => {
                  let _ = old_parent.children.remove(i)
                  // Record removal from old parent
                  self.mutations.push(
                    MutationRecord::child_list(NodeId(old_parent_id), removed=[
                      child,
                    ]),
                  )
                }
                None => ()
              }
            }
            None => ()
          }
        None => ()
      }
      // Set new parent
      c.parent_id = Some(parent_id)
      // Add to new parent's children
      p.children.push(child_id)
      // Record mutation
      self.mutations.push(MutationRecord::child_list(parent, added=[child]))
      // Invalidate layout cache
      self.invalidate_layout(parent_id)
      Ok(())
    }
    (None, _) => Err(NodeNotFound(node_id=parent))
    (_, None) => Err(NodeNotFound(node_id=child))
  }
}

///|
/// Insert child before reference node
pub fn DomTree::insert_before(
  self : DomTree,
  parent : NodeId,
  child : NodeId,
  reference : NodeId?,
) -> Result[Unit, CoreError] {
  let parent_id = parent.to_int()
  let child_id = child.to_int()
  match self.nodes.get(parent_id) {
    None => return Err(NodeNotFound(node_id=parent))
    Some(p) =>
      match self.nodes.get(child_id) {
        None => return Err(NodeNotFound(node_id=child))
        Some(c) => {
          // Remove from old parent
          match c.parent_id {
            Some(old_parent_id) =>
              match self.nodes.get(old_parent_id) {
                Some(old_parent) => {
                  let idx = old_parent.children.search(child_id)
                  match idx {
                    Some(i) => {
                      let _ = old_parent.children.remove(i)
                      // Record removal from old parent
                      self.mutations.push(
                        MutationRecord::child_list(NodeId(old_parent_id), removed=[
                          child,
                        ]),
                      )
                    }
                    None => ()
                  }
                }
                None => ()
              }
            None => ()
          }
          // Set new parent
          c.parent_id = Some(parent_id)
          // Insert at position
          match reference {
            Some(ref_node) => {
              let ref_id = ref_node.to_int()
              let idx = p.children.search(ref_id)
              match idx {
                Some(i) => p.children.insert(i, child_id)
                None => p.children.push(child_id) // Reference not found, append
              }
            }
            None => p.children.push(child_id) // No reference, append
          }
          // Record mutation
          self.mutations.push(MutationRecord::child_list(parent, added=[child]))
          self.invalidate_layout(parent_id)
          Ok(())
        }
      }
  }
}

///|
/// Remove child from parent
pub fn DomTree::remove_child(
  self : DomTree,
  parent : NodeId,
  child : NodeId,
) -> Result[Unit, CoreError] {
  let parent_id = parent.to_int()
  let child_id = child.to_int()
  match (self.nodes.get(parent_id), self.nodes.get(child_id)) {
    (Some(p), Some(c)) => {
      let idx = p.children.search(child_id)
      match idx {
        Some(i) => {
          let _ = p.children.remove(i)
          c.parent_id = None
          // Record mutation
          self.mutations.push(
            MutationRecord::child_list(parent, removed=[child]),
          )
          self.invalidate_layout(parent_id)
          Ok(())
        }
        None => Err(InvalidOperation(message="Child not found in parent"))
      }
    }
    (None, _) => Err(NodeNotFound(node_id=parent))
    (_, None) => Err(NodeNotFound(node_id=child))
  }
}

// =============================================================================
// Attribute Operations
// =============================================================================

///|
/// Set attribute on element
pub fn DomTree::set_attribute(
  self : DomTree,
  node : NodeId,
  name : String,
  value : String,
) -> Result[Unit, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => {
      let old_value = n.attributes.get(name)
      n.attributes[name] = value
      // Record mutation
      self.mutations.push(
        MutationRecord::attribute(node, name, old_value~, new_value=Some(value)),
      )
      self.invalidate_layout(id)
      Ok(())
    }
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Get attribute from element
pub fn DomTree::get_attribute(
  self : DomTree,
  node : NodeId,
  name : String,
) -> Result[String?, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => Ok(n.attributes.get(name))
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Remove attribute from element
pub fn DomTree::remove_attribute(
  self : DomTree,
  node : NodeId,
  name : String,
) -> Result[Unit, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => {
      let old_value = n.attributes.get(name)
      n.attributes.remove(name)
      // Record mutation
      self.mutations.push(
        MutationRecord::attribute(node, name, old_value~, new_value=None),
      )
      self.invalidate_layout(id)
      Ok(())
    }
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Get all attributes
pub fn DomTree::get_attributes(
  self : DomTree,
  node : NodeId,
) -> Result[Array[(String, String)], CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => {
      let attrs : Array[(String, String)] = []
      for name, value in n.attributes {
        attrs.push((name, value))
      }
      Ok(attrs)
    }
    None => Err(NodeNotFound(node_id=node))
  }
}

// =============================================================================
// Node Information
// =============================================================================

///|
/// Get node info
pub fn DomTree::get_node_info(
  self : DomTree,
  node : NodeId,
) -> Result[NodeInfo, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) =>
      Ok({
        node_id: node,
        node_type: n.node_type,
        node_name: n.tag_name,
        node_value: n.text_content,
        child_count: n.children.length(),
      })
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Get tag name
pub fn DomTree::get_tag_name(
  self : DomTree,
  node : NodeId,
) -> Result[String, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => Ok(n.tag_name)
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Get text content
pub fn DomTree::get_text_content(
  self : DomTree,
  node : NodeId,
) -> Result[String, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) =>
      match n.node_type {
        Text | Comment => Ok(n.text_content)
        _ => {
          // For elements, collect all descendant text
          let buf = StringBuilder::new()
          self.collect_text_content(id, buf)
          Ok(buf.to_string())
        }
      }
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Set text content
pub fn DomTree::set_text_content(
  self : DomTree,
  node : NodeId,
  content : String,
) -> Result[Unit, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => {
      match n.node_type {
        Text | Comment => {
          let old_value = n.text_content
          n.text_content = content
          // Record mutation
          self.mutations.push(
            MutationRecord::character_data(
              node,
              old_value=Some(old_value),
              new_value=Some(content),
            ),
          )
        }
        _ => {
          // For elements, remove all children and add text node
          // Record removal of existing children
          for child_id in n.children {
            self.mutations.push(
              MutationRecord::child_list(node, removed=[NodeId(child_id)]),
            )
          }
          n.children.clear()
          let text_node = self.create_text(content)
          let _ = self.append_child(node, text_node)

        }
      }
      self.invalidate_layout(id)
      Ok(())
    }
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Collect text content recursively
fn DomTree::collect_text_content(
  self : DomTree,
  node_id : Int,
  buf : StringBuilder,
) -> Unit {
  match self.nodes.get(node_id) {
    Some(n) =>
      match n.node_type {
        Text => buf.write_string(n.text_content)
        _ =>
          for child_id in n.children {
            self.collect_text_content(child_id, buf)
          }
      }
    None => ()
  }
}

// =============================================================================
// Tree Traversal
// =============================================================================

///|
/// Get document root
pub fn DomTree::get_document(self : DomTree) -> NodeId {
  NodeId(self.root_id)
}

///|
/// Get parent node
pub fn DomTree::get_parent(
  self : DomTree,
  node : NodeId,
) -> Result[NodeId?, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => Ok(n.parent_id.map(fn(x) { NodeId(x) }))
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Get child nodes
pub fn DomTree::get_children(
  self : DomTree,
  node : NodeId,
) -> Result[Array[NodeId], CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => Ok(n.children.map(fn(x) { NodeId(x) }))
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Get first child
pub fn DomTree::get_first_child(
  self : DomTree,
  node : NodeId,
) -> Result[NodeId?, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => Ok(n.children.get(0).map(NodeId::from_int))
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
/// Get next sibling
pub fn DomTree::get_next_sibling(
  self : DomTree,
  node : NodeId,
) -> Result[NodeId?, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) =>
      match n.parent_id {
        Some(parent_id) =>
          match self.nodes.get(parent_id) {
            Some(parent) => {
              let idx = parent.children.search(id)
              match idx {
                Some(i) if i + 1 < parent.children.length() =>
                  Ok(Some(NodeId(parent.children[i + 1])))
                _ => Ok(None)
              }
            }
            None => Ok(None)
          }
        None => Ok(None)
      }
    None => Err(NodeNotFound(node_id=node))
  }
}

// =============================================================================
// Layout Cache
// =============================================================================

///|
/// Invalidate layout cache for node and ancestors
fn DomTree::invalidate_layout(self : DomTree, node_id : Int) -> Unit {
  match self.nodes.get(node_id) {
    Some(n) => {
      n.cached_rect = None
      match n.parent_id {
        Some(parent_id) => self.invalidate_layout(parent_id)
        None => ()
      }
    }
    None => ()
  }
}

///|
/// Set cached rect (called by layout engine)
pub fn DomTree::set_cached_rect(
  self : DomTree,
  node : NodeId,
  rect : Rect,
) -> Unit {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => n.cached_rect = Some(rect)
    None => ()
  }
}

///|
/// Get cached rect
pub fn DomTree::get_cached_rect(self : DomTree, node : NodeId) -> Rect? {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => n.cached_rect
    None => None
  }
}

// =============================================================================
// Focus Management
// =============================================================================

///|
/// Set focused element
pub fn DomTree::set_focus(self : DomTree, node : NodeId?) -> Unit {
  self.focused_node = node.map(fn(x) { x.to_int() })
}

///|
/// Get focused element
pub fn DomTree::get_focus(self : DomTree) -> NodeId? {
  self.focused_node.map(fn(x) { NodeId(x) })
}

// =============================================================================
// Mutation Queue API
// =============================================================================

///|
/// Check if there are pending mutations
pub fn DomTree::has_pending_mutations(self : DomTree) -> Bool {
  not(self.mutations.is_empty())
}

///|
/// Get number of pending mutations
pub fn DomTree::pending_mutation_count(self : DomTree) -> Int {
  self.mutations.length()
}

///|
/// Flush pending mutations and return optimized records
/// Returns (records, result) where records is the optimized mutation list
/// and result contains statistics about the flush operation
pub fn DomTree::flush_mutations(
  self : DomTree,
) -> (Array[MutationRecord], FlushResult) {
  self.mutations.flush()
}

///|
/// Flush mutations with a custom handler for each record
/// Useful for applying mutations to other systems (e.g., LayoutTree)
pub fn[T] DomTree::flush_mutations_with(
  self : DomTree,
  handler : (MutationRecord) -> T,
) -> (Array[T], FlushResult) {
  self.mutations.flush_with(handler)
}

///|
/// Clear all pending mutations without processing
pub fn DomTree::clear_mutations(self : DomTree) -> Unit {
  self.mutations.clear()
}

///|
/// Get direct access to the mutation queue (for advanced use cases)
pub fn DomTree::get_mutation_queue(self : DomTree) -> MutationQueue {
  self.mutations
}