///|
fn Parser::has_open_html_select(self : Parser) -> Bool {
  self.has_open_html_element("select")
}

///|
fn Parser::in_select_insertion_mode(self : Parser) -> Bool {
  self.select_insertion_mode || self.has_open_html_select()
}

///|
fn Parser::reset_select_insertion_mode(self : Parser) -> Unit {
  self.select_insertion_mode = self.has_open_element("select")
}

///|
fn select_start_tag_reprocesses_in_body(name : StringView) -> Bool {
  match name {
    "caption"
    | "col"
    | "colgroup"
    | "input"
    | "table"
    | "tbody"
    | "td"
    | "textarea"
    | "tfoot"
    | "th"
    | "thead"
    | "tr" => true
    _ => false
  }
}

///|
fn Parser::close_select_for_reprocessed_start_tag(
  self : Parser,
  name : StringView,
  start : Int,
) -> Bool {
  if !self.in_select_insertion_mode() ||
    !select_start_tag_reprocesses_in_body(name) {
    return false
  }
  self.error_at("unexpected-start-tag-in-select", start)
  ignore(self.close_open_element("select"))
  self.reset_select_insertion_mode()
  true
}

///|
fn Parser::pop_until_select_in_insertion_mode(self : Parser) -> Unit {
  while self.stack.length() > 1 {
    let node = self.stack.pop().unwrap()
    if node.kind == Element && node.name == "select" {
      break
    }
  }
}

///|
fn Parser::close_open_select_item(self : Parser, name : String) -> Unit {
  self.close_current_element_if_name("option")
  if name == "optgroup" && self.in_select_insertion_mode() {
    self.close_current_element_if_name("optgroup")
  }
}

///|
fn Parser::handle_select_end_tag(
  self : Parser,
  name : String,
  tag_close_pos : Int,
) -> Bool {
  match name {
    "optgroup" =>
      if self.in_select_insertion_mode() {
        ignore(self.pop_current_element_if_name("option"))
        if !self.pop_current_element_if_name("optgroup") {
          self.error_at("unexpected-end-tag-in-select", tag_close_pos)
        }
        true
      } else {
        false
      }
    "option" =>
      if self.in_select_insertion_mode() {
        if !self.pop_current_element_if_name("option") {
          self.error_at("unexpected-end-tag-in-select", tag_close_pos)
        }
        true
      } else {
        false
      }
    "select" =>
      if self.in_select_insertion_mode() {
        self.pop_until_select_in_insertion_mode()
        self.reset_select_insertion_mode()
        true
      } else {
        false
      }
    _ if select_common_element_start_reports_in_select(name) &&
      self.in_select_insertion_mode() => {
      self.error_at("unexpected-end-tag-in-select", tag_close_pos)
      self.close_open_select_common_element(name)
      true
    }
    "menuitem" =>
      if self.in_select_insertion_mode() {
        self.error_at("unexpected-end-tag", tag_close_pos)
        true
      } else {
        false
      }
    _ if self.in_select_insertion_mode() &&
      select_formatting_end_reports_in_select(name) &&
      self.formatting_end_tag_is_outside_current_select(name) => {
      self.error_at("unexpected-end-tag-in-select", tag_close_pos)
      true
    }
    _ => false
  }
}

///|
fn select_common_element_start_reports_in_select(name : StringView) -> Bool {
  match name {
    "button" | "datalist" | "div" | "p" | "selectedcontent" | "span" => true
    _ => false
  }
}

///|
fn select_formatting_end_reports_in_select(name : StringView) -> Bool {
  match name {
    "a"
    | "b"
    | "big"
    | "code"
    | "em"
    | "font"
    | "i"
    | "nobr"
    | "s"
    | "small"
    | "strike"
    | "strong"
    | "tt"
    | "u" => true
    _ => false
  }
}

///|
fn select_ignored_start_reports_in_select(name : StringView) -> Bool {
  match name {
    "aside" => true
    _ => false
  }
}

///|
fn Parser::formatting_end_tag_is_outside_current_select(
  self : Parser,
  name : String,
) -> Bool {
  let target_index = self.last_stack_index_of(name)
  if target_index < 0 {
    return true
  }
  let select_index = self.last_stack_index_of("select")
  select_index >= 0 && target_index < select_index
}

///|
fn Parser::close_open_select_common_element(
  self : Parser,
  name : String,
) -> Unit {
  let select_index = self.last_stack_index_of("select")
  let target_index = self.last_stack_index_of(name)
  if target_index > select_index {
    while self.stack.length() > target_index {
      ignore(self.stack.pop())
    }
  }
}

///|
fn Parser::handle_select_special_start_tag(
  self : Parser,
  name : String,
  attrs : Map[String, String?],
  self_closing : Bool,
  start : Int,
  tag_close_pos : Int,
) -> Bool {
  if !self.in_select_insertion_mode() {
    return false
  }
  if name == "plaintext" {
    self.error_at("unexpected-start-tag-in-select", tag_close_pos)
    let node = self.node_with_origin(@dom.element(name, attrs~), start)
    self.current_node().append_child(node)
    self.consume_special_element_content(node, name)
    ignore(self.close_open_element("select"))
    self.reset_select_insertion_mode()
    return true
  }
  if name == "hr" {
    self.error_at("unexpected-start-tag-in-select", tag_close_pos)
    ignore(self.pop_current_element_if_name("option"))
    ignore(self.pop_current_element_if_name("optgroup"))
    self.insert_element_without_push("hr", attrs, start~)
    return true
  }
  if name == "br" || name == "img" {
    self.error_at("unexpected-start-tag-in-select", tag_close_pos)
    self.insert_element_without_push(name, attrs, start~)
    return true
  }
  if name == "menuitem" {
    self.error_at("unexpected-start-tag-in-select", tag_close_pos)
    self.insert_element_maybe_push(name, attrs, !self_closing, start~)
    return true
  }
  if select_common_element_start_reports_in_select(name) {
    self.error_at("unexpected-start-tag-in-select", tag_close_pos)
    self.insert_element_maybe_push(name, attrs, !self_closing, start~)
    return true
  }
  if select_ignored_start_reports_in_select(name) {
    self.error_at("unexpected-start-tag-in-select", tag_close_pos)
    return true
  }
  false
}