///|
/// Drop every node of `kind` below `root`, visiting template contents before
/// regular children. Uses an explicit frame stack instead of recursion; every
/// child that is kept gets its own frame, exactly like the former recursive
/// call, so template contents of any kept node are also visited.
fn apply_drop_kind_transform(
  root : @dom.Node,
  kind : @dom.NodeKind,
  transform : TransformSpec,
) -> Unit {
  let stack : Array[WalkFrame] = [WalkFrame::new(root)]
  while stack.last() is Some(frame) {
    if frame.schedule_template_first(stack) {
      continue
    }
    let node = frame.node
    guard node.kind is (Document | Fragment | Element) &&
      frame.index < node.children.length() else {
      ignore(stack.pop())
      continue
    }
    let index = frame.index
    let child = node.children[index]
    if child.kind == kind {
      transform_call_hook(child, transform)
      let label = match kind {
        Comment => "comment"
        Doctype => "doctype"
        _ => "node"
      }
      transform_report(transform, "Dropped \{label}", Some(child))
      transform_remove_child_at(node, index)
    } else {
      frame.index = index + 1
      stack.push(WalkFrame::new(child))
    }
  }
}

///|
fn node_has_foreign_namespace(node : @dom.Node) -> Bool {
  match node.ns {
    Some(ns) => ns != "html"
    None => false
  }
}

///|
/// Drop every foreign-namespace element below `root`, visiting template
/// contents before regular children. Uses an explicit frame stack instead of
/// recursion, mirroring the former recursive walk frame by frame.
fn apply_drop_foreign_namespaces_transform(
  root : @dom.Node,
  transform : TransformSpec,
) -> Unit {
  let stack : Array[WalkFrame] = [WalkFrame::new(root)]
  while stack.last() is Some(frame) {
    if frame.schedule_template_first(stack) {
      continue
    }
    let node = frame.node
    guard node.kind is (Document | Fragment | Element) &&
      frame.index < node.children.length() else {
      ignore(stack.pop())
      continue
    }
    let index = frame.index
    let child = node.children[index]
    if child.kind == Element && node_has_foreign_namespace(child) {
      transform_call_hook(child, transform)
      let name = @syn.lower_ascii(child.name)
      transform_report(
        transform,
        "Unsafe tag '\{name}' (foreign namespace)",
        Some(child),
      )
      transform_remove_child_at(node, index)
    } else {
      frame.index = index + 1
      stack.push(WalkFrame::new(child))
    }
  }
}