///|
/// A CLDR cardinal plural category.
#derive(Eq, Debug)
pub(all) enum PluralCategory {
  Zero
  One
  Two
  Few
  Many
  Other
}

///|
pub extend PluralCategory with Eq::{not_equal, equal}

///|
pub extend PluralCategory with Debug::{to_repr}

///|
/// Returns the catalog key suffix for this category, such as `one` or `other`.
pub fn PluralCategory::key(self : PluralCategory) -> String {
  match self {
    Zero => "zero"
    One => "one"
    Two => "two"
    Few => "few"
    Many => "many"
    Other => "other"
  }
}

///|
/// Selects the CLDR cardinal plural category for an integer count, using the
/// absolute value of the count. Rules follow the CLDR supplemental plural
/// data (https://unicode.org/repos/cldr/trunk/specs/ldml/tr35-numbers.html#Language_Plural_Rules).
/// Fractional and compact-notation operands are out of scope; languages
/// without a dedicated rule use the root `other` category.
pub fn Locale::plural_category(self : Locale, count : Int32) -> PluralCategory {
  let n = count.to_int64()
  let n = if n < 0L { -n } else { n }
  match self.language() {
    "zh" | "ja" | "ko" | "th" | "vi" | "id" => Other
    "en" | "de" | "nl" | "sv" | "da" | "fi" | "no" | "nb" | "tr" | "el" =>
      if n == 1L {
        One
      } else {
        Other
      }
    "fr" | "pt" =>
      if n <= 1L {
        One
      } else if is_exact_million(n) {
        Many
      } else {
        Other
      }
    "es" | "it" =>
      if n == 1L {
        One
      } else if is_exact_million(n) {
        Many
      } else {
        Other
      }
    "ru" | "uk" => east_slavic_plural(n)
    "pl" => polish_plural(n)
    // CLDR `cs` reserves `many` for visible fractions (v != 0), which an
    // integer count never has.
    "cs" => if n == 1L { One } else if n >= 2L && n <= 4L { Few } else { Other }
    "ar" => arabic_plural(n)
    "hi" => if n == 0L || n == 1L { One } else { Other }
    _ => Other
  }
}

///|
// CLDR 42+ adds `many` to fr/pt/es/it for exact millions; the compact-notation
// exponent operand (`e`) does not apply to plain integer counts.
fn is_exact_million(n : Int64) -> Bool {
  n != 0L && n % 1000000L == 0L
}

///|
fn east_slavic_plural(n : Int64) -> PluralCategory {
  let mod10 = n % 10L
  let mod100 = n % 100L
  if mod10 == 1L && mod100 != 11L {
    One
  } else if mod10 >= 2L && mod10 <= 4L && !(mod100 >= 12L && mod100 <= 14L) {
    Few
  } else if mod10 == 0L ||
    (mod10 >= 5L && mod10 <= 9L) ||
    (mod100 >= 11L && mod100 <= 14L) {
    Many
  } else {
    Other
  }
}

///|
fn polish_plural(n : Int64) -> PluralCategory {
  let mod10 = n % 10L
  let mod100 = n % 100L
  if n == 1L {
    One
  } else if mod10 >= 2L && mod10 <= 4L && !(mod100 >= 12L && mod100 <= 14L) {
    Few
  } else if mod10 <= 1L ||
    (mod10 >= 5L && mod10 <= 9L) ||
    (mod100 >= 12L && mod100 <= 14L) {
    // The mod10 <= 1 branch excludes n == 1 above, so 101/201 are `many`.
    Many
  } else {
    Other
  }
}

///|
fn arabic_plural(n : Int64) -> PluralCategory {
  let mod100 = n % 100L
  if n == 0L {
    Zero
  } else if n == 1L {
    One
  } else if n == 2L {
    Two
  } else if mod100 >= 3L && mod100 <= 10L {
    Few
  } else if mod100 >= 11L && mod100 <= 99L {
    Many
  } else {
    Other
  }
}