///|
/// 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 {
  mut node_type : NodeType
  mut tag_name : String
  mut text_content : String
  attributes : Map[String, String]
  mut custom_states : Array[String]
  mut form_value : String?
  mut form_state_value : String?
  mut parent_id : Int?
  children : Array[Int]
  mut shadow_root_id : Int?
  mut host_id : Int?
  mut shadow_mode : String?
  mut shadow_delegates_focus : Bool
  mut shadow_slot_assignment : String
  mut shadow_clonable : Bool
  mut shadow_serializable : Bool
  /// For elements: computed style cache
  mut cached_rect : Rect?
  /// Cached selector projection of this node's class list and attribute array,
  /// computed lazily and invalidated when attributes change. Lets querySelector
  /// reuse them across queries instead of re-splitting / rebuilding per query.
  mut sel_classes : Array[String]?
  mut sel_attrs : Array[@selector.Attribute]?
}

///|
fn DomNode::new(
  node_type : NodeType,
  tag_name : String,
  text_content? : String = "",
) -> DomNode {
  {
    node_type,
    tag_name,
    text_content,
    attributes: {},
    custom_states: [],
    form_value: None,
    form_state_value: None,
    parent_id: None,
    children: [],
    shadow_root_id: None,
    host_id: None,
    shadow_mode: None,
    shadow_delegates_focus: false,
    shadow_slot_assignment: "named",
    shadow_clonable: false,
    shadow_serializable: false,
    cached_rect: None,
    sel_classes: None,
    sel_attrs: None,
  }
}

///|
fn DomNode::new_document_fragment() -> DomNode {
  DomNode::new(DocumentFragment, "#document-fragment")
}

///|
/// 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))
}

///|
pub fn DomTree::create_runtime_node(
  self : DomTree,
  id : Int,
  node_type : NodeType,
  tag_name? : String = "",
  text_content? : String = "",
) -> Result[NodeId, CoreError] {
  let node = match node_type {
    Element => DomNode::new(Element, tag_name)
    Text => DomNode::new(Text, "#text", text_content~)
    Comment => DomNode::new(Comment, "#comment", text_content~)
    DocumentFragment => DomNode::new_document_fragment()
    _ => return Err(InvalidOperation(message="unsupported runtime node type"))
  }
  self.allocate_node_with_id(id, node)
}

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

///|
fn DomTree::allocate_node_with_id(
  self : DomTree,
  id : Int,
  node : DomNode,
) -> Result[NodeId, CoreError] {
  if id <= 0 {
    return Err(InvalidOperation(message="node id must be positive"))
  }
  if self.nodes.contains(id) {
    return Err(InvalidOperation(message="node id already exists"))
  }
  if id >= self.next_id {
    self.next_id = id + 1
  }
  self.nodes[id] = node
  Ok(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.record_child_removed(
                    NodeId(old_parent_id),
                    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.record_child_added(parent, 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.record_child_removed(
                        NodeId(old_parent_id),
                        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.record_child_added(parent, 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.record_child_removed(parent, 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
      n.sel_classes = None
      n.sel_attrs = None
      // Record mutation
      self.mutations.record_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)
      n.sel_classes = None
      n.sel_attrs = None
      // Record mutation
      self.mutations.record_attribute(node, name, old_value~)
      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))
  }
}

///|
fn normalize_custom_states(states : Array[String]) -> Array[String] {
  let normalized : Array[String] = []
  for state in states {
    if state.is_empty() {
      continue
    }
    if normalized.search(state) is None {
      normalized.push(state)
    }
  }
  normalized
}

///|
pub fn DomTree::set_custom_states(
  self : DomTree,
  node : NodeId,
  states : Array[String],
) -> Result[Unit, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => {
      if n.node_type != Element {
        return Err(
          InvalidOperation(message="Custom states can only be set on elements"),
        )
      }
      n.custom_states = normalize_custom_states(states)
      self.invalidate_layout(id)
      Ok(())
    }
    None => Err(NodeNotFound(node_id=node))
  }
}

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

///|
pub fn DomTree::set_form_associated_state(
  self : DomTree,
  node : NodeId,
  form_value : String?,
  form_state_value : String?,
) -> Result[Unit, CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => {
      if n.node_type != Element {
        return Err(
          InvalidOperation(
            message="Form-associated state can only be set on elements",
          ),
        )
      }
      n.form_value = form_value
      n.form_state_value = form_state_value
      Ok(())
    }
    None => Err(NodeNotFound(node_id=node))
  }
}

///|
pub fn DomTree::get_form_associated_state(
  self : DomTree,
  node : NodeId,
) -> Result[(String?, String?), CoreError] {
  let id = node.to_int()
  match self.nodes.get(id) {
    Some(n) => Ok((n.form_value, n.form_state_value))
    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.record_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.record_child_removed(node, 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 self.get_invalidation_parent_id(node_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
// =============================================================================

///|
/// Enable or disable mutation recording. Off by default: records exist only to
/// feed the incremental-layout bridge, so a standalone tree (querySelector /
/// one-shot build) records nothing. The bridge enables it on the trees it owns.
pub fn DomTree::set_mutation_recording(self : DomTree, on : Bool) -> Unit {
  self.mutations.set_recording(on)
}

///|
/// Check if there are pending mutations
pub fn DomTree::has_pending_mutations(self : DomTree) -> Bool {
  !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
}