///|
fn Parser::handle_template_table_context_start_tag(
  self : Parser,
  name : String,
  attrs : Map[String, String?],
  start : Int,
) -> Bool {
  if !self.template_table_context_mode_active() {
    return false
  }
  if name == "table" {
    self.error_at("unexpected-start-tag-implies-end-tag", start)
    self.error_at("unexpected-end-tag", start)
    return true
  }
  if name == "caption" {
    if !self.close_open_template_content_section() {
      match self.template_content_mode() {
        "row" | "table-body" => {
          // A caption after row or cell content acts as an end tag with
          // nothing in scope: parse error, ignore. With an open section
          // the in-table-body rules close it and the caption inserts.
          self.error_at("unexpected-start-tag", start)
          return true
        }
        _ => ()
      }
    }
    let node = self.node_with_origin(@dom.element("caption", attrs~), start)
    self.current_node().append_child(node)
    self.push_open_element(node)
    self.push_active_formatting_marker()
    self.disable_current_template_table_context_mode()
    return true
  }
  if (name == "col" || name == "colgroup") &&
    !self.close_open_template_content_section() &&
    (
      self.template_content_mode() == "row" ||
      self.template_content_mode() == "table-body"
    ) {
    self.error_at("unexpected-start-tag", start)
    return true
  }
  if name == "col" {
    let colgroup = @dom.element("colgroup")
    self.current_node().append_child(colgroup)
    colgroup.append_child(
      self.node_with_origin(@dom.element("col", attrs~), start),
    )
    self.push_open_element(colgroup)
    self.disable_current_template_table_context_mode()
    self.template_detached_column_group_mode = !self.in_template_content_without_inner_table()
    return true
  }
  if name == "colgroup" {
    let node = self.node_with_origin(@dom.element("colgroup", attrs~), start)
    self.current_node().append_child(node)
    self.push_open_element(node)
    self.disable_current_template_table_context_mode()
    self.template_detached_column_group_mode = !self.in_template_content_without_inner_table()
    return true
  }
  if name == "tbody" || name == "tfoot" || name == "thead" {
    if !self.close_open_template_content_section() {
      match self.template_content_mode() {
        "row" | "table-body" => {
          // A section start tag after row or cell content acts as an end
          // tag that finds nothing in scope: parse error, ignore. With
          // an open section the in-table-body rules close it first and
          // the new section inserts after it.
          self.error_at("unexpected-start-tag", start)
          return true
        }
        _ => ()
      }
    }
    let node = self.node_with_origin(@dom.element(name, attrs~), start)
    self.current_node().append_child(node)
    self.push_open_element(node)
    self.disable_current_template_table_context_mode()
    return true
  }
  if name == "tr" {
    match self.template_content_mode() {
      "row" => {
        // In-row tr acts as  with no row in scope: ignore.
        self.error_at("unexpected-start-tag", start)
        return true
      }
      "table" => {
        // In-table tr synthesizes a table body section.
        let section = @dom.element("tbody")
        self.current_node().append_child(section)
        self.push_open_element(section)
        let row = self.node_with_origin(@dom.element("tr"), start)
        section.append_child(row)
        self.push_open_element(row)
        self.disable_current_template_table_context_mode()
        return true
      }
      _ => {
        // In-template and in-table-body insert a bare row.
        let row = self.node_with_origin(@dom.element("tr", attrs~), start)
        self.current_node().append_child(row)
        self.push_open_element(row)
        self.disable_current_template_table_context_mode()
        return true
      }
    }
  }
  if name == "td" || name == "th" {
    let mode = self.template_content_mode()
    if mode == "table" {
      // In-table cell synthesizes a section and a row.
      let section = @dom.element("tbody")
      self.current_node().append_child(section)
      self.push_open_element(section)
      let row = @dom.element("tr")
      section.append_child(row)
      self.push_open_element(row)
    } else if mode == "table-body" {
      // In-table-body cell synthesizes a bare row.
      self.error_at("unexpected-cell-in-table-body", start)
      let row = @dom.element("tr")
      self.current_node().append_child(row)
      self.push_open_element(row)
    }
    let cell = self.node_with_origin(@dom.element(name, attrs~), start)
    self.current_node().append_child(cell)
    self.push_open_element(cell)
    self.push_active_formatting_marker()
    self.disable_current_template_table_context_mode()
    return true
  }
  false
}

///|
fn Parser::report_template_table_context_fostered_start(
  self : Parser,
  name : StringView,
  start : Int,
) -> Unit {
  if !self.template_table_context_mode_active() {
    return
  }
  match name {
    "html" | "script" | "style" | "template" => ()
    _ => self.error_at("foster-parenting-start-tag", start)
  }
}

///|
fn template_table_context_end_tag_defers_voodoo(name : StringView) -> Bool {
  name is ("colgroup" | "tr")
}

///|
fn template_table_context_section_end_tag_reports_unexpected(
  name : StringView,
) -> Bool {
  name is ("tbody" | "tfoot" | "thead")
}

///|
fn Parser::handle_template_table_context_end_tag(
  self : Parser,
  name : String,
  start : Int,
) -> Bool {
  if !self.in_template_content_without_inner_table() {
    return false
  }
  if template_table_context_end_tag_defers_voodoo(name) &&
    self.close_open_element(name) {
    self.record_template_content_close(name)
    self.enable_template_table_context_mode()
    return true
  }
  if template_table_context_section_end_tag_reports_unexpected(name) &&
    self.close_open_element(name) {
    self.error_at("unexpected-end-tag", start)
    self.record_template_content_close(name)
    self.enable_template_table_context_mode()
    return true
  }
  false
}

///|
fn Parser::record_template_content_close(self : Parser, name : String) -> Unit {
  match name {
    "td" | "th" => self.set_template_content_mode("row")
    "tr" => self.set_template_content_mode("table-body")
    "tbody" | "tfoot" | "thead" | "caption" | "colgroup" =>
      self.set_template_content_mode("table")
    _ => ()
  }
}

///|
/// Flow content (anything but a cell or an in-head element) arriving while
/// the current node is a bare template-content row is foster-parented into
/// the row's parent — the row stays open for later cells, mirroring the
/// in-row anything-else handling without a real table.
fn Parser::bare_template_row_fosters(self : Parser, name : StringView) -> Bool {
  if name == "td" ||
    name == "th" ||
    name == "tr" ||
    name == "template" ||
    name == "script" ||
    name == "style" ||
    name == "caption" ||
    name == "col" ||
    name == "colgroup" ||
    name == "tbody" ||
    name == "tfoot" ||
    name == "thead" ||
    name == "table" {
    // Cells, in-head elements, and table-structural tags are not flow
    // content; they take the row/table-body structural handling.
    return false
  }
  if !self.in_template_content_without_inner_table() {
    return false
  }
  let len = self.stack.length()
  if len < 2 {
    return false
  }
  let last = self.stack[len - 1]
  let below = self.stack[len - 2]
  last.kind == Element &&
  (
    last.name == "tr" ||
    last.name == "tbody" ||
    last.name == "tfoot" ||
    last.name == "thead"
  ) &&
  (last.ns is None || last.ns is Some("html")) &&
  below.kind == Element &&
  below.name == "template"
}

///|
/// Closes an open table section (and any open row) sitting above the
/// current template on the stack, recording table mode. Returns true when
/// a section was open: the in-table-body rules close it and let the
/// caption or column group insert after it instead of being ignored.
fn Parser::close_open_template_content_section(self : Parser) -> Bool {
  let template_index = self.last_open_template_index()
  if template_index < 0 {
    return false
  }
  let mut section_index = -1
  let mut index = self.stack.length()
  while index > template_index + 1 {
    index -= 1
    let node = self.stack[index]
    if node.kind == Element &&
      (node.name == "tbody" || node.name == "tfoot" || node.name == "thead") &&
      (node.ns is None || node.ns is Some("html")) {
      section_index = index
      break
    }
  }
  if section_index < 0 {
    return false
  }
  while self.stack.length() > section_index {
    ignore(self.pop_open_element())
  }
  self.set_template_content_mode("table")
  true
}

///|
/// Prepares a bare template-content row for an incoming start tag: cells
/// clear the stack back to the row (popping fostered flow elements), and
/// table-structural tags close the row (acting as ) so the mode
/// handler can gate or reprocess them.
fn Parser::prepare_bare_template_row_start(
  self : Parser,
  name : StringView,
) -> Unit {
  if !self.in_template_content_without_inner_table() {
    return
  }
  let template_index = self.last_open_template_index()
  let mut row_index = -1
  let mut index = self.stack.length()
  while index > template_index + 1 {
    index -= 1
    let node = self.stack[index]
    if node.kind == Element &&
      node.name == "tr" &&
      (node.ns is None || node.ns is Some("html")) {
      row_index = index
      break
    }
  }
  if row_index < 0 {
    if name == "tr" || name == "td" || name == "th" {
      // With an open section but no open row, pop foster-parented flow
      // elements back to the section so rows and cells enter it.
      let mut section_index = -1
      index = self.stack.length()
      while index > template_index + 1 {
        index -= 1
        let node = self.stack[index]
        if node.kind == Element &&
          (node.name == "tbody" || node.name == "tfoot" || node.name == "thead") &&
          (node.ns is None || node.ns is Some("html")) {
          section_index = index
          break
        }
      }
      if section_index >= 0 && section_index < self.stack.length() - 1 {
        while self.stack.length() > section_index + 1 {
          ignore(self.pop_open_element())
        }
      }
    }
    return
  }
  if name == "td" || name == "th" {
    let mut popped_cell = false
    while self.stack.length() > row_index + 1 {
      match self.pop_open_element() {
        Some(node) =>
          if node.kind == Element &&
            (node.name == "td" || node.name == "th") &&
            (node.ns is None || node.ns is Some("html")) {
            popped_cell = true
          }
        None => ()
      }
    }
    if popped_cell {
      // The implied close of the previous cell clears its formatting
      // marker, so stale formatting does not reconstruct into the next
      // cell.
      self.clear_active_formatting_to_marker()
    }
    return
  }
  if name == "tr" ||
    name == "caption" ||
    name == "col" ||
    name == "colgroup" ||
    name == "tbody" ||
    name == "tfoot" ||
    name == "thead" ||
    name == "table" {
    let mut popped_cell = false
    while self.stack.length() > row_index {
      match self.pop_open_element() {
        Some(node) =>
          if node.kind == Element &&
            (node.name == "td" || node.name == "th") &&
            (node.ns is None || node.ns is Some("html")) {
            popped_cell = true
          }
        None => ()
      }
    }
    if popped_cell {
      // Closing the row through an open cell also clears the cell's
      // formatting marker.
      self.clear_active_formatting_to_marker()
    }
    // A select popped with the row no longer holds the parser in select
    // mode.
    self.reset_select_insertion_mode()
    self.set_template_content_mode("table-body")
    self.enable_template_table_context_mode()
  }
}

///|
/// True when the current node can host the template content's table
/// machinery: the template itself or an open table-structure element.
/// Inside flow content (e.g. a div) the in-body rules apply instead and
/// stray table-structural start tags are ignored.
fn Parser::template_content_at_structural_position(self : Parser) -> Bool {
  if self.template_content_mode() == "body" {
    return false
  }
  let current = self.current_node()
  if current.kind == Element &&
    current.name == "template" &&
    (current.ns is None || current.ns is Some("html")) {
    return true
  }
  // Any open table-structure element above the template keeps the
  // machinery active, even with a foster-parented flow element on top.
  let template_index = self.last_open_template_index()
  let mut index = self.stack.length()
  while index > template_index + 1 && template_index >= 0 {
    index -= 1
    let node = self.stack[index]
    if node.kind == Element && (node.ns is None || node.ns is Some("html")) {
      match node.name {
        "tr"
        | "tbody"
        | "tfoot"
        | "thead"
        | "caption"
        | "colgroup"
        | "td"
        | "th" => return true
        _ => ()
      }
    }
  }
  false
}

///|
/// Flow content inserted directly at the template content top switches
/// the template's mode to in-body: later table-structural start tags are
/// then ignored per the in-body rules instead of synthesizing structure.
/// The in-head elements (template, script, style) leave the mode alone.
fn Parser::record_template_content_flow_insert(
  self : Parser,
  name : StringView,
) -> Unit {
  if name == "template" ||
    name == "script" ||
    name == "style" ||
    name == "base" ||
    name == "basefont" ||
    name == "bgsound" ||
    name == "link" ||
    name == "meta" ||
    name == "noframes" ||
    name == "title" ||
    name == "col" ||
    name == "colgroup" ||
    name == "caption" ||
    name == "tbody" ||
    name == "tfoot" ||
    name == "thead" ||
    name == "tr" ||
    name == "td" ||
    name == "th" {
    return
  }
  // Only content-top insertions (current node is the template itself)
  // can flip the mode, and this runs for every element insertion: the
  // cheap current-node test goes before any stack walk.
  let current = self.current_node()
  if !(current
    is { kind: Element, name: "template", ns: None | Some("html"), .. }) {
    return
  }
  if !self.in_template_content_without_inner_table() {
    return
  }
  if self.template_content_mode() != "" {
    // Only the initial in-template mode flips to in-body; established
    // table modes (row, table-body, table) persist through flow content.
    return
  }
  self.set_template_content_mode("body")
}