///|
/// Correct a phrase, including accidentally joined or separated words. The
/// limit applies to each word lookup, not to the final phrase distance. Return
/// one suggestion with the final OSA distance and estimated phrase frequency.
/// Tokens are separated by Unicode whitespace. Spelling and case stay exact;
/// normalize dictionary terms and input consistently in the calling application.
pub fn SymSpell::lookup_compound(
  self : SymSpell,
  phrase : String,
  max_edit_distance? : Int32,
) -> Array[Suggestion] raise SymSpellError {
  let limit = self.resolve_edit_distance(max_edit_distance)
  let terms = split_whitespace(phrase)
  let parts : Array[Suggestion] = []
  let mut last_was_combination = false
  for i = 0; i < terms.length(); i = i + 1 {
    let term = terms[i]
    let chars = term.to_array()
    let suggestions = self.lookup(term, verbosity=Top, max_edit_distance=limit)
    let original = suggestions.get(0)
    if i > 0 && !last_was_combination {
      let combined = self.lookup(
        terms[i - 1] + term,
        verbosity=Top,
        max_edit_distance=limit,
      )
      if combined.get(0) is Some(merged) {
        let previous = parts[parts.length() - 1]
        let current = original.unwrap_or({
          term,
          distance: limit + 1,
          count: unknown_frequency(chars.length()),
        })
        let separate_distance = previous.distance + current.distance
        let merged_distance = merged.distance + 1
        let separate_count = previous.count.to_double() /
          self.corpus_size.to_double() *
          current.count.to_double()
        if merged_distance < separate_distance ||
          (
            merged_distance == separate_distance &&
            merged.count.to_double() > separate_count
          ) {
          parts[parts.length() - 1] = {
            term: merged.term,
            distance: merged_distance,
            count: merged.count,
          }
          last_was_combination = true
          continue
        }
      }
    }
    last_was_combination = false
    if original is Some(best) && (best.distance == 0 || chars.length() == 1) {
      parts.push(best)
      continue
    }
    let mut best_split = original
    for split = 1; split < chars.length(); split = split + 1 {
      let left = self.lookup(
        String::from_array(chars[:split]),
        verbosity=Top,
        max_edit_distance=limit,
      )
      let right = self.lookup(
        String::from_array(chars[split:]),
        verbosity=Top,
        max_edit_distance=limit,
      )
      guard left.get(0) is Some(first) else { continue }
      guard right.get(0) is Some(second) else { continue }
      let correction = first.term + " " + second.term
      let distance = osa_distance(chars, correction.to_array(), limit).unwrap_or(
        limit + 1,
      )
      if best_split is Some(best) {
        if distance > best.distance {
          continue
        }
        if distance < best.distance {
          best_split = None
        }
      }
      let count = match self.bigrams.get(correction) {
        Some(frequency) => {
          let mut count = frequency
          let concatenated = first.term + second.term
          match original {
            Some(single) =>
              if concatenated == term {
                count = Int64::max(count, add_frequency(single.count, 2L))
              } else if single.term == first.term || single.term == second.term {
                count = Int64::max(count, add_frequency(single.count, 1L))
              }
            None =>
              if concatenated == term {
                count = Int64::max(
                  count,
                  add_frequency(Int64::max(first.count, second.count), 2L),
                )
              }
          }
          count
        }
        None => self.estimated_bigram_count(first.count, second.count)
      }
      if best_split is None || (best_split is Some(best) && count > best.count) {
        best_split = Some({ term: correction, distance, count, })
      }
    }
    parts.push(
      best_split.unwrap_or({
        term,
        distance: limit + 1,
        count: unknown_frequency(chars.length()),
      }),
    )
  }
  let builder = StringBuilder()
  let mut count = self.corpus_size.to_double()
  for i = 0; i < parts.length(); i = i + 1 {
    if i > 0 {
      builder.write_char(' ')
    }
    builder.write_string(parts[i].term)
    count = count * (parts[i].count.to_double() / self.corpus_size.to_double())
  }
  let correction = builder.to_string()
  let original = phrase.to_array()
  let corrected = correction.to_array()
  let distance = osa_distance(
    original,
    corrected,
    Int32::max(original.length(), corrected.length()),
  ).unwrap()
  [{ term: correction, distance, count: probability_count(count), }]
}