///|
/// A source location decoded from a `#:` PO reference comment.
pub(all) struct SourceReference {
  path : String
  line : Int?
  column : Int?
} derive(Debug, Eq)

///|
/// Aggregate status counts for a parsed PO/POT document.
pub(all) struct PoStatistics {
  headers : Int
  messages : Int
  active : Int
  translated : Int
  untranslated : Int
  fuzzy : Int
  obsolete : Int
  singular : Int
  plural : Int
  contextual : Int
} derive(Debug, Eq)

///|
fn parse_reference_number(text : String) -> Int? {
  if text == "" {
    return None
  }
  let mut value = 0
  for c in text {
    guard c is ('0'..='9') else { return None }
    let digit = c.to_int() - '0'.to_int()
    if value > 214748364 || (value == 214748364 && digit > 7) {
      return None
    }
    value = value * 10 + digit
  }
  Some(value)
}

///|
fn parse_source_reference(token : String) -> SourceReference {
  match token.rev_split_once(":") {
    Some((before_last, last)) =>
      match parse_reference_number(last.to_owned()) {
        Some(last_number) => {
          let remaining = before_last.to_owned()
          match remaining.rev_split_once(":") {
            Some((path, possible_line)) =>
              match parse_reference_number(possible_line.to_owned()) {
                Some(line) =>
                  {
                    path: path.to_owned(),
                    line: Some(line),
                    column: Some(last_number),
                  }
                None =>
                  { path: remaining, line: Some(last_number), column: None }
              }
            None => { path: remaining, line: Some(last_number), column: None }
          }
        }
        None => { path: token, line: None, column: None }
      }
    None => { path: token, line: None, column: None }
  }
}

///|
/// Return unique comma-separated flags in first-seen order.
pub fn PoEntry::flags(self : PoEntry) -> Array[String] {
  let result : Array[String] = []
  for comment in self.comments {
    if comment.kind == Flag {
      for raw_flag in comment.text.split(",") {
        add_unique_string(result, raw_flag.trim().to_owned())
      }
    }
  }
  result
}

///|
/// Decode whitespace-separated `#:` source reference tokens.
///
/// `path`, `path:line`, and `path:line:column` are recognized. GNU PO permits
/// tool-specific quoting for paths containing whitespace; this helper keeps
/// those advanced spellings as separate raw tokens rather than guessing.
pub fn PoEntry::source_references(self : PoEntry) -> Array[SourceReference] {
  let result : Array[SourceReference] = []
  for comment in self.comments {
    if comment.kind == Reference {
      let normalized = comment.text.replace_all(old="\t", new=" ")
      for raw_token in normalized.split(" ") {
        let token = raw_token.to_owned()
        if token != "" {
          let reference = parse_source_reference(token)
          if !result.contains(reference) {
            result.push(reference)
          }
        }
      }
    }
  }
  result
}

///|
/// Find a context-aware entry. Obsolete entries are ignored by default.
pub fn PoFile::find_entry(
  self : PoFile,
  msgid : String,
  context? : String,
  include_obsolete? : Bool = false,
) -> PoEntry? {
  for entry in self.entries {
    if entry.msgid == msgid &&
      entry.context == context &&
      (include_obsolete || !entry.obsolete) {
      return Some(entry)
    }
  }
  None
}

///|
/// Compute catalog status counts in one pass.
pub fn PoFile::statistics(self : PoFile) -> PoStatistics {
  let mut headers = 0
  let mut messages = 0
  let mut active = 0
  let mut translated = 0
  let mut untranslated = 0
  let mut fuzzy = 0
  let mut obsolete = 0
  let mut singular = 0
  let mut plural = 0
  let mut contextual = 0
  for entry in self.entries {
    if entry.is_header() {
      headers += 1
      continue
    }
    messages += 1
    if entry.obsolete {
      obsolete += 1
    } else {
      active += 1
    }
    if entry.is_fuzzy() {
      fuzzy += 1
    }
    if all_translations_empty(entry) {
      untranslated += 1
    } else {
      translated += 1
    }
    if entry.msgid_plural is Some(_) {
      plural += 1
    } else {
      singular += 1
    }
    if entry.context is Some(_) {
      contextual += 1
    }
  }
  {
    headers,
    messages,
    active,
    translated,
    untranslated,
    fuzzy,
    obsolete,
    singular,
    plural,
    contextual,
  }
}