///|
fn foreign_breakout_start_name(name : StringView) -> Bool {
  match name {
    "b"
    | "big"
    | "blockquote"
    | "body"
    | "br"
    | "center"
    | "code"
    | "dd"
    | "div"
    | "dl"
    | "dt"
    | "em"
    | "embed"
    | "h1"
    | "h2"
    | "h3"
    | "h4"
    | "h5"
    | "h6"
    | "head"
    | "hr"
    | "i"
    | "img"
    | "li"
    | "listing"
    | "menu"
    | "meta"
    | "nobr"
    | "ol"
    | "p"
    | "pre"
    | "ruby"
    | "s"
    | "small"
    | "span"
    | "strong"
    | "strike"
    | "sub"
    | "sup"
    | "table"
    | "tt"
    | "u"
    | "ul"
    | "var" => true
    _ => false
  }
}

///|
fn foreign_font_has_breakout_attr(attrs : Map[String, String?]) -> Bool {
  attrs.contains("color") || attrs.contains("face") || attrs.contains("size")
}

///|
fn foreign_start_breaks_out(
  name : StringView,
  attrs : Map[String, String?],
) -> Bool {
  foreign_breakout_start_name(name) ||
  (name == "font" && foreign_font_has_breakout_attr(attrs))
}

///|
fn Parser::clean_foreign_content_text(
  self : Parser,
  raw : StringView,
  start : Int,
) -> String {
  let out = StringBuilder::new(size_hint=raw.length())
  let mut offset = 0
  for ch in raw {
    if ch == '\u{0000}' {
      self.error_at("unexpected-null-character", start + offset)
      self.error_at("invalid-codepoint-in-foreign-content", start + offset)
      out.write_char('\u{FFFD}')
    } else {
      out.write_char(ch)
    }
    offset += ch.utf16_len()
  }
  out.to_string()
}

///|
fn Parser::pop_until_html_or_integration_point(self : Parser) -> Unit {
  while self.stack.length() > 1 {
    let current = self.current_node()
    match current.ns {
      Some(ns) if is_foreign_namespace(ns) => {
        if node_is_html_integration_point(current) {
          return
        }
        if self.node_is_foreign_fragment_context(current) {
          return
        }
        ignore(self.stack.pop())
      }
      _ => return
    }
  }
}

///|
fn Parser::handle_foreign_content_start_tag(
  self : Parser,
  name : String,
  attrs : Map[String, String?],
  self_closing : Bool,
  start : Int,
  tag_close_pos : Int,
) -> Bool {
  if !self.current_node_uses_foreign_content_for_start(name) {
    return false
  }
  if foreign_start_breaks_out(name, attrs) {
    self.error_at("unexpected-html-element-in-foreign-content", tag_close_pos)
    self.pop_until_html_or_integration_point()
    return false
  }
  let current = self.current_node()
  let ns = current.ns.unwrap()
  let adjusted_name = adjusted_start_tag_name_for_namespace(ns, name)
  let adjusted_attrs = prepare_foreign_attrs(ns, attrs)
  let node = self.node_with_origin(
    @dom.element(adjusted_name, attrs=adjusted_attrs, ns~),
    start,
  )
  self.current_node().append_child(node)
  if !self_closing {
    self.stack.push(node)
  }
  true
}

///|
fn Parser::handle_foreign_content_end_tag(
  self : Parser,
  name : String,
  start : Int,
) -> Bool {
  let current = self.current_node()
  match current.ns {
    Some(ns) if is_foreign_namespace(ns) => ()
    _ => return false
  }
  if name == "br" || name == "p" {
    self.error_at("unexpected-html-element-in-foreign-content", start)
    self.pop_until_html_or_integration_point()
    return false
  }
  let mut index = self.stack.length()
  let mut first = true
  while index > 0 {
    index -= 1
    let node = self.stack[index]
    if node.kind == Element && names_match_end_tag(node, name) {
      if self.node_is_foreign_fragment_context(node) {
        self.error_at("unexpected-end-tag-in-fragment-context", start)
        return true
      }
      if node_is_html_namespace(node) {
        return false
      }
      while self.stack.length() > index {
        ignore(self.stack.pop())
      }
      return true
    }
    if first {
      self.error_at("unexpected-end-tag", start)
      first = false
    }
    if node_is_html_namespace(node) {
      return false
    }
  }
  true
}

///|
fn Parser::parse_foreign_fragment_context(
  self : Parser,
  context : FragmentContext,
  ns : String,
) -> Unit {
  let name = adjusted_start_tag_name_for_namespace(ns, context.tag_name)
  let wrapper = @dom.element(name, ns~)
  self.fragment_context_foreign = Some(wrapper)
  self.root.append_child(wrapper)
  self.stack.push(wrapper)
  self.parse_all()
  unwrap_fragment_context_element(self.root, wrapper)
  self.fragment_context_foreign = None
}