///|
fn array_contains_string(values : Array[String], expected : String) -> Bool {
  for value in values {
    if value == expected {
      return true
    }
  }
  false
}

///|
/// Explain how a numeric choice selects a branch.
pub fn select_plural_branch(
  locale : String,
  value : Int,
  ordinal : Bool,
  selectors : Array[String],
) -> PluralSelection raise MessageError {
  let category = if ordinal {
    ordinal_category(locale, value)
  } else {
    plural_category(locale, value)
  }
  let exact_selector = "=" + value.to_string()
  let used_exact = array_contains_string(selectors, exact_selector)
  let selected_selector = if used_exact {
    exact_selector
  } else if array_contains_string(selectors, category) {
    category
  } else if array_contains_string(selectors, "other") {
    "other"
  } else {
    raise MissingChoice(category)
  }
  {
    locale: canonicalize_locale_lossy(locale),
    value,
    ordinal,
    category,
    exact_selector,
    selected_selector,
    used_exact,
    used_other: selected_selector == "other" && category != "other",
  }
}

///|
pub fn PluralSelection::locale(self : PluralSelection) -> String {
  self.locale
}

///|
pub fn PluralSelection::value(self : PluralSelection) -> Int {
  self.value
}

///|
pub fn PluralSelection::ordinal(self : PluralSelection) -> Bool {
  self.ordinal
}

///|
pub fn PluralSelection::category(self : PluralSelection) -> String {
  self.category
}

///|
pub fn PluralSelection::exact_selector(self : PluralSelection) -> String {
  self.exact_selector
}

///|
pub fn PluralSelection::selected_selector(self : PluralSelection) -> String {
  self.selected_selector
}

///|
pub fn PluralSelection::used_exact(self : PluralSelection) -> Bool {
  self.used_exact
}

///|
pub fn PluralSelection::used_other(self : PluralSelection) -> Bool {
  self.used_other
}

///|
pub fn PluralSelection::summary(self : PluralSelection) -> String {
  let mode = if self.ordinal { "ordinal" } else { "cardinal" }
  "\{self.locale} \{mode} \{self.value}: category=\{self.category}, selected=\{self.selected_selector}"
}

///|
fn categories_for_mode(locale : String, ordinal : Bool) -> Array[String] {
  if ordinal {
    required_ordinal_categories(locale)
  } else {
    required_cardinal_categories(locale)
  }
}

///|
fn category_for_mode(locale : String, value : Int, ordinal : Bool) -> String {
  if ordinal {
    ordinal_category(locale, value)
  } else {
    plural_category(locale, value)
  }
}

///|
fn normalized_range(start : Int, end : Int) -> (Int, Int) {
  if start <= end {
    (start, end)
  } else {
    (end, start)
  }
}

///|
/// Collect representative integer values for each plural category.
pub fn plural_examples(
  locale : String,
  start : Int,
  end : Int,
  ordinal? : Bool = false,
  limit_per_category? : Int = 5,
) -> Array[PluralExamples] {
  let (lower, upper) = normalized_range(start, end)
  let categories = categories_for_mode(locale, ordinal)
  let values : Map[String, Array[Int]] = Map([])
  for category in categories {
    values[category] = []
  }
  for value in lower..<=upper {
    let category = category_for_mode(locale, value, ordinal)
    match values.get(category) {
      Some(examples) =>
        if examples.length() < limit_per_category {
          examples.push(value)
        }
      None => values[category] = [value]
    }
  }
  let output : Array[PluralExamples] = []
  for category in categories {
    output.push({ category, values: values[category] })
  }
  output
}

///|
pub fn PluralExamples::category(self : PluralExamples) -> String {
  self.category
}

///|
pub fn PluralExamples::values(self : PluralExamples) -> Array[Int] {
  self.values
}

///|
pub fn PluralExamples::is_empty(self : PluralExamples) -> Bool {
  self.values.length() == 0
}

///|
pub fn PluralExamples::summary(self : PluralExamples) -> String {
  let values : Array[String] = []
  for value in self.values {
    values.push(value.to_string())
  }
  "\{self.category}: \{values.join(", ")}"
}

///|
/// Return every category observed over an inclusive integer range.
pub fn observed_plural_categories(
  locale : String,
  start : Int,
  end : Int,
  ordinal? : Bool = false,
) -> Array[String] {
  let (lower, upper) = normalized_range(start, end)
  let seen : Map[String, Unit] = Map([])
  for value in lower..<=upper {
    seen[category_for_mode(locale, value, ordinal)] = ()
  }
  let output : Array[String] = []
  for category, _ in seen {
    output.push(category)
  }
  output.sort()
  output
}

///|
/// Check whether selectors cover all categories declared for a locale.
pub fn missing_plural_categories(
  locale : String,
  selectors : Array[String],
  ordinal? : Bool = false,
) -> Array[String] {
  let missing : Array[String] = []
  for category in categories_for_mode(locale, ordinal) {
    if !array_contains_string(selectors, category) {
      missing.push(category)
    }
  }
  missing
}