// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// ============================================================================
// Active formatting elements list (WHATWG 13.2.4.3)
// ============================================================================

///|
/// Check if a tag name is a formatting element (adoption agency applies)
pub fn is_formatting_element(name : String) -> Bool {
  match name {
    "a"
    | "b"
    | "big"
    | "code"
    | "em"
    | "font"
    | "i"
    | "nobr"
    | "s"
    | "small"
    | "strike"
    | "strong"
    | "tt"
    | "u" => true
    _ => false
  }
}

///|
/// Push a formatting marker onto the list
fn TreeBuilder::push_formatting_marker(self : TreeBuilder) -> Unit {
  self.active_formatting.push(Marker)
}

///|
/// Push an element onto the active formatting list
fn TreeBuilder::push_active_formatting(
  self : TreeBuilder,
  node_id : Int,
  tag_name : String,
  attrs : Array[Attribute],
) -> Unit {
  // Check for existing elements with same tag and attributes (Noah's Ark)
  let mut count = 0
  let mut oldest_idx = -1
  for i = self.active_formatting.length() - 1; i >= 0; i = i - 1 {
    match self.active_formatting[i] {
      Marker => break
      Element(tag_name=entry_tag, attributes~, ..) =>
        if entry_tag == tag_name && attributes_match(attributes, attrs) {
          count += 1
          oldest_idx = i
        }
    }
  }
  // Remove oldest if we already have 3
  if count >= 3 && oldest_idx >= 0 {
    let _ = self.active_formatting.remove(oldest_idx)
  }
  let attributes : Array[(String, String)] = attrs.map(fn(a) {
    (a.name, a.value)
  })
  self.active_formatting.push(Element(node_id~, tag_name~, attributes~))
}

///|
/// Check if two attribute arrays match
fn attributes_match(a : Array[(String, String)], b : Array[Attribute]) -> Bool {
  if a.length() != b.length() {
    return false
  }
  for attr in b {
    let found = a
      .iter()
      .any(fn(pair) { pair.0 == attr.name && pair.1 == attr.value })
    if !found {
      return false
    }
  }
  true
}

///|
/// Reconstruct the active formatting elements (WHATWG 13.2.4.3)
fn TreeBuilder::reconstruct_active_formatting(self : TreeBuilder) -> Unit {
  if self.active_formatting.is_empty() {
    return
  }
  // Check if last entry is a marker or in stack
  let last = self.active_formatting[self.active_formatting.length() - 1]
  match last {
    Marker => return
    Element(node_id~, ..) => if self.is_in_open_elements(node_id) { return }
  }
  // Reconstruct backwards
  let mut idx = self.active_formatting.length() - 1
  // Rewind
  while idx > 0 {
    idx -= 1
    match self.active_formatting[idx] {
      Marker => {
        idx += 1
        break
      }
      Element(node_id~, ..) =>
        if self.is_in_open_elements(node_id) {
          idx += 1
          break
        }
    }
  }
  // Advance and create elements
  while idx < self.active_formatting.length() {
    match self.active_formatting[idx] {
      Marker => ()
      Element(tag_name~, attributes~, ..) => {
        // Create new element
        let attrs : Array[Attribute] = attributes.map(fn(pair) {
          { name: pair.0, value: pair.1, }
        })
        let new_id = self.insert_html_element(tag_name, attrs)
        // Replace entry in list
        self.active_formatting[idx] = Element(
          node_id=new_id,
          tag_name~,
          attributes~,
        )
      }
    }
    idx += 1
  }
}

///|
/// Clear active formatting elements to the last marker
fn TreeBuilder::clear_active_formatting_to_marker(self : TreeBuilder) -> Unit {
  while self.active_formatting.length() > 0 {
    let entry = self.active_formatting.pop()
    match entry {
      Some(Marker) => break
      _ => ()
    }
  }
}

///|
/// Find a formatting element by tag name
fn TreeBuilder::find_formatting_element(
  self : TreeBuilder,
  tag_name : String,
) -> FormattingEntry? {
  for i = self.active_formatting.length() - 1; i >= 0; i = i - 1 {
    match self.active_formatting[i] {
      Marker => return None
      Element(tag_name=entry_tag, ..) as entry =>
        if entry_tag == tag_name {
          return Some(entry)
        }
    }
  }
  None
}

///|
/// Remove an element from the active formatting list by node id
fn TreeBuilder::remove_from_active_formatting(
  self : TreeBuilder,
  target_id : Int,
) -> Unit {
  for i = self.active_formatting.length() - 1; i >= 0; i = i - 1 {
    match self.active_formatting[i] {
      Element(node_id=entry_id, ..) if entry_id == target_id => {
        let _ = self.active_formatting.remove(i)
        return
      }
      _ => ()
    }
  }
}

///|
/// Check if a node is in the stack of open elements
fn TreeBuilder::is_in_open_elements(self : TreeBuilder, node_id : Int) -> Bool {
  for entry in self.open_elements {
    if entry.node_id == node_id {
      return true
    }
  }
  false
}

///|
/// Get the formatting element entry at an index
fn TreeBuilder::get_formatting_entry(
  self : TreeBuilder,
  idx : Int,
) -> FormattingEntry? {
  if idx >= 0 && idx < self.active_formatting.length() {
    Some(self.active_formatting[idx])
  } else {
    None
  }
}

///|
/// Find formatting element index in list
fn TreeBuilder::find_formatting_element_index(
  self : TreeBuilder,
  tag_name : String,
) -> Int {
  for i = self.active_formatting.length() - 1; i >= 0; i = i - 1 {
    match self.active_formatting[i] {
      Marker => return -1
      Element(tag_name=entry_tag, ..) => if entry_tag == tag_name { return i }
    }
  }
  -1
}

///|
/// Find the furthest block from the formatting element
fn TreeBuilder::find_furthest_block(
  self : TreeBuilder,
  formatting_idx : Int,
) -> Int {
  // Find the formatting element's position in the stack
  let formatting_entry = self.active_formatting[formatting_idx]
  let formatting_node_id = match formatting_entry {
    Element(node_id~, ..) => node_id
    Marker => return -1
  }
  // Find stack position
  let mut stack_idx = -1
  for i, entry in self.open_elements {
    if entry.node_id == formatting_node_id {
      stack_idx = i
      break
    }
  }
  if stack_idx == -1 {
    return -1
  }
  // Search for furthest block
  for i = stack_idx + 1; i < self.open_elements.length(); i = i + 1 {
    let entry = self.open_elements[i]
    // Check if element is "special" - namespace-aware per WHATWG spec
    let is_special = match entry.ns {
      HTML => is_special_element(entry.tag_name)
      SVG =>
        // Only foreignObject, desc, title are special in SVG namespace
        entry.tag_name == "foreignObject" ||
        entry.tag_name == "desc" ||
        entry.tag_name == "title"
      MathML =>
        // Only these are special in MathML namespace
        entry.tag_name == "mi" ||
        entry.tag_name == "mo" ||
        entry.tag_name == "mn" ||
        entry.tag_name == "ms" ||
        entry.tag_name == "mtext" ||
        entry.tag_name == "annotation-xml"
    }
    if is_special {
      // Verify this element is actually a DOM descendant of the formatting element
      // This handles cases where adoption agency left the stack out of sync with DOM
      if self.is_dom_descendant(entry.node_id, formatting_node_id) {
        return i
      }
    }
  }
  -1
}

///|
/// Check if node_id is a DOM descendant of ancestor_id
fn TreeBuilder::is_dom_descendant(
  self : TreeBuilder,
  node_id : Int,
  ancestor_id : Int,
) -> Bool {
  let mut current = node_id
  while true {
    let parent = self.document.get_parent(current)
    if parent == NO_NODE {
      return false
    }
    if parent == ancestor_id {
      return true
    }
    current = parent
  }
  false
}