///|
/// Run the same query against a flat index and return results in input order.
pub fn FlatIndex::search_batch(
  self : FlatIndex,
  queries : Array[Array[Double]],
  top_k : Int,
  metric : DistanceMetric,
  filters : Array[(String, String)],
) -> Array[Array[SearchResult]] raise VectorError {
  let all = []
  for query in queries {
    all.push(self.search(query, top_k, metric, filters))
  }
  all
}

///|
/// Search a batch using IVF-Flat with the same probe budget for each query.
pub fn IvfIndex::search_batch(
  self : IvfIndex,
  queries : Array[Array[Double]],
  top_k : Int,
  nprobe : Int,
  filters : Array[(String, String)],
) -> Array[Array[SearchResult]] raise VectorError {
  let all = []
  for query in queries {
    all.push(self.search(query, top_k, nprobe, filters))
  }
  all
}

///|
/// Search a batch with the approximate LSH index.
pub fn LshIndex::search_batch(
  self : LshIndex,
  queries : Array[Array[Double]],
  top_k : Int,
  filters : Array[(String, String)],
) -> Array[Array[SearchResult]] raise VectorError {
  let all = []
  for query in queries {
    all.push(self.search(query, top_k, filters))
  }
  all
}

///|
/// Find the first result with the requested document id.
pub fn find_result(results : Array[SearchResult], id : String) -> SearchResult? {
  for result in results {
    if result.id == id {
      return Some(result)
    }
  }
  None
}

///|
/// Calculate recall@k between an approximate result list and an exact list.
pub fn recall_at_k(
  approximate : Array[SearchResult],
  exact : Array[SearchResult],
  k : Int,
) -> Double {
  if k <= 0 || exact.length() == 0 {
    return 0.0
  }
  let limit = if k < exact.length() { k } else { exact.length() }
  let mut hits = 0
  for i = 0; i < limit; i = i + 1 {
    if find_result(approximate, exact[i].id) is Some(_) {
      hits = hits + 1
    }
  }
  hits.to_double() / limit.to_double()
}

///|
/// Calculate the mean recall of corresponding query batches.
pub fn mean_recall(
  approximate : Array[Array[SearchResult]],
  exact : Array[Array[SearchResult]],
  k : Int,
) -> Double {
  if approximate.length() == 0 || exact.length() == 0 {
    return 0.0
  }
  let count = if approximate.length() < exact.length() {
    approximate.length()
  } else {
    exact.length()
  }
  let mut total = 0.0
  for i = 0; i < count; i = i + 1 {
    total = total + recall_at_k(approximate[i], exact[i], k)
  }
  total / count.to_double()
}