// 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.

///|
/// 13.2.5.72 Character reference state
fn Tokenizer::run_character_reference_state(self : Tokenizer) -> Token? {
  self.temp_buffer.reset()
  self.temp_buffer.write_char('&')
  match self.peek() {
    Some(c) if is_ascii_alphanumeric(c) => {
      self.state = NamedCharacterReference
      None
    }
    Some('#') => {
      let _ = self.consume()
      self.temp_buffer.write_char('#')
      self.state = NumericCharacterReference
      None
    }
    _ => {
      self.flush_temp_buffer()
      self.state = self.return_state
      None
    }
  }
}

///|
/// 13.2.5.73 Named character reference state
fn Tokenizer::run_named_character_reference_state(self : Tokenizer) -> Token? {
  // Try to find longest matching entity
  let matched = self.try_match_named_entity()
  match matched {
    Some((entity_name, codepoints)) => {
      // Check if we need special handling for attribute values
      let last_char = entity_name.to_array()[entity_name.to_array().length() - 1]
      let in_attribute = self.return_state == AttributeValueDoubleQuoted ||
        self.return_state == AttributeValueSingleQuoted ||
        self.return_state == AttributeValueUnquoted
      if in_attribute && last_char != ';' {
        // Check next input character
        match self.peek() {
          Some('=') => {
            // Don't consume as entity, flush as text
            self.flush_temp_buffer()
            self.state = self.return_state
            return None
          }
          Some(c) if is_ascii_alphanumeric(c) => {
            // Don't consume as entity, flush as text
            self.flush_temp_buffer()
            self.state = self.return_state
            return None
          }
          _ => ()
        }
      }
      // Emit error if entity doesn't end with semicolon
      if last_char != ';' {
        self.emit_error_at_next_position(
          MissingSemicolonAfterCharacterReference,
        )
      }
      // Clear temp buffer and emit codepoints
      self.temp_buffer.reset()
      for cp in codepoints {
        self.emit_char_to_current_context(Int::unsafe_to_char(cp))
      }
      self.state = self.return_state
      None
    }
    None => {
      // No entity found, flush the & and go to ambiguous state
      self.flush_temp_buffer()
      self.state = AmbiguousAmpersand
      None
    }
  }
}

///|
/// 13.2.5.74 Ambiguous ampersand state
fn Tokenizer::run_ambiguous_ampersand_state(self : Tokenizer) -> Token? {
  match self.consume() {
    Some(c) if is_ascii_alphanumeric(c) => {
      self.emit_char_to_current_context(c)
      None
    }
    Some(';') => {
      self.emit_error(UnknownNamedCharacterReference)
      self.reconsume()
      self.state = self.return_state
      None
    }
    Some(_) => {
      self.reconsume()
      self.state = self.return_state
      None
    }
    None => {
      self.state = self.return_state
      None
    }
  }
}

///|
/// 13.2.5.75 Numeric character reference state
fn Tokenizer::run_numeric_character_reference_state(self : Tokenizer) -> Token? {
  self.char_ref_code = 0
  match self.peek() {
    Some('x') | Some('X') => {
      let c = self.consume().unwrap()
      self.temp_buffer.write_char(c)
      self.state = HexadecimalCharacterReferenceStart
      None
    }
    _ => {
      self.state = DecimalCharacterReferenceStart
      None
    }
  }
}

///|
/// 13.2.5.76 Hexadecimal character reference start state
fn Tokenizer::run_hexadecimal_character_reference_start_state(
  self : Tokenizer,
) -> Token? {
  match self.peek() {
    Some(c) if is_ascii_hex_digit(c) => {
      self.state = HexadecimalCharacterReference
      None
    }
    _ => {
      self.emit_error_at_next_position(
        AbsenceOfDigitsInNumericCharacterReference,
      )
      self.flush_temp_buffer()
      self.state = self.return_state
      None
    }
  }
}

///|
/// 13.2.5.77 Decimal character reference start state
fn Tokenizer::run_decimal_character_reference_start_state(
  self : Tokenizer,
) -> Token? {
  match self.peek() {
    Some(c) if is_ascii_digit(c) => {
      self.state = DecimalCharacterReference
      None
    }
    _ => {
      self.emit_error_at_next_position(
        AbsenceOfDigitsInNumericCharacterReference,
      )
      self.flush_temp_buffer()
      self.state = self.return_state
      None
    }
  }
}

///|
/// 13.2.5.78 Hexadecimal character reference state
fn Tokenizer::run_hexadecimal_character_reference_state(
  self : Tokenizer,
) -> Token? {
  match self.consume() {
    Some(c) if is_ascii_digit(c) => {
      // Cap at a value larger than max Unicode to avoid overflow
      if self.char_ref_code <= 0x10FFFF {
        self.char_ref_code = self.char_ref_code * 16 + (c.to_int() - 0x30)
      }
      None
    }
    Some(c) if is_ascii_upper_hex_digit(c) => {
      if self.char_ref_code <= 0x10FFFF {
        self.char_ref_code = self.char_ref_code * 16 + (c.to_int() - 0x37)
      }
      None
    }
    Some(c) if is_ascii_lower_hex_digit(c) => {
      if self.char_ref_code <= 0x10FFFF {
        self.char_ref_code = self.char_ref_code * 16 + (c.to_int() - 0x57)
      }
      None
    }
    Some(';') => {
      self.state = NumericCharacterReferenceEnd
      None
    }
    Some(_) => {
      self.emit_error(MissingSemicolonAfterCharacterReference)
      self.reconsume()
      self.state = NumericCharacterReferenceEnd
      None
    }
    None => {
      self.emit_error(MissingSemicolonAfterCharacterReference)
      self.state = NumericCharacterReferenceEnd
      None
    }
  }
}

///|
/// 13.2.5.79 Decimal character reference state
fn Tokenizer::run_decimal_character_reference_state(self : Tokenizer) -> Token? {
  match self.consume() {
    Some(c) if is_ascii_digit(c) => {
      // Cap at a value larger than max Unicode (0x10FFFF) to avoid overflow
      // while still detecting out-of-range values
      if self.char_ref_code <= 0x10FFFF {
        self.char_ref_code = self.char_ref_code * 10 + (c.to_int() - 0x30)
      }
      None
    }
    Some(';') => {
      self.state = NumericCharacterReferenceEnd
      None
    }
    Some(_) => {
      self.emit_error(MissingSemicolonAfterCharacterReference)
      self.reconsume()
      self.state = NumericCharacterReferenceEnd
      None
    }
    None => {
      self.emit_error(MissingSemicolonAfterCharacterReference)
      self.state = NumericCharacterReferenceEnd
      None
    }
  }
}

///|
/// 13.2.5.80 Numeric character reference end state
fn Tokenizer::run_numeric_character_reference_end_state(
  self : Tokenizer,
) -> Token? {
  let code = self.char_ref_code
  // Check for errors and apply replacements
  let final_char = if code == 0 {
    self.emit_error_at_next_position(NullCharacterReference)
    '\u{FFFD}'
  } else if code > 0x10FFFF {
    self.emit_error_at_next_position(CharacterReferenceOutsideUnicodeRange)
    '\u{FFFD}'
  } else if code >= 0xD800 && code <= 0xDFFF {
    self.emit_error_at_next_position(SurrogateCharacterReference)
    '\u{FFFD}'
  } else if is_noncharacter(code) {
    self.emit_error_at_next_position(NoncharacterCharacterReference)
    Int::unsafe_to_char(code)
  } else if code == 0x0D ||
    (is_control_char(code) && !is_ascii_whitespace_code(code)) {
    self.emit_error_at_next_position(ControlCharacterReference)
    // Apply Windows-1252 remapping for 0x80-0x9F
    remap_windows_1252(code)
  } else {
    Int::unsafe_to_char(code)
  }
  self.temp_buffer.reset()
  self.emit_char_to_current_context(final_char)
  self.state = self.return_state
  None
}

///|
/// Helper: flush temp buffer to current context
fn Tokenizer::flush_temp_buffer(self : Tokenizer) -> Unit {
  let buf = self.temp_buffer.to_string()
  for c in buf {
    self.emit_char_to_current_context(c)
  }
}

///|
/// Helper: emit character to current context (attribute value or token stream)
/// Normalizes CR to LF per WHATWG spec
fn Tokenizer::emit_char_to_current_context(self : Tokenizer, c : Char) -> Unit {
  // Normalize CR to LF (applies to character references too)
  let ch = if c == '\r' { '\n' } else { c }
  match self.return_state {
    AttributeValueDoubleQuoted
    | AttributeValueSingleQuoted
    | AttributeValueUnquoted => self.current_attr_value.write_char(ch)
    _ => self.pending_tokens.push(Character(ch))
  }
}

///|
/// Check if code point is a noncharacter
fn is_noncharacter(code : Int) -> Bool {
  (code >= 0xFDD0 && code <= 0xFDEF) ||
  (code & 0xFFFF) == 0xFFFE ||
  (code & 0xFFFF) == 0xFFFF
}

///|
/// Check if code point is a control character
fn is_control_char(code : Int) -> Bool {
  (code >= 0x00 && code <= 0x1F) || (code >= 0x7F && code <= 0x9F)
}

///|
/// Check if code point is ASCII whitespace
fn is_ascii_whitespace_code(code : Int) -> Bool {
  code == 0x09 || code == 0x0A || code == 0x0C || code == 0x0D || code == 0x20
}

///|
/// Windows-1252 remapping for control characters 0x80-0x9F
fn remap_windows_1252(code : Int) -> Char {
  if code >= 0x80 && code <= 0x9F {
    let table : FixedArray[Int] = [
      0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030,
      0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, 0x0090, 0x2018, 0x2019, 0x201C,
      0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D,
      0x017E, 0x0178,
    ]
    Int::unsafe_to_char(table[code - 0x80])
  } else {
    Int::unsafe_to_char(code)
  }
}

///|
/// Try to match a named character reference
/// Returns (entity_name, codepoints) if found
fn Tokenizer::try_match_named_entity(self : Tokenizer) -> (String, Array[Int])? {
  // Build up the entity name character by character
  let name_buf = StringBuilder()
  let mut best_match : (String, Array[Int])? = None
  let mut consumed = 0
  while true {
    match self.peek_at(consumed) {
      Some(c) if is_ascii_alphanumeric(c) || c == ';' => {
        name_buf.write_char(c)
        consumed += 1
        let candidate = name_buf.to_string()
        // Check if this is a valid entity
        match lookup_entity(candidate) {
          Some(cps) => best_match = Some((candidate, cps))
          None => ()
        }
        // Stop at semicolon
        if c == ';' {
          break
        }
      }
      _ => break
    }
    // Safety limit
    if consumed > 32 {
      break
    }
  }
  // Consume the matched characters
  match best_match {
    Some((name, _)) => {
      let len = name.to_array().length()
      for _ in 0.. ()
  }
  best_match
}

///|
/// 13.2.5.69 CDATA section state
fn Tokenizer::run_cdata_section_state(self : Tokenizer) -> Token? {
  match self.consume() {
    Some(']') => {
      self.state = CDATASectionBracket
      None
    }
    Some(c) => Some(Character(c))
    None => {
      self.emit_error(EofInCdata)
      Some(EOF)
    }
  }
}

///|
/// 13.2.5.70 CDATA section bracket state
fn Tokenizer::run_cdata_section_bracket_state(self : Tokenizer) -> Token? {
  match self.consume() {
    Some(']') => {
      self.state = CDATASectionEnd
      None
    }
    Some(_) => {
      self.reconsume()
      self.state = CDATASection
      Some(Character(']'))
    }
    None => {
      self.reconsume()
      self.state = CDATASection
      Some(Character(']'))
    }
  }
}

///|
/// 13.2.5.71 CDATA section end state
fn Tokenizer::run_cdata_section_end_state(self : Tokenizer) -> Token? {
  match self.consume() {
    Some(']') => Some(Character(']'))
    Some('>') => {
      self.state = Data
      None
    }
    Some(_) => {
      self.reconsume()
      self.state = CDATASection
      self.pending_tokens.push(Character(']'))
      Some(Character(']'))
    }
    None => {
      self.reconsume()
      self.state = CDATASection
      self.pending_tokens.push(Character(']'))
      Some(Character(']'))
    }
  }
}