///|
fn locale_support_entry(
  language : String,
  english_name : String,
  native_name : String,
  direction : TextDirection,
  cardinal_categories : Array[String],
  ordinal_categories : Array[String],
) -> LocaleSupport {
  {
    language,
    english_name,
    native_name,
    direction,
    cardinal_categories,
    ordinal_categories,
  }
}

///|
/// Return human-facing metadata for every explicitly supported language.
pub fn locale_support_table() -> Array[LocaleSupport] {
  [
    locale_support_entry("zh", "Chinese", "中文", LeftToRight, ["other"], [
      "other",
    ]),
    locale_support_entry(
      "en",
      "English",
      "English",
      LeftToRight,
      ["one", "other"],
      ["one", "two", "few", "other"],
    ),
    locale_support_entry(
      "ja",
      "Japanese",
      "日本語",
      LeftToRight,
      ["other"],
      ["other"],
    ),
    locale_support_entry(
      "ru",
      "Russian",
      "Русский",
      LeftToRight,
      ["one", "few", "many", "other"],
      ["other"],
    ),
    locale_support_entry(
      "ar",
      "Arabic",
      "العربية",
      RightToLeft,
      ["zero", "one", "two", "few", "many", "other"],
      ["other"],
    ),
  ]
}

///|
pub fn locale_support(locale : String) -> LocaleSupport? {
  let language = locale_language_lossy(locale)
  for support in locale_support_table() {
    if support.language == language {
      return Some(support)
    }
  }
  None
}

///|
pub fn LocaleSupport::language(self : LocaleSupport) -> String {
  self.language
}

///|
pub fn LocaleSupport::english_name(self : LocaleSupport) -> String {
  self.english_name
}

///|
pub fn LocaleSupport::native_name(self : LocaleSupport) -> String {
  self.native_name
}

///|
pub fn LocaleSupport::direction(self : LocaleSupport) -> TextDirection {
  self.direction
}

///|
pub fn LocaleSupport::cardinal_categories(
  self : LocaleSupport,
) -> Array[String] {
  self.cardinal_categories
}

///|
pub fn LocaleSupport::ordinal_categories(self : LocaleSupport) -> Array[String] {
  self.ordinal_categories
}

///|
pub fn LocaleSupport::summary(self : LocaleSupport) -> String {
  "\{self.language} \{self.english_name} (\{self.native_name}), \{self.direction.name()}, cardinal=\{self.cardinal_categories.join("/")}, ordinal=\{self.ordinal_categories.join("/")}"
}

///|
fn canonical_available_map(available : Array[String]) -> Map[String, String] {
  let output : Map[String, String] = Map([])
  for locale in available {
    output[canonicalize_locale_lossy(locale)] = locale
  }
  output
}

///|
fn negotiation_candidates(
  requested : String,
  default_locale : String?,
) -> Array[String] {
  let output : Array[String] = []
  let seen : Map[String, Unit] = Map([])
  append_locale_chain(output, seen, requested)
  match default_locale {
    Some(locale) => append_locale_chain(output, seen, locale)
    None => ()
  }
  output
}

///|
/// Negotiate a requested locale against an available locale list.
pub fn negotiate_locale(
  requested : String,
  available : Array[String],
  default_locale? : String,
) -> LocaleNegotiation {
  let candidates = negotiation_candidates(requested, default_locale)
  let available_map = canonical_available_map(available)
  let mut matched : String? = None
  let mut used_default = false
  let requested_chain = locale_fallback_chain(requested)
  for candidate in candidates {
    match available_map.get(candidate) {
      Some(original) => {
        matched = Some(original)
        let mut requested_candidate = false
        for value in requested_chain {
          if value == candidate {
            requested_candidate = true
            break
          }
        }
        used_default = !requested_candidate
        break
      }
      None => ()
    }
  }
  {
    requested: canonicalize_locale_lossy(requested),
    candidates,
    available,
    matched,
    used_default,
  }
}

///|
pub fn LocaleNegotiation::requested(self : LocaleNegotiation) -> String {
  self.requested
}

///|
pub fn LocaleNegotiation::candidates(self : LocaleNegotiation) -> Array[String] {
  self.candidates
}

///|
pub fn LocaleNegotiation::available(self : LocaleNegotiation) -> Array[String] {
  self.available
}

///|
pub fn LocaleNegotiation::matched(self : LocaleNegotiation) -> String? {
  self.matched
}

///|
pub fn LocaleNegotiation::used_default(self : LocaleNegotiation) -> Bool {
  self.used_default
}

///|
pub fn LocaleNegotiation::succeeded(self : LocaleNegotiation) -> Bool {
  self.matched is Some(_)
}

///|
pub fn LocaleNegotiation::summary(self : LocaleNegotiation) -> String {
  match self.matched {
    Some(locale) =>
      if self.used_default {
        "\{self.requested} -> \{locale} (default)"
      } else {
        "\{self.requested} -> \{locale}"
      }
    None => "\{self.requested} -> no match"
  }
}