///|
/// Closest string search functionality
/// 
/// Find the most similar string from a string array to a given string
/// Uses Levenshtein distance algorithm by default

///|
/// Closest string search options
pub struct ClosestStringOptions {
  /// Whether to be case sensitive
  /// Default value: false
  case_sensitive : Bool
  /// Custom comparison function
  /// Parameters: (a, b) -> distance between two strings
  /// Default value: levenshtein_distance
  compare_fn : (String, String) -> Int
}

///|
/// Create default closest string search options
pub fn default_closest_string_options() -> ClosestStringOptions {
  ClosestStringOptions::{
    case_sensitive: false,
    compare_fn: levenshtein_distance,
  }
}

///|
/// Create case-sensitive closest string search options
pub fn case_sensitive_closest_string_options() -> ClosestStringOptions {
  ClosestStringOptions::{
    case_sensitive: true,
    compare_fn: levenshtein_distance,
  }
}

///|
pub suberror StringMatchingError String

///|
/// Find the most similar string from a string array
/// 
/// Uses Levenshtein distance algorithm by default to calculate distance between words
/// 
/// * `given_word` - The string for measuring distance
/// * `possible_words` - Array of candidate strings
/// * `options` - Comparison options
/// + Returns the closest string
/// + Throws if the possible_words array is empty
/// 
/// # Examples
/// ```mbt
/// let possible_words = ["length", "size", "blah", "help"]
/// let suggestion = closest_string("hep", possible_words, None)
/// // suggestion will be "help"
/// ```
pub fn closest_string(
  given_word : String,
  possible_words : Array[String],
  options : ClosestStringOptions?,
) -> String raise StringMatchingError {
  if possible_words.length() == 0 {
    raise StringMatchingError(
      "When using closest_string(), the possible_words array must contain at least one word",
    )
  }
  let opts = match options {
    Some(opts) => opts
    None => default_closest_string_options()
  }
  let compare_fn = opts.compare_fn
  let case_sensitive = opts.case_sensitive
  let mut nearest_word = possible_words[0]
  let mut closest_distance = 2147483647 // Int max value
  for word in possible_words {
    let distance = if case_sensitive {
      compare_fn(given_word, word)
    } else {
      let given_lower = to_lowercase_string(given_word)
      let word_lower = to_lowercase_string(word)
      compare_fn(given_lower, word_lower)
    }
    if distance < closest_distance {
      nearest_word = word
      closest_distance = distance
    }
  }
  nearest_word
}

///|
/// Simplified version using default options
pub fn closest_string_simple(
  given_word : String,
  possible_words : Array[String],
) -> String raise StringMatchingError {
  closest_string(given_word, possible_words, None)
}

///|
/// Find multiple closest strings (return top n)
/// 
/// * `given_word` - The string for measuring distance
/// * `possible_words` - Array of candidate strings
/// * `count` - Number of closest strings to return
/// * `options` - Comparison options
/// + Returns an array of top count strings sorted by similarity
pub fn closest_strings(
  given_word : String,
  possible_words : Array[String],
  count : Int,
  options : ClosestStringOptions?,
) -> Array[String] {
  if possible_words.length() == 0 {
    return []
  }

  // Use similarity sorting functionality
  let compare_opts = match options {
    Some(opts) =>
      Some(CompareSimilarityOptions::{
        case_sensitive: opts.case_sensitive,
        compare_fn: opts.compare_fn,
      })
    None => None
  }
  let sorted = sort_by_similarity(possible_words, given_word, compare_opts)
  let result = Array::new()
  let max_count = if count > sorted.length() { sorted.length() } else { count }
  for i = 0; i < max_count; i = i + 1 {
    result.push(sorted[i])
  }
  result
}

///|
/// Tests
test "closest_string_basic" {
  let possible_words = ["length", "size", "blah", "help"]
  let suggestion = closest_string_simple("hep", possible_words)
  assert_eq(suggestion, "help")
}

///|
test "closest_string_empty_array" {
  let possible_words : Array[String] = []
  let err_msg = closest_string_simple("test", possible_words) catch {
    StringMatchingError(msg) => msg
  }
  inspect(
    err_msg,
    content="When using closest_string(), the possible_words array must contain at least one word",
  )
}

///|
test "closest_string_case_sensitive" {
  let possible_words = ["Hello", "hello", "HELLO"]
  let opts = case_sensitive_closest_string_options()
  let suggestion = closest_string("Hello", possible_words, Some(opts))
  assert_eq(suggestion, "Hello")
}

///|
test "closest_string_case_insensitive" {
  let possible_words = ["Hello", "world", "test"]
  let suggestion = closest_string_simple("hello", possible_words)
  assert_eq(suggestion, "Hello")
}

///|
test "closest_strings_multiple" {
  let possible_words = ["help", "hello", "world", "test", "hi"]
  let suggestions = closest_strings("hep", possible_words, 3, None)

  // The top three closest should include "help"
  assert_eq(suggestions[0], "help")
  assert_eq(suggestions.length(), 3)
}