// Internationalization support for moon-humanize.
//
// This module provides locale activation (mirroring python-humanize's
// `humanize.activate`/`deactivate`), message translation helpers, plural
// selection, and thousands/decimal separator lookup. The actual translated
// strings live in `i18n_data.mbt`, which is auto-generated from the project's
// `src/humanize/locale/*.po` files by `scripts/po2mbt`.

/// A supported humanize locale.
///
/// The variant name is the raw locale directory name under
/// `src/humanize/locale/` (e.g. `zh_CN`, `pt_BR`, `fr_FR`). This mirrors
/// python-humanize, where activation is performed by locale directory name.
pub(all) enum Locale {
  Ar
  BnBD
  CaES
  DaDK
  DeDE
  ElGR
  Eo
  EsES
  Eu
  FaIR
  FiFI
  FrFR
  HeIL
  HuHU
  IdID
  ItIT
  JaJP
  KoKR
  Lv
  Nb
  NlNL
  PlPL
  PtBR
  PtPT
  RuRU
  SiLK
  SkSK
  SlSI
  SvSE
  Tlh
  TrTR
  UkUA
  Uz
  ViVN
  ZhCN
  ZhHK
  En
  Zh
}

/// Parse a raw locale identifier (e.g. `"zh_CN"`, `"en_GB"`, `"fr_FR"`) into a
/// `Locale`. Returns `None` if the locale is unknown or an English variant
/// (English locales use no translation, matching python-humanize behaviour).
pub fn Locale::from_string(locale : String) -> Option[Locale] {
  // English and unknown locales fall through to no translation. The bare `en`
  // locale is activated explicitly (it carries its own English catalog).
  if locale.has_prefix("en_") {
    return None
  }
  match locale {
    "ar" => Some(Ar)
    "bn_BD" => Some(BnBD)
    "ca_ES" => Some(CaES)
    "da_DK" => Some(DaDK)
    "de_DE" => Some(DeDE)
    "el_GR" => Some(ElGR)
    "eo" => Some(Eo)
    "es_ES" => Some(EsES)
    "eu" => Some(Eu)
    "fa_IR" => Some(FaIR)
    "fi_FI" => Some(FiFI)
    "fr_FR" => Some(FrFR)
    "he_IL" => Some(HeIL)
    "hu_HU" => Some(HuHU)
    "id_ID" => Some(IdID)
    "it_IT" => Some(ItIT)
    "ja_JP" => Some(JaJP)
    "ko_KR" => Some(KoKR)
    "lv" => Some(Lv)
    "nb" => Some(Nb)
    "nl_NL" => Some(NlNL)
    "pl_PL" => Some(PlPL)
    "pt_BR" => Some(PtBR)
    "pt_PT" => Some(PtPT)
    "ru_RU" => Some(RuRU)
    "si_LK" => Some(SiLK)
    "sk_SK" => Some(SkSK)
    "sl_SI" => Some(SlSI)
    "sv_SE" => Some(SvSE)
    "tlh" => Some(Tlh)
    "tr_TR" => Some(TrTR)
    "uk_UA" => Some(UkUA)
    "uz" => Some(Uz)
    "vi_VN" => Some(ViVN)
    "zh_CN" => Some(ZhCN)
    "zh_HK" => Some(ZhHK)
    "en" => Some(En)
    "zh" => Some(Zh)
    _ => None
  }
}

/// The locale directory name for this `Locale` (used to look up the right
/// translation table in `i18n_data.mbt`).
pub fn Locale::to_dir(self : Locale) -> String {
  match self {
    Ar => "ar"
    BnBD => "bn_BD"
    CaES => "ca_ES"
    DaDK => "da_DK"
    DeDE => "de_DE"
    ElGR => "el_GR"
    Eo => "eo"
    EsES => "es_ES"
    Eu => "eu"
    FaIR => "fa_IR"
    FiFI => "fi_FI"
    FrFR => "fr_FR"
    HeIL => "he_IL"
    HuHU => "hu_HU"
    IdID => "id_ID"
    ItIT => "it_IT"
    JaJP => "ja_JP"
    KoKR => "ko_KR"
    Lv => "lv"
    Nb => "nb"
    NlNL => "nl_NL"
    PlPL => "pl_PL"
    PtBR => "pt_BR"
    PtPT => "pt_PT"
    RuRU => "ru_RU"
    SiLK => "si_LK"
    SkSK => "sk_SK"
    SlSI => "sl_SI"
    SvSE => "sv_SE"
    Tlh => "tlh"
    TrTR => "tr_TR"
    UkUA => "uk_UA"
    Uz => "uz"
    ViVN => "vi_VN"
    ZhCN => "zh_CN"
    ZhHK => "zh_HK"
    En => "en"
    Zh => "zh"
  }
}

// ---------------------------------------------------------------------------
// Current locale state
// ---------------------------------------------------------------------------

/// The active locale translation, or `None` for no translation (English).
///
/// Mirrors python-humanize's module-level `_CURRENT.locale` / `deactivate()`.
// TODO(T7): wire this to the shared humanizer state when the humanizer is
// connected. For now it defaults to no translation (English output).
priv struct LocaleState {
  mut active : Option[Locale]
}

let locale_state : LocaleState = { active: None }

/// Activate a locale by raw identifier.
///
/// `None` or any `en*` locale disables translation (equivalent to
/// `deactivate()`), matching python-humanize's `activate(None)`. Returns the
/// resulting active locale (`None` means no translation).
pub fn activate(locale : String) -> Option[Locale] {
  let loc = Locale::from_string(locale)
  locale_state.active = loc
  loc
}

/// Deactivate internationalisation (revert to English / no translation).
pub fn deactivate() -> Unit {
  locale_state.active = None
}

/// Return the currently active locale, or `None` for no translation.
pub fn current_locale() -> Option[Locale] {
  locale_state.active
}

/// Return the current translation handle (the active `Locale`), or `None`
/// for no translation (English). Mirrors python-humanize's
/// `humanize.i18n.get_translation()` which returns the active locale's
/// `NullTranslations` object (or a fresh `NullTranslations()` when none is set).
pub fn get_translation() -> Option[Locale] {
  current_locale()
}

// ---------------------------------------------------------------------------
// Translation helpers
// ---------------------------------------------------------------------------

/// Translate `message` for the active locale. Falls back to the original
/// English `message` when no translation is active or none is found.
pub fn gettext(message : String) -> String {
  match current_locale() {
    None => message
    Some(loc) => lookup_message(loc, message, message)
  }
}

/// Translate `message` with a context (`msgctxt`), allowing duplicate source
/// strings to map to different translations. Mirrors python-humanize's
/// `pgettext`.
pub fn pgettext(msgctxt : String, message : String) -> String {
  match current_locale() {
    None => message
    Some(loc) => lookup_context(loc, msgctxt, message, message)
  }
}

/// Select the correct plural form for `num` given the active locale's plural
/// rules, returning the translated plural string. Falls back to `plural` for
/// English / no translation.
pub fn ngettext(singular : String, plural : String, num : Int) -> String {
  match current_locale() {
    None => if num == 1 { singular } else { plural }
    Some(loc) => {
      let form = plural_index(loc, num)
      lookup_plural(loc, singular, plural, form, plural)
    }
  }
}

// ---------------------------------------------------------------------------
// Separator lookup (mirrors python-humanize's thousands/decimal maps)
// ---------------------------------------------------------------------------

/// Thousands separator for the active locale (defaults to `","`).
pub fn thousands_separator() -> String {
  match current_locale() {
    None => ","
    Some(loc) => thousands_sep_for(loc)
  }
}

/// Decimal separator for the active locale (defaults to `"."`).
pub fn decimal_separator() -> String {
  match current_locale() {
    None => "."
    Some(loc) => decimal_sep_for(loc)
  }
}

// ---------------------------------------------------------------------------
// Message identifiers (Feature 2: i18n-aware scientific notation)
// ---------------------------------------------------------------------------

/// Logical identifiers for humanize messages that participate in localized
/// formatting. `msg_str` resolves one (given a `Locale`) to a `MsgId` whose
/// `format` method substitutes positional `%s` placeholders.
///
/// Not every locale ships every key (only `en`/`ru_RU`/`zh`/`zh_CN` currently
/// carry the scientific-notation keys). `msg_str` falls back to the English
/// catalog, then to the raw key string, so formatting is always total.
pub(all) enum MsgKey {
  ERR
  SEC
  MIN
  HR
  DAY
  WK
  NOW
  RECENT
  FUTURE
  PAST
  SCItmpl
  EXPword1
  EXPword2
  EXPword3
  EXPword4
  EXPword5
  EXPword6
}

/// A message id already resolved to its translated template string for a
/// locale. `format` substitutes positional `%s` placeholders.
pub struct MsgId {
  template : String
}

/// The `.po` msgid string for a given `MsgKey`.
fn msgid_of(self : MsgKey) -> String {
  match self {
    ERR => "an error occurred"
    SEC => "second"
    MIN => "minute"
    HR => "hour"
    DAY => "day"
    WK => "week"
    NOW => "just now"
    RECENT => "recently"
    FUTURE => "from now"
    PAST => "ago"
    SCItmpl => "scientific_tmpl"
    EXPword1 => "EXPword1"
    EXPword2 => "EXPword2"
    EXPword3 => "EXPword3"
    EXPword4 => "EXPword4"
    EXPword5 => "EXPword5"
    EXPword6 => "EXPword6"
  }
}

/// Resolve a message key to its translated template string in `locale`, falling
/// back to the English catalog and finally to the msgid itself.
pub fn msg_str(locale : Locale, id : MsgKey) -> MsgId {
  let key = id.msgid_of()
  let en = lookup_message(En, key, key)
  let resolved = lookup_message(locale, key, en)
  { template: resolved }
}

/// Substitute positional `%s` placeholders in the template with `args`.
pub fn format(self : MsgId, args : Array[String]) -> String {
  let mut out = ""
  let mut arg_i = 0
  let chars = self.template.to_array()
  let len = chars.length()
  let mut i = 0
  while i < len {
    if chars[i] == '%' && i + 1 < len && chars[i + 1] == 's' {
      if arg_i < args.length() {
        out = out + args[arg_i]
        arg_i = arg_i + 1
      }
      i = i + 2
    } else {
      out = out + chars[i].to_string()
      i = i + 1
    }
  }
  out
}

/// The raw resolved template (no substitution).
pub fn to_string(self : MsgId) -> String {
  self.template
}