///|
/// Parse JSONL documents with `id` or `_id`, optional `title`, and `text`.
/// The complete batch is validated before the caller changes its index.
pub fn parse_document_jsonl(
  input : String,
) -> Result[Array[(String, String)], String] {
  let documents : Array[(String, String)] = []
  let seen : Map[String, Bool] = Map([])
  let mut line_number = 0
  for raw_line in input.split("\n") {
    line_number = line_number + 1
    let line = raw_line.trim().to_owned()
    if line == "" {
      continue
    }
    let parsed = @json.parse(line) catch {
      _ => return Err("line \{line_number}: invalid JSON")
    }
    guard parsed is Object(fields) else {
      return Err("line \{line_number}: expected an object")
    }
    let id = match fields.get("id") {
      Some(String(value)) => value
      _ =>
        match fields.get("_id") {
          Some(String(value)) => value
          _ => return Err("line \{line_number}: missing id or _id")
        }
    }
    if id == "" {
      return Err("line \{line_number}: empty document ID")
    }
    if seen.contains(id) {
      return Err("line \{line_number}: duplicate document ID \{id}")
    }
    let body = match fields.get("text") {
      Some(String(value)) => value
      _ => return Err("line \{line_number}: missing text")
    }
    let title = match fields.get("title") {
      Some(String(value)) => value
      _ => ""
    }
    let content = if title == "" { body } else { title + "\n\n" + body }
    seen[id] = true
    documents.push((id, content))
  }
  if documents.is_empty() {
    Err("no documents in JSONL input")
  } else {
    Ok(documents)
  }
}