///|
fn common_prefix_length(left : String, right : String, maximum : Int) -> Int {
  let shared = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  let limit = if shared < maximum { shared } else { maximum }
  let mut prefix = 0
  while prefix < limit && left[prefix] == right[prefix] {
    prefix = prefix + 1
  }
  prefix
}

///|
/// Computes Jaro-Winkler similarity with a validated prefix scaling factor.
pub fn jaro_winkler_similarity(
  left : String,
  right : String,
  scaling : Double,
) -> Result[Double, SimilarityError] {
  if scaling.is_nan() || scaling.is_inf() || scaling < 0.0 || scaling > 0.25 {
    return Err(InvalidWinklerScaling(scaling))
  }
  let base = jaro_similarity(left, right)
  if base < 0.7 {
    return Ok(base)
  }
  let prefix = common_prefix_length(left, right, 4)
  let score = base + prefix.to_double() * scaling * (1.0 - base)
  Ok(if score > 1.0 { 1.0 } else { score })
}