///|
fn Parser::last_open_template_index(self : Parser) -> Int {
  if self.open_template_count == 0 {
    return -1
  }
  let mut index = self.stack.length()
  while index > 0 {
    index -= 1
    let node = self.stack[index]
    if node.kind == Element &&
      node.name == "template" &&
      (node.ns is None || node.ns is Some("html")) {
      return index
    }
  }
  -1
}

///|
fn Parser::in_template_content_without_inner_table(self : Parser) -> Bool {
  let template_index = self.last_open_template_index()
  template_index >= 0 && self.last_open_table_index() < template_index
}

///|
fn Parser::current_template_node(self : Parser) -> @dom.Node? {
  let template_index = self.last_open_template_index()
  if template_index >= 0 {
    Some(self.stack[template_index])
  } else {
    None
  }
}

///|
fn node_array_contains_identity(
  nodes : Array[@dom.Node],
  target : @dom.Node,
) -> Bool {
  nodes[:].any(node => physical_equal(node, target))
}

///|
fn remove_parser_node_identity(
  nodes : Array[@dom.Node],
  target : @dom.Node,
) -> Unit {
  let mut index = 0
  while index < nodes.length() {
    if physical_equal(nodes[index], target) {
      ignore(nodes.remove(index))
    } else {
      index += 1
    }
  }
}

///|
fn Parser::template_column_group_mode_active(self : Parser) -> Bool {
  match self.current_template_node() {
    Some(template) =>
      node_array_contains_identity(
        self.template_column_group_templates,
        template,
      )
    None => false
  }
}

///|
fn Parser::enable_template_column_group_mode(self : Parser) -> Unit {
  match self.current_template_node() {
    Some(template) =>
      if !node_array_contains_identity(
          self.template_column_group_templates,
          template,
        ) {
        self.template_column_group_templates.push(template)
      }
    None => ()
  }
}

///|
fn Parser::disable_current_template_column_group_mode(self : Parser) -> Unit {
  match self.current_template_node() {
    Some(template) =>
      remove_parser_node_identity(
        self.template_column_group_templates,
        template,
      )
    None => ()
  }
}

///|
fn Parser::disable_template_column_group_mode_for_node(
  self : Parser,
  template : @dom.Node,
) -> Unit {
  remove_parser_node_identity(self.template_column_group_templates, template)
}

///|
fn Parser::template_table_context_mode_active(self : Parser) -> Bool {
  if self.template_table_context_detached_mode &&
    self.last_open_template_index() < 0 {
    return true
  }
  match self.current_template_node() {
    Some(template) =>
      node_array_contains_identity(
        self.template_table_context_templates,
        template,
      )
    None => false
  }
}

///|
fn Parser::enable_template_table_context_mode(self : Parser) -> Unit {
  match self.current_template_node() {
    Some(template) =>
      if !node_array_contains_identity(
          self.template_table_context_templates,
          template,
        ) {
        self.template_table_context_templates.push(template)
      }
    None => self.template_table_context_detached_mode = true
  }
}

///|
fn Parser::disable_current_template_table_context_mode(self : Parser) -> Unit {
  match self.current_template_node() {
    Some(template) =>
      remove_parser_node_identity(
        self.template_table_context_templates,
        template,
      )
    None => self.template_table_context_detached_mode = false
  }
}

///|
fn Parser::disable_template_table_context_mode_for_node(
  self : Parser,
  template : @dom.Node,
) -> Unit {
  remove_parser_node_identity(self.template_table_context_templates, template)
}

///|
fn Parser::clear_detached_template_table_context_for_template_start(
  self : Parser,
  name : StringView,
) -> Unit {
  if name == "template" &&
    self.template_table_context_detached_mode &&
    self.last_open_template_index() < 0 {
    self.template_table_context_detached_mode = false
  }
}

///|
fn Parser::handle_template_end_tag(
  self : Parser,
  name : String,
  error_pos : Int,
  source_end_tag? : String,
) -> Bool {
  if name != "template" {
    return false
  }
  let template_index = self.last_open_template_index()
  if template_index < 0 {
    self.error_at("unexpected-end-tag", error_pos)
    return true
  }
  let template = self.stack[template_index]
  if source_end_tag is Some(raw) {
    set_node_source_end_tag(template, raw)
  }
  self.disable_template_column_group_mode_for_node(template)
  self.disable_template_table_context_mode_for_node(template)
  self.clear_template_content_mode_for_node(template)
  self.template_detached_column_group_mode = false
  while self.stack.length() > template_index {
    ignore(self.pop_open_element())
  }
  self.clear_active_formatting_to_marker()
  true
}

///|
/// The template content's table mode hint: which structural element was
/// last closed directly inside the current template's contents ("row"
/// after a cell, "table-body" after a tr, "table" after a section or
/// caption, "" initially). Drives the spec's template insertion-mode
/// behavior for the next structural start tag.
fn Parser::template_content_mode(self : Parser) -> String {
  match self.current_template_node() {
    Some(template) => {
      for entry in self.template_content_modes {
        let (node, mode) = entry
        if physical_equal(node, template) {
          return mode
        }
      }
      ""
    }
    None => ""
  }
}

///|
fn Parser::set_template_content_mode(self : Parser, mode : String) -> Unit {
  match self.current_template_node() {
    Some(template) => {
      let mut index = 0
      while index < self.template_content_modes.length() {
        let (node, _) = self.template_content_modes[index]
        if physical_equal(node, template) {
          self.template_content_modes[index] = (template, mode)
          return
        }
        index += 1
      }
      self.template_content_modes.push((template, mode))
    }
    None => ()
  }
}

///|
fn Parser::clear_template_content_mode_for_node(
  self : Parser,
  template : @dom.Node,
) -> Unit {
  let mut index = 0
  while index < self.template_content_modes.length() {
    let (node, _) = self.template_content_modes[index]
    if physical_equal(node, template) {
      ignore(self.template_content_modes.remove(index))
    } else {
      index += 1
    }
  }
}