///|
/// Controls when a localized number includes a sign.
pub(all) enum SignDisplay {
  Auto
  Always
  Never
  ExceptZero
} derive(Eq, Debug)

///|
/// Symbols and grouping rules used by a numbering system.
pub struct NumberSymbols {
  decimal : String
  group : String
  minus : String
  plus : String
  primary_group : Int
  secondary_group : Int
} derive(Eq, Debug)

///|
pub fn NumberSymbols::decimal(self : NumberSymbols) -> String {
  self.decimal
}

///|
pub fn NumberSymbols::group(self : NumberSymbols) -> String {
  self.group
}

///|
pub fn NumberSymbols::minus(self : NumberSymbols) -> String {
  self.minus
}

///|
pub fn NumberSymbols::plus(self : NumberSymbols) -> String {
  self.plus
}

///|
pub fn NumberSymbols::primary_group(self : NumberSymbols) -> Int {
  self.primary_group
}

///|
pub fn NumberSymbols::secondary_group(self : NumberSymbols) -> Int {
  self.secondary_group
}

///|
/// Returns practical decimal symbols for a locale.
///
/// MoonLocale intentionally keeps Latin digits as its portable default while
/// selecting punctuation and grouping conventions from the locale.
pub fn number_symbols(locale : Locale) -> NumberSymbols {
  match locale.language() {
    "fr" =>
      {
        decimal: ",",
        group: "\u{202f}",
        minus: "-",
        plus: "+",
        primary_group: 3,
        secondary_group: 3,
      }
    "de" | "es" | "it" | "pt" | "nl" | "da" =>
      {
        decimal: ",",
        group: ".",
        minus: "-",
        plus: "+",
        primary_group: 3,
        secondary_group: 3,
      }
    "ru" | "uk" | "pl" | "sv" | "no" | "fi" | "cs" | "sk" =>
      {
        decimal: ",",
        group: "\u{a0}",
        minus: "-",
        plus: "+",
        primary_group: 3,
        secondary_group: 3,
      }
    "hi" | "bn" | "mr" | "gu" | "ta" | "te" =>
      {
        decimal: ".",
        group: ",",
        minus: "-",
        plus: "+",
        primary_group: 3,
        secondary_group: 2,
      }
    "ar" =>
      {
        decimal: "\u{66b}",
        group: "\u{66c}",
        minus: "-",
        plus: "+",
        primary_group: 3,
        secondary_group: 3,
      }
    _ =>
      {
        decimal: ".",
        group: ",",
        minus: "-",
        plus: "+",
        primary_group: 3,
        secondary_group: 3,
      }
  }
}

///|
/// Immutable configuration for localized number output.
pub struct NumberFormatter {
  locale : Locale
  grouping : Bool
  minimum_integer_digits : Int
  sign_display : SignDisplay
} derive(Eq, Debug)

///|
pub fn NumberFormatter::new(locale : Locale) -> NumberFormatter {
  { locale, grouping: true, minimum_integer_digits: 1, sign_display: Auto }
}

///|
pub fn NumberFormatter::locale(self : NumberFormatter) -> Locale {
  self.locale
}

///|
pub fn NumberFormatter::with_grouping(
  self : NumberFormatter,
  enabled : Bool,
) -> NumberFormatter {
  { ..self, grouping: enabled }
}

///|
/// Sets the minimum number of integer digits, clamped to the range 1–21.
pub fn NumberFormatter::with_minimum_integer_digits(
  self : NumberFormatter,
  digits : Int,
) -> NumberFormatter {
  let clamped = if digits < 1 { 1 } else if digits > 21 { 21 } else { digits }
  { ..self, minimum_integer_digits: clamped }
}

///|
pub fn NumberFormatter::with_sign_display(
  self : NumberFormatter,
  display : SignDisplay,
) -> NumberFormatter {
  { ..self, sign_display: display }
}

///|
fn repeat_string(value : String, count : Int) -> String {
  let output = StringBuilder::new()
  for index = 0; index < count; index = index + 1 {
    output.write_string(value)
  }
  output.to_string()
}

///|
fn integer_parts(value : Int) -> (Bool, String) {
  let rendered = value.to_string()
  if rendered[0] == 45 {
    (true, "\{rendered[1:]}")
  } else {
    (false, rendered)
  }
}

///|
fn group_digits(
  digits : String,
  separator : String,
  primary : Int,
  secondary : Int,
) -> String {
  if digits.length() <= primary {
    return digits
  }
  let output = StringBuilder::new()
  let length = digits.length()
  for index = 0; index < length; index = index + 1 {
    if index > 0 {
      let remaining = length - index
      if remaining == primary ||
        (remaining > primary && (remaining - primary) % secondary == 0) {
        output.write_string(separator)
      }
    }
    output.write_string("\{digits[index:index + 1]}")
  }
  output.to_string()
}

///|
fn number_sign(
  negative : Bool,
  zero : Bool,
  display : SignDisplay,
  symbols : NumberSymbols,
) -> String {
  match display {
    Never => ""
    Always => if negative { symbols.minus } else { symbols.plus }
    ExceptZero =>
      if zero {
        ""
      } else if negative {
        symbols.minus
      } else {
        symbols.plus
      }
    Auto => if negative { symbols.minus } else { "" }
  }
}

///|
/// Formats an integer using locale punctuation and grouping.
pub fn NumberFormatter::format_int(
  self : NumberFormatter,
  value : Int,
) -> String {
  let (negative, raw_digits) = integer_parts(value)
  let padding = self.minimum_integer_digits - raw_digits.length()
  let digits = if padding > 0 {
    repeat_string("0", padding) + raw_digits
  } else {
    raw_digits
  }
  let symbols = number_symbols(self.locale)
  let body = if self.grouping {
    group_digits(
      digits,
      symbols.group,
      symbols.primary_group,
      symbols.secondary_group,
    )
  } else {
    digits
  }
  number_sign(negative, value == 0, self.sign_display, symbols) + body
}

///|
/// Convenience entry point with default options.
pub fn format_int(locale : Locale, value : Int) -> String {
  NumberFormatter::new(locale).format_int(value)
}