///|
/// Streaming HTML5 Tree Builder
/// Builds DOM tree incrementally from HTML chunks

///|
/// Streaming tree builder that processes HTML incrementally
pub(all) struct StreamingTreeBuilder {
  tokenizer : StreamingTokenizer
  mut insertion_mode : InsertionMode
  mut original_insertion_mode : InsertionMode?
  open_elements : Array[Element]
  active_formatting : Array[FormattingElement]
  mut foster_parenting : Bool
  mut frameset_ok : Bool
  mut head_element : Element?
  mut pending_table_chars : Array[Char]
  pending_text : StringBuilder
  pending_tokens : Array[Token]
  // Root element
  mut root : Element?
}

///|
pub fn StreamingTreeBuilder::new() -> StreamingTreeBuilder {
  {
    tokenizer: StreamingTokenizer::new(),
    insertion_mode: InsertionMode::Initial,
    original_insertion_mode: None,
    open_elements: [],
    active_formatting: [],
    foster_parenting: false,
    frameset_ok: true,
    head_element: None,
    pending_table_chars: [],
    pending_text: StringBuilder::new(),
    pending_tokens: [],
    root: None,
  }
}

///|
/// Feed a chunk of HTML
pub fn StreamingTreeBuilder::feed(
  self : StreamingTreeBuilder,
  chunk : String,
) -> Unit {
  self.tokenizer.feed(chunk)
  self.process_available_tokens()
  // Compact tokenizer buffer periodically to free memory
  self.tokenizer.compact()
}

///|
/// Signal end of input and get the result
pub fn StreamingTreeBuilder::finish(self : StreamingTreeBuilder) -> Element {
  self.tokenizer.finish()
  self.process_available_tokens()
  self.flush_pending_text()
  match self.root {
    Some(elem) => elem
    None => {
      let html = stb_create_element("html", {})
      let body = stb_create_element("body", {})
      html.children.push(Node::Element(body))
      html
    }
  }
}

///|
/// Process all available tokens from the tokenizer
fn StreamingTreeBuilder::process_available_tokens(
  self : StreamingTreeBuilder,
) -> Unit {
  while true {
    match self.tokenizer.next_token() {
      Some(Token::EOF) => break
      Some(token) => {
        self.process_token_single(token)
        // Process requeued tokens
        while !self.pending_tokens.is_empty() {
          let next = self.pending_tokens.remove(0)
          self.process_token_single(next)
        }
      }
      None => break // Need more data
    }
  }
}

///|
fn stb_create_element(tag : String, attrs : Map[String, String]) -> Element {
  let id = attrs.get("id")
  let classes = match attrs.get("class") {
    Some(class_str) => {
      let parts : Array[String] = []
      for part in class_str.split(" ") {
        let s = part.to_owned().trim()
        if !s.is_empty() {
          parts.push(s.to_owned())
        }
      }
      parts
    }
    None => []
  }
  let style = attrs.get("style")
  { tag, id, classes, style, attributes: attrs, children: [] }
}

///|
fn StreamingTreeBuilder::current_node(self : StreamingTreeBuilder) -> Element? {
  if self.open_elements.is_empty() {
    None
  } else {
    Some(self.open_elements[self.open_elements.length() - 1])
  }
}

///|
fn StreamingTreeBuilder::reprocess_token(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  self.pending_tokens.push(token)
}

///|
fn StreamingTreeBuilder::insert_element(
  self : StreamingTreeBuilder,
  element : Element,
) -> Unit {
  self.flush_pending_text()
  match self.current_node() {
    Some(current) => current.children.push(Node::Element(element))
    None => ()
  }
  self.open_elements.push(element)
}

///|
fn StreamingTreeBuilder::insert_character(
  self : StreamingTreeBuilder,
  c : Char,
) -> Unit {
  self.pending_text.write_char(c)
}

///|
fn StreamingTreeBuilder::insert_characters(
  self : StreamingTreeBuilder,
  s : String,
) -> Unit {
  self.pending_text.write_string(s)
}

///|
fn StreamingTreeBuilder::flush_pending_text(
  self : StreamingTreeBuilder,
) -> Unit {
  if self.pending_text.is_empty() {
    return
  }
  let raw_text = self.pending_text.to_string()
  self.pending_text.reset()
  let text = decode_html_entities(raw_text)
  match self.current_node() {
    Some(current) => {
      let len = current.children.length()
      if len > 0 {
        match current.children[len - 1] {
          Node::Text(existing) => {
            current.children[len - 1] = Node::Text(existing + text)
            return
          }
          _ => ()
        }
      }
      if stb_is_whitespace_only(text) {
        if current.children.length() == 0 && stb_is_plain_space_only(text) {
          current.children.push(Node::Text(" "))
        }
        return
      }
      current.children.push(Node::Text(text))
    }
    None => ()
  }
}

///|
fn stb_is_whitespace_only(s : String) -> Bool {
  for c in s {
    if !stb_is_whitespace(c) {
      return false
    }
  }
  true
}

///|
fn stb_is_plain_space_only(s : String) -> Bool {
  if s.is_empty() {
    return false
  }
  for c in s {
    if c != ' ' {
      return false
    }
  }
  true
}

///|
fn stb_is_whitespace(c : Char) -> Bool {
  c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\u000C'
}

///|
fn StreamingTreeBuilder::pop_until(
  self : StreamingTreeBuilder,
  tag : String,
) -> Unit {
  self.flush_pending_text()
  while !self.open_elements.is_empty() {
    let elem = self.open_elements.pop()
    match elem {
      Some(e) if e.tag == tag => break
      _ => ()
    }
  }
}

///|
fn StreamingTreeBuilder::has_element_in_scope(
  self : StreamingTreeBuilder,
  tag : String,
) -> Bool {
  for i = self.open_elements.length() - 1; i >= 0; i = i - 1 {
    let elem = self.open_elements[i]
    if elem.tag == tag {
      return true
    }
    if stb_is_scope_boundary(elem.tag) {
      return false
    }
  }
  false
}

///|
fn stb_is_scope_boundary(tag : String) -> Bool {
  match tag {
    "applet"
    | "caption"
    | "html"
    | "table"
    | "td"
    | "th"
    | "marquee"
    | "object"
    | "template" => true
    _ => false
  }
}

///|
fn StreamingTreeBuilder::generate_implied_end_tags(
  self : StreamingTreeBuilder,
) -> Unit {
  while !self.open_elements.is_empty() {
    match self.current_node() {
      Some(e) =>
        match e.tag {
          "dd"
          | "dt"
          | "li"
          | "optgroup"
          | "option"
          | "p"
          | "rb"
          | "rp"
          | "rt"
          | "rtc" => {
            let _ = self.open_elements.pop()
          }
          _ => break
        }
      None => break
    }
  }
}

///|
fn StreamingTreeBuilder::generate_implied_end_tags_except(
  self : StreamingTreeBuilder,
  except : String,
) -> Unit {
  while !self.open_elements.is_empty() {
    match self.current_node() {
      Some(e) if e.tag == except => break
      Some(e) =>
        match e.tag {
          "dd"
          | "dt"
          | "li"
          | "optgroup"
          | "option"
          | "p"
          | "rb"
          | "rp"
          | "rt"
          | "rtc" => {
            let _ = self.open_elements.pop()
          }
          _ => break
        }
      None => break
    }
  }
}

///|
fn stb_is_void_element(tag : String) -> Bool {
  match tag {
    "area"
    | "base"
    | "br"
    | "col"
    | "embed"
    | "hr"
    | "img"
    | "input"
    | "link"
    | "meta"
    | "param"
    | "source"
    | "track"
    | "wbr" => true
    _ => false
  }
}

///|
/// Process a single token
fn StreamingTreeBuilder::process_token_single(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match self.insertion_mode {
    InsertionMode::Initial => self.stb_handle_initial(token)
    InsertionMode::BeforeHtml => self.stb_handle_before_html(token)
    InsertionMode::BeforeHead => self.stb_handle_before_head(token)
    InsertionMode::InHead => self.stb_handle_in_head(token)
    InsertionMode::AfterHead => self.stb_handle_after_head(token)
    InsertionMode::InBody => self.stb_handle_in_body(token)
    InsertionMode::Text => self.stb_handle_text(token)
    InsertionMode::AfterBody => self.stb_handle_after_body(token)
    _ => self.stb_handle_in_body(token)
  }
}

///|
fn StreamingTreeBuilder::stb_handle_initial(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Doctype(_) => self.insertion_mode = InsertionMode::BeforeHtml
    Token::Character(c) if stb_is_whitespace(c) => ()
    Token::Characters(s) if stb_is_whitespace_only(s) => ()
    _ => {
      self.insertion_mode = InsertionMode::BeforeHtml
      self.reprocess_token(token)
    }
  }
}

///|
fn StreamingTreeBuilder::stb_handle_before_html(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Doctype(_) => ()
    Token::Character(c) if stb_is_whitespace(c) => ()
    Token::Characters(s) if stb_is_whitespace_only(s) => ()
    Token::StartTag(tag, attrs, _) if tag == "html" => {
      let elem = stb_create_element(tag, attrs)
      self.open_elements.push(elem)
      self.root = Some(elem)
      self.insertion_mode = InsertionMode::BeforeHead
    }
    _ => {
      let elem = stb_create_element("html", {})
      self.open_elements.push(elem)
      self.root = Some(elem)
      self.insertion_mode = InsertionMode::BeforeHead
      self.reprocess_token(token)
    }
  }
}

///|
fn StreamingTreeBuilder::stb_handle_before_head(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Character(c) if stb_is_whitespace(c) => ()
    Token::Characters(s) if stb_is_whitespace_only(s) => ()
    Token::StartTag(tag, attrs, _) if tag == "head" => {
      let elem = stb_create_element(tag, attrs)
      self.insert_element(elem)
      self.head_element = Some(elem)
      self.insertion_mode = InsertionMode::InHead
    }
    _ => {
      let elem = stb_create_element("head", {})
      self.insert_element(elem)
      self.head_element = Some(elem)
      self.insertion_mode = InsertionMode::InHead
      self.reprocess_token(token)
    }
  }
}

///|
fn StreamingTreeBuilder::stb_handle_in_head(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Character(c) if stb_is_whitespace(c) => self.insert_character(c)
    Token::Characters(s) if stb_is_whitespace_only(s) =>
      self.insert_characters(s)
    Token::StartTag(tag, attrs, _) =>
      match tag {
        "title" | "style" | "script" => {
          let elem = stb_create_element(tag, attrs)
          self.insert_element(elem)
          self.original_insertion_mode = Some(self.insertion_mode)
          self.insertion_mode = InsertionMode::Text
        }
        "base" | "basefont" | "bgsound" | "link" | "meta" => {
          let elem = stb_create_element(tag, attrs)
          self.insert_element(elem)
          let _ = self.open_elements.pop()
        }
        "head" => ()
        _ => {
          let _ = self.open_elements.pop()
          self.insertion_mode = InsertionMode::AfterHead
          self.reprocess_token(token)
        }
      }
    Token::EndTag(tag) =>
      match tag {
        "head" => {
          let _ = self.open_elements.pop()
          self.insertion_mode = InsertionMode::AfterHead
        }
        _ => ()
      }
    _ => {
      let _ = self.open_elements.pop()
      self.insertion_mode = InsertionMode::AfterHead
      self.reprocess_token(token)
    }
  }
}

///|
fn StreamingTreeBuilder::stb_handle_after_head(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Character(c) if stb_is_whitespace(c) => self.insert_character(c)
    Token::Characters(s) if stb_is_whitespace_only(s) =>
      self.insert_characters(s)
    Token::StartTag(tag, attrs, _) if tag == "body" => {
      let elem = stb_create_element(tag, attrs)
      self.insert_element(elem)
      self.insertion_mode = InsertionMode::InBody
    }
    _ => {
      let elem = stb_create_element("body", {})
      self.insert_element(elem)
      self.insertion_mode = InsertionMode::InBody
      self.reprocess_token(token)
    }
  }
}

///|
fn StreamingTreeBuilder::stb_handle_in_body(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Character(c) => self.insert_character(c)
    Token::Characters(s) => self.insert_characters(s)
    Token::Comment(_) => ()
    Token::Doctype(_) => ()
    Token::StartTag(tag, attrs, self_closing) =>
      // Simplified: just create elements
      if stb_is_void_element(tag) || self_closing {
        let elem = stb_create_element(tag, attrs)
        self.insert_element(elem)
        let _ = self.open_elements.pop()
      } else {
        let elem = stb_create_element(tag, attrs)
        self.insert_element(elem)
      }
    Token::EndTag(tag) => {
      self.flush_pending_text()
      match tag {
        // Special handling for p element (uses generate_implied_end_tags_except)
        "p" =>
          if self.has_element_in_scope("p") {
            self.generate_implied_end_tags_except("p")
            self.pop_until("p")
          }
        // Tags that need generate_implied_end_tags_except
        "li" =>
          if self.has_element_in_scope("li") {
            self.generate_implied_end_tags_except("li")
            self.pop_until("li")
          }
        "dd" | "dt" =>
          if self.has_element_in_scope(tag) {
            self.generate_implied_end_tags_except(tag)
            self.pop_until(tag)
          }
        // Body end tag changes mode
        "body" =>
          if self.has_element_in_scope("body") {
            self.insertion_mode = InsertionMode::AfterBody
          }
        // All other end tags
        _ =>
          if self.has_element_in_scope(tag) {
            self.generate_implied_end_tags()
            self.pop_until(tag)
          }
      }
    }
    Token::EOF => ()
  }
}

///|
fn StreamingTreeBuilder::stb_handle_text(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Character(c) => self.insert_character(c)
    Token::Characters(s) => self.insert_characters(s)
    Token::EOF | Token::EndTag(_) => {
      self.flush_pending_text()
      let _ = self.open_elements.pop()
      match self.original_insertion_mode {
        Some(mode) => self.insertion_mode = mode
        None => self.insertion_mode = InsertionMode::InBody
      }
    }
    _ => ()
  }
}

///|
fn StreamingTreeBuilder::stb_handle_after_body(
  self : StreamingTreeBuilder,
  token : Token,
) -> Unit {
  match token {
    Token::Character(c) if stb_is_whitespace(c) =>
      self.stb_handle_in_body(Token::Character(c))
    Token::Characters(s) if stb_is_whitespace_only(s) =>
      self.stb_handle_in_body(Token::Characters(s))
    Token::EndTag(tag) if tag == "html" =>
      self.insertion_mode = InsertionMode::AfterAfterBody
    Token::EOF => ()
    _ => {
      self.insertion_mode = InsertionMode::InBody
      self.reprocess_token(token)
    }
  }
}

///|
/// Parse HTML document using streaming
pub fn parse_document_streaming(html : String, chunk_size : Int) -> Document {
  let builder = StreamingTreeBuilder::new()
  let len = html.length()
  let mut offset = 0
  while offset < len {
    let end = if offset + chunk_size > len { len } else { offset + chunk_size }
    let chunk = html.unsafe_substring(start=offset, end~)
    builder.feed(chunk)
    offset = end
  }
  let root = builder.finish()
  {
    root,
    stylesheets: [],
    stylesheet_links: [],
    quirks_mode: !has_html_no_quirks_doctype(html),
  }
}