///|
/// Units commonly displayed by consumer, business, and system applications.
pub(all) enum MeasurementUnit {
  Meter
  Kilometer
  Centimeter
  Millimeter
  SquareMeter
  Hectare
  Liter
  Milliliter
  Gram
  Kilogram
  MeterPerSecond
  KilometerPerHour
  BitPerSecond
  KilobitPerSecond
  MegabitPerSecond
  Celsius
  Fahrenheit
  Byte
  Kilobyte
  Megabyte
  Gigabyte
  Terabyte
  Kibibyte
  Mebibyte
  Gibibyte
  Tebibyte
  Millisecond
  Second
  Minute
  Hour
  Day
} derive(Eq, Debug)

///|
/// Controls whether a unit is written as a word, abbreviation, or symbol.
pub(all) enum UnitWidth {
  UnitLong
  UnitShort
  UnitNarrow
} derive(Eq, Debug)

///|
/// Controls the base used when automatically scaling byte quantities.
pub(all) enum ByteSizeStyle {
  DecimalBytes
  BinaryBytes
} derive(Eq, Debug)

///|
/// Locale-aware formatter for measurements and application-facing quantities.
pub struct UnitFormatter {
  locale : Locale
  width : UnitWidth
  number : DecimalFormatter
} derive(Eq, Debug)

///|
pub fn UnitFormatter::new(locale : Locale) -> UnitFormatter {
  { locale, width: UnitLong, number: DecimalFormatter::new(locale) }
}

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

///|
pub fn UnitFormatter::with_width(
  self : UnitFormatter,
  width : UnitWidth,
) -> UnitFormatter {
  { ..self, width, }
}

///|
pub fn UnitFormatter::with_fraction_digits(
  self : UnitFormatter,
  minimum : Int,
  maximum : Int,
) -> UnitFormatter {
  {
    ..self,
    number: self.number
    .with_minimum_fraction_digits(minimum)
    .with_maximum_fraction_digits(maximum),
  }
}

///|
fn unit_symbol(unit : MeasurementUnit) -> String {
  match unit {
    Meter => "m"
    Kilometer => "km"
    Centimeter => "cm"
    Millimeter => "mm"
    SquareMeter => "m\u{b2}"
    Hectare => "ha"
    Liter => "L"
    Milliliter => "mL"
    Gram => "g"
    Kilogram => "kg"
    MeterPerSecond => "m/s"
    KilometerPerHour => "km/h"
    BitPerSecond => "bit/s"
    KilobitPerSecond => "kbit/s"
    MegabitPerSecond => "Mbit/s"
    Celsius => "\u{b0}C"
    Fahrenheit => "\u{b0}F"
    Byte => "B"
    Kilobyte => "kB"
    Megabyte => "MB"
    Gigabyte => "GB"
    Terabyte => "TB"
    Kibibyte => "KiB"
    Mebibyte => "MiB"
    Gibibyte => "GiB"
    Tebibyte => "TiB"
    Millisecond => "ms"
    Second => "s"
    Minute => "min"
    Hour => "h"
    Day => "d"
  }
}

///|
fn english_unit_name(unit : MeasurementUnit, plural : Bool) -> String {
  match (unit, plural) {
    (Meter, false) => "meter"
    (Meter, true) => "meters"
    (Kilometer, false) => "kilometer"
    (Kilometer, true) => "kilometers"
    (Centimeter, false) => "centimeter"
    (Centimeter, true) => "centimeters"
    (Millimeter, false) => "millimeter"
    (Millimeter, true) => "millimeters"
    (SquareMeter, false) => "square meter"
    (SquareMeter, true) => "square meters"
    (Hectare, false) => "hectare"
    (Hectare, true) => "hectares"
    (Liter, false) => "liter"
    (Liter, true) => "liters"
    (Milliliter, false) => "milliliter"
    (Milliliter, true) => "milliliters"
    (Gram, false) => "gram"
    (Gram, true) => "grams"
    (Kilogram, false) => "kilogram"
    (Kilogram, true) => "kilograms"
    (MeterPerSecond, _) => "meters per second"
    (KilometerPerHour, _) => "kilometers per hour"
    (BitPerSecond, _) => "bits per second"
    (KilobitPerSecond, _) => "kilobits per second"
    (MegabitPerSecond, _) => "megabits per second"
    (Celsius, _) => "degrees Celsius"
    (Fahrenheit, _) => "degrees Fahrenheit"
    (Byte, false) => "byte"
    (Byte, true) => "bytes"
    (Kilobyte, false) => "kilobyte"
    (Kilobyte, true) => "kilobytes"
    (Megabyte, false) => "megabyte"
    (Megabyte, true) => "megabytes"
    (Gigabyte, false) => "gigabyte"
    (Gigabyte, true) => "gigabytes"
    (Terabyte, false) => "terabyte"
    (Terabyte, true) => "terabytes"
    (Kibibyte, false) => "kibibyte"
    (Kibibyte, true) => "kibibytes"
    (Mebibyte, false) => "mebibyte"
    (Mebibyte, true) => "mebibytes"
    (Gibibyte, false) => "gibibyte"
    (Gibibyte, true) => "gibibytes"
    (Tebibyte, false) => "tebibyte"
    (Tebibyte, true) => "tebibytes"
    (Millisecond, false) => "millisecond"
    (Millisecond, true) => "milliseconds"
    (Second, false) => "second"
    (Second, true) => "seconds"
    (Minute, false) => "minute"
    (Minute, true) => "minutes"
    (Hour, false) => "hour"
    (Hour, true) => "hours"
    (Day, false) => "day"
    (Day, true) => "days"
  }
}

///|
fn french_unit_name(unit : MeasurementUnit, plural : Bool) -> String {
  let suffix = if plural { "s" } else { "" }
  match unit {
    Meter => "m\u{e8}tre\{suffix}"
    Kilometer => "kilom\u{e8}tre\{suffix}"
    Centimeter => "centim\u{e8}tre\{suffix}"
    Millimeter => "millim\u{e8}tre\{suffix}"
    SquareMeter => "m\u{e8}tre\{suffix} carr\u{e9}\{suffix}"
    Hectare => "hectare\{suffix}"
    Liter => "litre\{suffix}"
    Milliliter => "millilitre\{suffix}"
    Gram => "gramme\{suffix}"
    Kilogram => "kilogramme\{suffix}"
    MeterPerSecond => "m\u{e8}tres par seconde"
    KilometerPerHour => "kilom\u{e8}tres par heure"
    BitPerSecond => "bits par seconde"
    KilobitPerSecond => "kilobits par seconde"
    MegabitPerSecond => "m\u{e9}gabits par seconde"
    Celsius => "degr\u{e9}s Celsius"
    Fahrenheit => "degr\u{e9}s Fahrenheit"
    Byte => "octet\{suffix}"
    Kilobyte => "kilooctet\{suffix}"
    Megabyte => "m\u{e9}gaoctet\{suffix}"
    Gigabyte => "gigaoctet\{suffix}"
    Terabyte => "t\u{e9}raoctet\{suffix}"
    Kibibyte => "kibioctet\{suffix}"
    Mebibyte => "m\u{e9}bioctet\{suffix}"
    Gibibyte => "gibioctet\{suffix}"
    Tebibyte => "t\u{e9}bioctet\{suffix}"
    Millisecond => "milliseconde\{suffix}"
    Second => "seconde\{suffix}"
    Minute => "minute\{suffix}"
    Hour => "heure\{suffix}"
    Day => "jour\{suffix}"
  }
}

///|
fn german_unit_name(unit : MeasurementUnit, plural : Bool) -> String {
  match (unit, plural) {
    (Meter, _) => "Meter"
    (Kilometer, _) => "Kilometer"
    (Centimeter, _) => "Zentimeter"
    (Millimeter, _) => "Millimeter"
    (SquareMeter, _) => "Quadratmeter"
    (Hectare, _) => "Hektar"
    (Liter, _) => "Liter"
    (Milliliter, _) => "Milliliter"
    (Gram, _) => "Gramm"
    (Kilogram, _) => "Kilogramm"
    (MeterPerSecond, _) => "Meter pro Sekunde"
    (KilometerPerHour, _) => "Kilometer pro Stunde"
    (BitPerSecond, _) => "Bit pro Sekunde"
    (KilobitPerSecond, _) => "Kilobit pro Sekunde"
    (MegabitPerSecond, _) => "Megabit pro Sekunde"
    (Celsius, _) => "Grad Celsius"
    (Fahrenheit, _) => "Grad Fahrenheit"
    (Byte, _) => "Byte"
    (Kilobyte, _) => "Kilobyte"
    (Megabyte, _) => "Megabyte"
    (Gigabyte, _) => "Gigabyte"
    (Terabyte, _) => "Terabyte"
    (Kibibyte, _) => "Kibibyte"
    (Mebibyte, _) => "Mebibyte"
    (Gibibyte, _) => "Gibibyte"
    (Tebibyte, _) => "Tebibyte"
    (Millisecond, false) => "Millisekunde"
    (Millisecond, true) => "Millisekunden"
    (Second, false) => "Sekunde"
    (Second, true) => "Sekunden"
    (Minute, false) => "Minute"
    (Minute, true) => "Minuten"
    (Hour, false) => "Stunde"
    (Hour, true) => "Stunden"
    (Day, false) => "Tag"
    (Day, true) => "Tage"
  }
}

///|
fn spanish_unit_name(unit : MeasurementUnit, plural : Bool) -> String {
  let suffix = if plural { "s" } else { "" }
  match unit {
    Meter => "metro\{suffix}"
    Kilometer => "kil\u{f3}metro\{suffix}"
    Centimeter => "cent\u{ed}metro\{suffix}"
    Millimeter => "mil\u{ed}metro\{suffix}"
    SquareMeter => "metro\{suffix} cuadrado\{suffix}"
    Hectare => "hect\u{e1}rea\{suffix}"
    Liter => "litro\{suffix}"
    Milliliter => "mililitro\{suffix}"
    Gram => "gramo\{suffix}"
    Kilogram => "kilogramo\{suffix}"
    MeterPerSecond => "metros por segundo"
    KilometerPerHour => "kil\u{f3}metros por hora"
    BitPerSecond => "bits por segundo"
    KilobitPerSecond => "kilobits por segundo"
    MegabitPerSecond => "megabits por segundo"
    Celsius => "grados Celsius"
    Fahrenheit => "grados Fahrenheit"
    Byte => "byte\{suffix}"
    Kilobyte => "kilobyte\{suffix}"
    Megabyte => "megabyte\{suffix}"
    Gigabyte => "gigabyte\{suffix}"
    Terabyte => "terabyte\{suffix}"
    Kibibyte => "kibibyte\{suffix}"
    Mebibyte => "mebibyte\{suffix}"
    Gibibyte => "gibibyte\{suffix}"
    Tebibyte => "tebibyte\{suffix}"
    Millisecond => "milisegundo\{suffix}"
    Second => "segundo\{suffix}"
    Minute => "minuto\{suffix}"
    Hour => "hora\{suffix}"
    Day => "d\u{ed}a\{suffix}"
  }
}

///|
fn italian_unit_name(unit : MeasurementUnit, plural : Bool) -> String {
  let suffix = if plural { "i" } else { "o" }
  match unit {
    Meter => "metr\{suffix}"
    Kilometer => "chilometr\{suffix}"
    Centimeter => "centimetr\{suffix}"
    Millimeter => "millimetr\{suffix}"
    SquareMeter => "metr\{suffix} quadrat\{suffix}"
    Hectare => if plural { "ettari" } else { "ettaro" }
    Liter => "litr\{suffix}"
    Milliliter => "millilitr\{suffix}"
    Gram => "gramm\{suffix}"
    Kilogram => "chilogramm\{suffix}"
    MeterPerSecond => "metri al secondo"
    KilometerPerHour => "chilometri all'ora"
    BitPerSecond => "bit al secondo"
    KilobitPerSecond => "kilobit al secondo"
    MegabitPerSecond => "megabit al secondo"
    Celsius => "gradi Celsius"
    Fahrenheit => "gradi Fahrenheit"
    Byte => "byte"
    Kilobyte => "kilobyte"
    Megabyte => "megabyte"
    Gigabyte => "gigabyte"
    Terabyte => "terabyte"
    Kibibyte => "kibibyte"
    Mebibyte => "mebibyte"
    Gibibyte => "gibibyte"
    Tebibyte => "tebibyte"
    Millisecond => "millisecond\{suffix}"
    Second => "second\{suffix}"
    Minute => if plural { "minuti" } else { "minuto" }
    Hour => if plural { "ore" } else { "ora" }
    Day => if plural { "giorni" } else { "giorno" }
  }
}

///|
fn chinese_unit_name(unit : MeasurementUnit) -> String {
  match unit {
    Meter => "\u{7c73}"
    Kilometer => "\u{5343}\u{7c73}"
    Centimeter => "\u{5398}\u{7c73}"
    Millimeter => "\u{6beb}\u{7c73}"
    SquareMeter => "\u{5e73}\u{65b9}\u{7c73}"
    Hectare => "\u{516c}\u{9877}"
    Liter => "\u{5347}"
    Milliliter => "\u{6beb}\u{5347}"
    Gram => "\u{514b}"
    Kilogram => "\u{5343}\u{514b}"
    MeterPerSecond => "\u{7c73}\u{6bcf}\u{79d2}"
    KilometerPerHour => "\u{5343}\u{7c73}\u{6bcf}\u{5c0f}\u{65f6}"
    BitPerSecond => "\u{6bd4}\u{7279}\u{6bcf}\u{79d2}"
    KilobitPerSecond => "\u{5343}\u{6bd4}\u{7279}\u{6bcf}\u{79d2}"
    MegabitPerSecond => "\u{5146}\u{6bd4}\u{7279}\u{6bcf}\u{79d2}"
    Celsius => "\u{6444}\u{6c0f}\u{5ea6}"
    Fahrenheit => "\u{534e}\u{6c0f}\u{5ea6}"
    Byte => "\u{5b57}\u{8282}"
    Kilobyte => "\u{5343}\u{5b57}\u{8282}"
    Megabyte => "\u{5146}\u{5b57}\u{8282}"
    Gigabyte => "\u{5409}\u{5b57}\u{8282}"
    Terabyte => "\u{592a}\u{5b57}\u{8282}"
    Kibibyte => "KiB"
    Mebibyte => "MiB"
    Gibibyte => "GiB"
    Tebibyte => "TiB"
    Millisecond => "\u{6beb}\u{79d2}"
    Second => "\u{79d2}"
    Minute => "\u{5206}\u{949f}"
    Hour => "\u{5c0f}\u{65f6}"
    Day => "\u{5929}"
  }
}

///|
fn japanese_unit_name(unit : MeasurementUnit) -> String {
  match unit {
    Meter => "\u{30e1}\u{30fc}\u{30c8}\u{30eb}"
    Kilometer => "\u{30ad}\u{30ed}\u{30e1}\u{30fc}\u{30c8}\u{30eb}"
    Centimeter => "\u{30bb}\u{30f3}\u{30c1}\u{30e1}\u{30fc}\u{30c8}\u{30eb}"
    Millimeter => "\u{30df}\u{30ea}\u{30e1}\u{30fc}\u{30c8}\u{30eb}"
    SquareMeter => "\u{5e73}\u{65b9}\u{30e1}\u{30fc}\u{30c8}\u{30eb}"
    Hectare => "\u{30d8}\u{30af}\u{30bf}\u{30fc}\u{30eb}"
    Liter => "\u{30ea}\u{30c3}\u{30c8}\u{30eb}"
    Milliliter => "\u{30df}\u{30ea}\u{30ea}\u{30c3}\u{30c8}\u{30eb}"
    Gram => "\u{30b0}\u{30e9}\u{30e0}"
    Kilogram => "\u{30ad}\u{30ed}\u{30b0}\u{30e9}\u{30e0}"
    MeterPerSecond => "\u{30e1}\u{30fc}\u{30c8}\u{30eb}\u{6bce}\u{79d2}"
    KilometerPerHour =>
      "\u{30ad}\u{30ed}\u{30e1}\u{30fc}\u{30c8}\u{30eb}\u{6bce}\u{6642}"
    BitPerSecond => "\u{30d3}\u{30c3}\u{30c8}\u{6bce}\u{79d2}"
    KilobitPerSecond =>
      "\u{30ad}\u{30ed}\u{30d3}\u{30c3}\u{30c8}\u{6bce}\u{79d2}"
    MegabitPerSecond =>
      "\u{30e1}\u{30ac}\u{30d3}\u{30c3}\u{30c8}\u{6bce}\u{79d2}"
    Celsius => "\u{6442}\u{6c0f}\u{5ea6}"
    Fahrenheit => "\u{83ef}\u{6c0f}\u{5ea6}"
    Byte => "\u{30d0}\u{30a4}\u{30c8}"
    Kilobyte => "\u{30ad}\u{30ed}\u{30d0}\u{30a4}\u{30c8}"
    Megabyte => "\u{30e1}\u{30ac}\u{30d0}\u{30a4}\u{30c8}"
    Gigabyte => "\u{30ae}\u{30ac}\u{30d0}\u{30a4}\u{30c8}"
    Terabyte => "\u{30c6}\u{30e9}\u{30d0}\u{30a4}\u{30c8}"
    Kibibyte => "KiB"
    Mebibyte => "MiB"
    Gibibyte => "GiB"
    Tebibyte => "TiB"
    Millisecond => "\u{30df}\u{30ea}\u{79d2}"
    Second => "\u{79d2}"
    Minute => "\u{5206}"
    Hour => "\u{6642}\u{9593}"
    Day => "\u{65e5}"
  }
}

///|
fn unit_long_name(
  locale : Locale,
  unit : MeasurementUnit,
  plural : Bool,
) -> String {
  match locale.language() {
    "zh" => chinese_unit_name(unit)
    "ja" => japanese_unit_name(unit)
    "fr" => french_unit_name(unit, plural)
    "de" => german_unit_name(unit, plural)
    "es" => spanish_unit_name(unit, plural)
    "it" => italian_unit_name(unit, plural)
    _ => english_unit_name(unit, plural)
  }
}

///|
fn decimal_is_one(value : Decimal) -> Bool {
  value == Decimal::from_int(1)
}

///|
fn unit_separator(locale : Locale, width : UnitWidth) -> String {
  match width {
    UnitNarrow => ""
    UnitShort =>
      if locale.language() == "zh" || locale.language() == "ja" {
        ""
      } else {
        "\u{a0}"
      }
    UnitLong =>
      if locale.language() == "zh" || locale.language() == "ja" {
        ""
      } else {
        " "
      }
  }
}

///|
/// Formats a decimal quantity with a localized unit label.
pub fn UnitFormatter::format(
  self : UnitFormatter,
  value : Decimal,
  unit : MeasurementUnit,
) -> String {
  let number = self.number.format(value)
  let label = match self.width {
    UnitLong => unit_long_name(self.locale, unit, !decimal_is_one(value))
    UnitShort | UnitNarrow => unit_symbol(unit)
  }
  "\{number}\{unit_separator(self.locale, self.width)}\{label}"
}

///|
pub fn UnitFormatter::format_int(
  self : UnitFormatter,
  value : Int,
  unit : MeasurementUnit,
) -> String {
  self.format(Decimal::from_int(value), unit)
}

///|
fn scaled_decimal(value : Int, divisor : Int, fraction_digits : Int) -> Decimal {
  if divisor == 1 {
    return Decimal::from_int(value)
  }
  let digits = if fraction_digits < 0 {
    0
  } else if fraction_digits > 3 {
    3
  } else {
    fraction_digits
  }
  let factor = power_of_ten(digits)
  let magnitude = positive_int(value)
  let step = if divisor / factor < 1 { 1 } else { divisor / factor }
  let mut scaled = magnitude / step
  let remainder = magnitude % step
  if remainder * 2 >= step {
    scaled = scaled + 1
  }
  Decimal::new(if value < 0 { -scaled } else { scaled }, digits)
}

///|
fn select_byte_unit(
  magnitude : Int,
  style : ByteSizeStyle,
) -> (Int, MeasurementUnit) {
  match style {
    DecimalBytes =>
      if magnitude >= 1000000000 {
        (1000000000, Gigabyte)
      } else if magnitude >= 1000000 {
        (1000000, Megabyte)
      } else if magnitude >= 1000 {
        (1000, Kilobyte)
      } else {
        (1, Byte)
      }
    BinaryBytes =>
      if magnitude >= 1073741824 {
        (1073741824, Gibibyte)
      } else if magnitude >= 1048576 {
        (1048576, Mebibyte)
      } else if magnitude >= 1024 {
        (1024, Kibibyte)
      } else {
        (1, Byte)
      }
  }
}

///|
/// Automatically selects a byte unit using SI (1000) or IEC (1024) scaling.
pub fn format_byte_size(
  bytes : Int,
  locale : Locale,
  style? : ByteSizeStyle = DecimalBytes,
  fraction_digits? : Int = 1,
) -> String {
  let (divisor, unit) = select_byte_unit(positive_int(bytes), style)
  let value = scaled_decimal(bytes, divisor, fraction_digits)
  UnitFormatter::new(locale)
  .with_width(UnitShort)
  .with_fraction_digits(0, fraction_digits)
  .format(value, unit)
}

///|
fn push_duration_part(
  parts : Array[String],
  formatter : UnitFormatter,
  value : Int,
  unit : MeasurementUnit,
  limit : Int,
) -> Unit {
  if value > 0 && parts.length() < limit {
    parts.push(formatter.format_int(value, unit))
  }
}

///|
/// Formats seconds as a localized compound duration such as
/// `1 hour, 2 minutes` or `1小时2分钟`.
pub fn format_duration(
  total_seconds : Int,
  locale : Locale,
  width? : UnitWidth = UnitLong,
  max_parts? : Int = 2,
) -> String {
  let limit = if max_parts < 1 {
    1
  } else if max_parts > 4 {
    4
  } else {
    max_parts
  }
  let negative = total_seconds < 0
  let magnitude = positive_int(total_seconds)
  let days = magnitude / 86400
  let hours = magnitude % 86400 / 3600
  let minutes = magnitude % 3600 / 60
  let seconds = magnitude % 60
  let formatter = UnitFormatter::new(locale).with_width(width)
  let parts : Array[String] = []
  push_duration_part(parts, formatter, days, Day, limit)
  push_duration_part(parts, formatter, hours, Hour, limit)
  push_duration_part(parts, formatter, minutes, Minute, limit)
  push_duration_part(parts, formatter, seconds, Second, limit)
  if parts.length() == 0 {
    parts.push(formatter.format_int(0, Second))
  }
  let rendered = ListFormatter::new(locale)
    .with_type(Unit)
    .with_width(
      match width {
        UnitLong => Long
        UnitShort => Short
        UnitNarrow => Narrow
      },
    )
    .format(parts)
  if negative {
    "-\{rendered}"
  } else {
    rendered
  }
}