///|
/// Errors raised while parsing a UTF-8 Chinese dictionary text resource.
///
/// Line numbers are one-based. The accepted format is
/// `word frequency [tag]`; an optional tag is parsed but not retained.
pub(all) suberror ChineseDictionaryResourceError {
  InvalidFieldCount(Int, Int)
  InvalidFrequency(Int, String)
  InvalidWord(Int, String)
  DuplicateWord(Int, String)
  FrequencyOverflow(Int)
  LexiconError(ChineseLexiconError)
} derive(Eq, @debug.Debug)

///|
pub impl Show for ChineseDictionaryResourceError with fn output(self, logger) {
  match self {
    InvalidFieldCount(line, count) =>
      logger.write_string(
        "Chinese dictionary line \{line} must contain 2 or 3 fields, found \{count}",
      )
    InvalidFrequency(line, value) =>
      logger.write_string(
        "Chinese dictionary line \{line} has invalid positive frequency: \{value}",
      )
    InvalidWord(line, word) =>
      logger.write_string(
        "Chinese dictionary line \{line} has a non-Han word: \{word}",
      )
    DuplicateWord(line, word) =>
      logger.write_string(
        "Chinese dictionary line \{line} repeats word: \{word}",
      )
    FrequencyOverflow(line) =>
      logger.write_string(
        "Chinese dictionary total frequency overflows at line \{line}",
      )
    LexiconError(error) =>
      logger.write_string(
        "Chinese dictionary passed parsing but failed lexicon construction: \{error}",
      )
  }
}

///|
/// Splits one dictionary line on Unicode whitespace.
///
/// A UTF-8 BOM is accepted only before the first field on line one. Blank
/// lines and lines whose first non-whitespace character is `#` return no
/// fields.
fn dictionary_line_fields(line : String, line_number : Int) -> Array[String] {
  let characters = line.to_array()
  let fields : Array[String] = []
  let mut cursor = 0
  if line_number == 1 &&
    characters.length() > 0 &&
    characters[0].to_int() == 0xFEFF {
    cursor = 1
  }
  while cursor < characters.length() && characters[cursor].is_whitespace() {
    cursor += 1
  }
  if cursor >= characters.length() || characters[cursor] == '#' {
    return fields
  }
  while cursor < characters.length() {
    while cursor < characters.length() && characters[cursor].is_whitespace() {
      cursor += 1
    }
    if cursor >= characters.length() {
      break
    }
    let start = cursor
    while cursor < characters.length() && !characters[cursor].is_whitespace() {
      cursor += 1
    }
    fields.push(String::from_array(characters[start:cursor]))
  }
  fields
}

///|
/// Parses one strictly positive base-10 Int without accepting signs or
/// overflowing MoonBit's 32-bit Int.
fn parse_dictionary_frequency(value : String) -> Int? {
  let characters = value.to_array()
  if characters.length() == 0 {
    return None
  }
  let mut result = 0
  for character in characters {
    let code = character.to_int()
    if code < 0x30 || code > 0x39 {
      return None
    }
    let digit = code - 0x30
    if result > (0x7FFFFFFF - digit) / 10 {
      return None
    }
    result = result * 10 + digit
  }
  if result > 0 {
    Some(result)
  } else {
    None
  }
}

///|
/// Parses a caller-provided UTF-8 dictionary resource into an immutable Trie.
///
/// File-system access and caching stay outside the analysis package so the
/// same parser works for native, JavaScript, WASM, and custom Directory data.
pub fn ChineseLexicon::from_dictionary_text(
  text : String,
) -> ChineseLexicon raise ChineseDictionaryResourceError {
  let builder = ChineseLexiconBuilder::new()
  let mut line_number = 0
  for line in text.split("\n") {
    line_number += 1
    let fields = dictionary_line_fields(line.to_owned(), line_number)
    if fields.length() == 0 {
      continue
    }
    guard fields.length() == 2 || fields.length() == 3 else {
      raise ChineseDictionaryResourceError::InvalidFieldCount(
        line_number,
        fields.length(),
      )
    }
    let word = fields[0]
    for character in word {
      guard is_han_character(character) else {
        raise ChineseDictionaryResourceError::InvalidWord(line_number, word)
      }
    }
    let frequency = match parse_dictionary_frequency(fields[1]) {
      Some(value) => value
      None =>
        raise ChineseDictionaryResourceError::InvalidFrequency(
          line_number,
          fields[1],
        )
    }
    builder.add(word, frequency, true) catch {
      ChineseLexiconError::DuplicateWord(_) =>
        raise ChineseDictionaryResourceError::DuplicateWord(line_number, word)
      ChineseLexiconError::FrequencyOverflow =>
        raise ChineseDictionaryResourceError::FrequencyOverflow(line_number)
      error => raise ChineseDictionaryResourceError::LexiconError(error)
    }
  }
  builder.finish()
}