///|
/// Copy a corpus while retaining only documents with ids in a half-open range.
pub fn slice_documents(
  docs : Array[Document],
  start : Int,
  end : Int,
) -> Array[Document] {
  let first = if start < 0 { 0 } else { start }
  let last = if end < first {
    first
  } else if end > docs.length() {
    docs.length()
  } else {
    end
  }
  let result = []
  for i = first; i < last; i = i + 1 {
    result.push(docs[i])
  }
  result
}

///|
/// Split a corpus into a deterministic prefix and suffix.
pub fn split_documents(
  docs : Array[Document],
  prefix_count : Int,
) -> (Array[Document], Array[Document]) {
  let prefix = if prefix_count < 0 {
    0
  } else if prefix_count > docs.length() {
    docs.length()
  } else {
    prefix_count
  }
  (
    slice_documents(docs, 0, prefix),
    slice_documents(docs, prefix, docs.length()),
  )
}

///|
/// Return documents whose ids start with a prefix.
pub fn filter_id_prefix(
  docs : Array[Document],
  prefix : String,
) -> Array[Document] {
  let result = []
  for doc in docs {
    if doc.id.has_prefix(prefix) {
      result.push(doc)
    }
  }
  result
}

///|
/// Remove duplicate ids, keeping the first document for each id.
pub fn deduplicate_documents(docs : Array[Document]) -> Array[Document] {
  let seen = Map([])
  let result = []
  for doc in docs {
    if !seen.contains(doc.id) {
      seen.set(doc.id, true)
      result.push(doc)
    }
  }
  result
}

///|
/// Return the vector norms in document order.
pub fn document_norms(
  docs : Array[Document],
) -> Array[Double] raise VectorError {
  let norms = []
  for doc in docs {
    norms.push(vector_norm(doc.vector))
  }
  norms
}

///|
/// Return a copy with all vectors transformed by a scalar.
pub fn scale_documents(
  docs : Array[Document],
  scalar : Double,
) -> Array[Document] raise VectorError {
  let result = []
  for doc in docs {
    result.push(
      Document::new(doc.id, vector_scale(doc.vector, scalar), doc.metadata),
    )
  }
  result
}

///|
/// Return all vectors as independent arrays for numerical processing.
pub fn document_vectors(docs : Array[Document]) -> Array[Array[Double]] {
  let result = []
  for doc in docs {
    let vector = Array::make(doc.vector.length(), 0.0)
    for i = 0; i < doc.vector.length(); i = i + 1 {
      vector[i] = doc.vector[i]
    }
    result.push(vector)
  }
  result
}

///|
/// Add a deterministic ordinal metadata field to a corpus.
pub fn annotate_ordinals(
  docs : Array[Document],
  key : String,
) -> Array[Document] {
  let result = []
  for i = 0; i < docs.length(); i = i + 1 {
    result.push(set_metadata(docs[i], key, i.to_string()))
  }
  result
}