///| HTML block parsing (split from block_parser.mbt).

///| The seven start conditions from the CommonMark spec, plus custom elements

///| (Web Components), which this parser treats like the block-level tags of

///| condition 6.

///|
/// HTML block-level tag names (CommonMark Type 6)
let html_block_tags : Array[String] = [
  "address", "article", "aside", "base", "basefont", "blockquote", "body", "caption",
  "center", "col", "colgroup", "dd", "details", "dialog", "dir", "div", "dl", "dt",
  "fieldset", "figcaption", "figure", "footer", "form", "frame", "frameset", "h1",
  "h2", "h3", "h4", "h5", "h6", "head", "header", "hr", "html", "iframe", "legend",
  "li", "link", "main", "menu", "menuitem", "nav", "noframes", "ol", "optgroup",
  "option", "p", "param", "search", "section", "summary", "svg", "table", "tbody",
  "td", "tfoot", "th", "thead", "title", "tr", "track", "ul",
]

///|
/// Check if a tag name is a block-level HTML tag
/// Also recognizes custom elements (Web Components) as block tags
fn is_html_block_tag(tag : String) -> Bool {
  // Custom elements (Web Components) are always block-level
  if is_custom_element_tag(tag) {
    return true
  }
  for t in html_block_tags {
    if tag[:].equal_ignore_ascii_case(t[:]) {
      return true
    }
  }
  false
}

///|
/// Check if a tag name is a custom element (Web Component)
/// Custom elements must contain at least one hyphen and start with a letter
fn is_custom_element_tag(tag : String) -> Bool {
  if tag.is_empty() {
    return false
  }
  // Must start with a letter
  let first = tag.unsafe_get(0)
  if !((first >= 0x61 && first <= 0x7A) || (first >= 0x41 && first <= 0x5A)) {
    return false
  }
  // Must contain a hyphen
  for i in 0..` raw-text closer at or after `start`.

///|
/// SIMD finds sparse `<` candidates; scalar case-insensitive comparison only
/// runs at those positions. No substring is materialized while scanning.
fn find_html_raw_text_closer(text : String, start : Int, tag : String) -> Int? {
  let text_len = text.length()
  let needle = ""
  let needle_len = needle.length()
  guard start >= 0 && start <= text_len else { return None }
  let mut search_from = start
  while search_from + needle_len <= text_len {
    match find_exact_from(text, search_from, "<") {
      Some(candidate) => {
        guard candidate + needle_len <= text_len else { return None }
        let candidate_view = text[candidate:candidate + needle_len]
        if candidate_view.equal_ignore_ascii_case(needle[:]) {
          return Some(candidate)
        }
        search_from = candidate + 1
      }
      None => return None
    }
  }
  None
}

///|
/// Read the tag name starting at `start` (after `<` or ` Int {
  let len = text.length()
  let mut i = start
  guard i < len && is_ascii_letter_unit(text.unsafe_get(i)) else {
    return start
  }
  while i < len &&
        (is_ascii_alnum_unit(text.unsafe_get(i)) || text.unsafe_get(i) == '-') {
    i = i + 1
  }
  i
}

///|
/// Decide which HTML block start condition, if any, this line satisfies.
fn BlockParser::try_html_block_start(
  self : BlockParser,
  container : Node,
) -> Node? {
  guard self.peek_line(self.next_nonspace) == '<' else { return None }
  let line = self.line
  let start = self.next_nonspace
  let kind = html_block_kind(line, start, container)
  guard kind != HtmlBlockKind::None else { return None }
  let node = self.add_child(
    container,
    NodeKind::HtmlBlockNode,
    self.line_start + start,
  )
  node.html_kind = kind
  if kind == HtmlBlockKind::RawText {
    // A raw text block runs until its own closing tag, so remember which.
    node.info = raw_text_tag(line, start)
  }
  node.end = self.line_end
  Some(node)
}

///|
/// The raw text element name that opens a condition-1 block at `start`.
fn raw_text_tag(line : String, start : Int) -> String {
  let name_end = read_tag_name(line, start + 1)
  for raw in html_raw_text_tags {
    if line.unsafe_substring(start=start + 1, end=name_end)[:].equal_ignore_ascii_case(
        raw[:],
      ) {
      return raw
    }
  }
  ""
}

///|
fn html_block_kind(
  line : String,
  start : Int,
  container : Node,
) -> HtmlBlockKind {
  let len = line.length()
  // 2: comment, 3: processing instruction, 4: declaration, 5: CDATA
  if matches_at(line, start, "") is Some(_)
    HtmlBlockKind::ProcessingInstruction =>
      find_exact_end_from(line, from, "?>") is Some(_)
    HtmlBlockKind::Declaration =>
      find_exact_end_from(line, from, ">") is Some(_)
    HtmlBlockKind::Cdata => find_exact_end_from(line, from, "]]>") is Some(_)
    _ => false
  }
}