///|
/// Add two vectors coordinate by coordinate.
pub fn vector_add(
  a : Array[Double],
  b : Array[Double],
) -> Array[Double] raise VectorError {
  if a.length() != b.length() {
    raise DimensionMismatch("Cannot add vectors with different dimensions")
  }
  if a.length() == 0 {
    raise EmptyVector
  }
  let result = Array::make(a.length(), 0.0)
  for i = 0; i < a.length(); i = i + 1 {
    result[i] = a[i] + b[i]
  }
  result
}

///|
/// Subtract one vector from another.
pub fn vector_subtract(
  a : Array[Double],
  b : Array[Double],
) -> Array[Double] raise VectorError {
  if a.length() != b.length() {
    raise DimensionMismatch("Cannot subtract vectors with different dimensions")
  }
  if a.length() == 0 {
    raise EmptyVector
  }
  let result = Array::make(a.length(), 0.0)
  for i = 0; i < a.length(); i = i + 1 {
    result[i] = a[i] - b[i]
  }
  result
}

///|
/// Scale a vector by a scalar.
pub fn vector_scale(
  vector : Array[Double],
  scalar : Double,
) -> Array[Double] raise VectorError {
  if vector.length() == 0 {
    raise EmptyVector
  }
  let result = Array::make(vector.length(), 0.0)
  for i = 0; i < vector.length(); i = i + 1 {
    result[i] = vector[i] * scalar
  }
  result
}

///|
/// Compute the arithmetic mean of a non-empty vector set.
pub fn vector_mean(
  vectors : Array[Array[Double]],
) -> Array[Double] raise VectorError {
  let summary = summarize_vectors(vectors)
  summary.mean_vector
}

///|
/// Compute a centroid from selected documents.
pub fn document_centroid(
  docs : Array[Document],
) -> Array[Double] raise VectorError {
  let vectors = []
  for doc in docs {
    vectors.push(doc.vector)
  }
  vector_mean(vectors)
}

///|
/// Compute the sum of squared coordinate error around a centroid.
pub fn within_cluster_sse(
  docs : Array[Document],
  centroid : Array[Double],
) -> Double raise VectorError {
  if docs.length() == 0 {
    return 0.0
  }
  let mut total = 0.0
  for doc in docs {
    let diff = vector_subtract(doc.vector, centroid)
    total = total + dot_product(diff, diff)
  }
  total
}

///|
/// Return the index of the largest coordinate, or no value for an empty vector.
pub fn argmax(vector : Array[Double]) -> Int? {
  if vector.length() == 0 {
    return None
  }
  let mut best = 0
  for i = 1; i < vector.length(); i = i + 1 {
    if vector[i] > vector[best] {
      best = i
    }
  }
  Some(best)
}

///|
/// Return the index of the smallest coordinate, or no value for an empty vector.
pub fn argmin(vector : Array[Double]) -> Int? {
  if vector.length() == 0 {
    return None
  }
  let mut best = 0
  for i = 1; i < vector.length(); i = i + 1 {
    if vector[i] < vector[best] {
      best = i
    }
  }
  Some(best)
}

///|
/// Clamp every coordinate to an inclusive interval.
pub fn vector_clip(
  vector : Array[Double],
  low : Double,
  high : Double,
) -> Array[Double] {
  let result = Array::make(vector.length(), 0.0)
  for i = 0; i < vector.length(); i = i + 1 {
    result[i] = if vector[i] < low {
      low
    } else if vector[i] > high {
      high
    } else {
      vector[i]
    }
  }
  result
}

///|
/// Return a matrix of pairwise distances for small diagnostic datasets.
pub fn pairwise_distances(
  vectors : Array[Array[Double]],
  metric : DistanceMetric,
) -> Array[Array[Double]] raise VectorError {
  let matrix = Array::make(vectors.length(), [])
  for i = 0; i < vectors.length(); i = i + 1 {
    let row = Array::make(vectors.length(), 0.0)
    for j = 0; j < vectors.length(); j = j + 1 {
      row[j] = calculate_distance(vectors[i], vectors[j], metric)
    }
    matrix[i] = row
  }
  matrix
}