///|
fn RenderNode::collect_draw_commands(
  self : RenderNode,
  commands : Array[DrawCommand],
) -> Unit {
  let overlays : Array[DrawCommand] = []
  let deferred : Array[RenderNode] = []
  self.collect_draw_commands_with_overlays(commands, overlays, deferred)
  RenderNode::drain_deferred_draw(deferred, commands, overlays)
  for command in overlays {
    commands.push(command)
  }
}

///|
fn RenderNode::drain_deferred_draw(
  deferred : Array[RenderNode],
  commands : Array[DrawCommand],
  overlays : Array[DrawCommand],
) -> Unit {
  let mut head = 0
  while head < deferred.length() {
    deferred[head].collect_draw_commands_with_overlays(
      commands, overlays, deferred,
    )
    head = head + 1
  }
}

///|
fn RenderNode::drain_deferred_frame(
  deferred : Array[RenderNode],
  commands : Array[DrawCommand],
  overlays : Array[DrawCommand],
) -> Unit {
  let mut head = 0
  while head < deferred.length() {
    deferred[head].collect_frame_commands_with_overlays(
      commands, overlays, deferred,
    )
    head = head + 1
  }
}

///|
/// Return the union of view-level overlay paint bounds. Native platform views
/// may use this to expose the already-rendered overlay pixels above their own
/// surface without introducing a second renderer.
fn RenderNode::overlay_bounds(self : RenderNode) -> @core.Rect? {
  let mut bounds : @core.Rect? = None
  for command in self.overlay_paint_commands {
    bounds = @core.optional_rect_union(bounds, command.bounds())
  }
  for child in self.children {
    bounds = @core.optional_rect_union(bounds, child.overlay_bounds())
  }
  bounds
}

///|
fn RenderNode::collect_frame_commands(
  self : RenderNode,
  commands : Array[DrawCommand],
) -> Unit {
  let overlays : Array[DrawCommand] = []
  let deferred : Array[RenderNode] = []
  self.collect_frame_commands_with_overlays(commands, overlays, deferred)
  RenderNode::drain_deferred_frame(deferred, commands, overlays)
  for command in overlays {
    commands.push(command)
  }
}

///|
fn RenderNode::collect_frame_commands_with_overlays(
  self : RenderNode,
  commands : Array[DrawCommand],
  overlays : Array[DrawCommand],
  deferred : Array[RenderNode],
) -> Unit {
  let fade = match self.render_opacity {
    Some(opacity) => opacity < 1.0
    None => false
  }
  if fade {
    commands.push(
      PushOpacity(
        match self.render_opacity {
          Some(opacity) => opacity
          None => 1.0
        },
      ),
    )
  }
  if self.is_repaint_boundary {
    match self.cached_layer_spec() {
      Some(spec) => {
        commands.push(BeginRetainedLayer(spec))
        self.collect_uncached_frame_commands_with_overlays(
          commands, overlays, deferred,
        )
        commands.push(EndRetainedLayer(spec.key))
      }
      None =>
        self.collect_uncached_frame_commands_with_overlays(
          commands, overlays, deferred,
        )
    }
  } else {
    self.collect_uncached_frame_commands_with_overlays(
      commands, overlays, deferred,
    )
  }
}

///|
fn RenderNode::collect_draw_commands_with_overlays(
  self : RenderNode,
  commands : Array[DrawCommand],
  overlays : Array[DrawCommand],
  deferred : Array[RenderNode],
) -> Unit {
  let fade = match self.render_opacity {
    Some(opacity) => opacity < 1.0
    None => false
  }
  if fade {
    commands.push(
      PushOpacity(
        match self.render_opacity {
          Some(opacity) => opacity
          None => 1.0
        },
      ),
    )
  }
  match self.cached_draw_commands {
    Some(cached) => {
      for command in cached {
        commands.push(command)
      }
      match self.cached_overlay_draw_commands {
        Some(cached_overlays) =>
          for command in cached_overlays {
            overlays.push(command)
          }
        None => ()
      }
      return
    }
    None => ()
  }
  for command in self.paint_commands {
    commands.push(command)
  }
  for child in self.children {
    if child.is_overlay_child {
      deferred.push(child)
    } else {
      child.collect_draw_commands_with_overlays(commands, overlays, deferred)
    }
  }
  for command in self.post_paint_commands {
    commands.push(command)
  }
  for command in self.overlay_paint_commands {
    overlays.push(command)
  }
}

///|
fn RenderNode::collect_uncached_frame_commands_with_overlays(
  self : RenderNode,
  commands : Array[DrawCommand],
  overlays : Array[DrawCommand],
  deferred : Array[RenderNode],
) -> Unit {
  for command in self.paint_commands {
    commands.push(command)
  }
  for child in self.children {
    if child.is_overlay_child {
      deferred.push(child)
    } else {
      child.collect_frame_commands_with_overlays(commands, overlays, deferred)
    }
  }
  for command in self.post_paint_commands {
    commands.push(command)
  }
  for command in self.overlay_paint_commands {
    overlays.push(command)
  }
}

///|
fn RenderNode::with_repaint_boundary_cache(self : RenderNode) -> RenderNode {
  let commands : Array[DrawCommand] = []
  let overlays : Array[DrawCommand] = []
  // Cache building inlines hoisted overlay children so the retained layer is
  // self-contained; only the root traversal defers them.
  let deferred : Array[RenderNode] = []
  self.collect_draw_commands_with_overlays(commands, overlays, deferred)
  RenderNode::drain_deferred_draw(deferred, commands, overlays)
  {
    ..self,
    is_repaint_boundary: true,
    layer_cache_key: Some(self.repaint_boundary_cache_key()),
    cached_draw_commands: Some(commands),
    cached_overlay_draw_commands: Some(overlays),
  }
}

///|
fn RenderNode::cached_layer_spec(self : RenderNode) -> RetainedLayerSpec? {
  match self.layer_cache_key {
    Some(key) =>
      Some(
        RetainedLayerSpec::new(
          key~,
          frame=self.paint_bounds,
          content_revision=self.content_revision,
        ),
      )
    None => None
  }
}

///|
fn RenderNode::repaint_boundary_cache_key(self : RenderNode) -> String {
  "element=\{self.element_id.value()};x=\{self.frame.origin.x};y=\{self.frame.origin.y};w=\{self.frame.size.width};h=\{self.frame.size.height}"
}

///|
/// True when any hoisted overlay subtree under this node (including hoisted
/// children of descendants) covers `point`. Pointer dispatch and focus pickup
/// use this as a global prepass so a popup anywhere in the tree wins over
/// in-flow content painted underneath it.
fn RenderNode::subtree_overlay_contains(
  self : RenderNode,
  point : Point,
) -> Bool {
  for child in self.children {
    if child.is_overlay_child && child.hit_bounds().contains(point) {
      return true
    }
    if child.subtree_overlay_contains(point) {
      return true
    }
  }
  false
}

///|
/// One-pass per-tree index for damage computation. Built once per frame per
/// previous/current tree in `compute_damage_region`, replacing the previous
/// up-to-two full DFS traversals per dirty id with O(1) lookups and O(depth)
/// parent-chain walks.
priv struct DamageIndexEntry {
  paint_bounds : Rect
  parent_id : Int?
  is_repaint_boundary : Bool
}

///|
priv struct DamageIndex {
  entries : Map[Int, DamageIndexEntry]
}

///|
fn DamageIndex::build(root : RenderNode) -> DamageIndex {
  let entries : Map[Int, DamageIndexEntry] = Map([])
  DamageIndex::build_node(root, None, entries)
  { entries, }
}

///|
fn DamageIndex::build_node(
  node : RenderNode,
  parent_id : Int?,
  entries : Map[Int, DamageIndexEntry],
) -> Unit {
  for child in node.children {
    DamageIndex::build_node(child, Some(node.element_id.value()), entries)
  }
  entries[node.element_id.value()] = {
    paint_bounds: node.paint_bounds,
    parent_id,
    is_repaint_boundary: node.is_repaint_boundary,
  }
}

///|
/// Nearest self-or-ancestor repaint-boundary `paint_bounds`, falling back to
/// the node's own bounds — same contract as the removed `find_damage_bounds`.
fn DamageIndex::damage_bounds(self : DamageIndex, id : ElementId) -> Rect? {
  match self.entries.get(id.value()) {
    Some(entry) => {
      if entry.is_repaint_boundary {
        return Some(entry.paint_bounds)
      }
      let mut cursor = entry.parent_id
      while cursor is Some(pid) {
        match self.entries.get(pid) {
          Some(parent) => {
            if parent.is_repaint_boundary {
              return Some(parent.paint_bounds)
            }
            cursor = parent.parent_id
          }
          // Unreachable while every entry except the root has a parent in
          // the map; fall back to the node's own bounds like the old DFS.
          None => return Some(entry.paint_bounds)
        }
      }
      Some(entry.paint_bounds)
    }
    None => None
  }
}

///|
fn RenderNode::find_render_node(
  self : RenderNode,
  id : ElementId,
) -> RenderNode? {
  if self.element_id == id {
    Some(self)
  } else {
    for child in self.children {
      match child.find_render_node(id) {
        Some(node) => return Some(node)
        None => ()
      }
    }
    None
  }
}

///|
fn ElementNode::render_focused_with_text_system(
  self : ElementNode,
  placed : PlacedNode,
  focused~ : ElementId?,
  environment? : Environment = Environment::neutral(),
  text_system~ : TextSystem,
  now_ms~ : Double,
  overlay_enter? : Array[(ElementId, Double)] = [],
) -> RenderNode {
  self.render_with_environment(
    placed,
    focused,
    RenderEnvironment::from_environment(environment)
    .with_text_system(text_system)
    .with_now_ms(now_ms),
    overlay_enter~,
  )
}