///|
pub fn align_document(
  document : CorpusDocument,
  options? : AlignOptions = default_options(),
) -> CorpusResult {
  let report = align(document.source, document.target, options~)
  let source_units = segment_text(document.source, options~)
  let target_units = segment_text(document.target, options~)
  let anchors = match_anchors(source_units, target_units)
  { id: document.id, report, quality: assess_quality(report, anchors~) }
}

///|
/// Align every document in a batch while preserving input order.
pub fn align_corpus(
  documents : Array[CorpusDocument],
  options? : AlignOptions = default_options(),
) -> Array[CorpusResult] {
  let results = []
  for document in documents {
    results.push(align_document(document, options~))
  }
  results
}

///|
/// Summarize batch output for CI and release notes.
pub fn summarize_corpus(results : Array[CorpusResult]) -> CorpusSummary {
  let mut source_units = 0
  let mut target_units = 0
  let mut pairs = 0
  let mut f1_total = 0.0
  let mut score_total = 0.0
  let mut warnings = 0
  let mut issues = 0
  for result in results {
    source_units += result.report.source_count
    target_units += result.report.target_count
    pairs += result.report.pairs.length()
    f1_total += if result.quality.source_units == result.quality.target_units {
      1.0
    } else {
      0.5
    }
    score_total += result.quality.score
    warnings += result.report.warnings.length()
    issues += result.quality.issues.length()
  }
  let count = results.length().max(1).to_double()
  {
    document_count: results.length(),
    total_source_units: source_units,
    total_target_units: target_units,
    total_pairs: pairs,
    mean_f1: f1_total / count,
    mean_quality_score: score_total / count,
    warning_count: warnings,
    issue_count: issues,
  }
}

///|
/// Filter batch results to documents that pass a quality gate.
pub fn accepted_documents(
  results : Array[CorpusResult],
  gate? : QualityGate = default_quality_gate(),
) -> Array[CorpusResult] {
  let accepted = []
  for result in results {
    if passes_quality_gate(result.quality, gate~) {
      accepted.push(result)
    }
  }
  accepted
}

///|
/// Filter batch results to documents requiring human review.
pub fn review_queue(results : Array[CorpusResult]) -> Array[CorpusResult] {
  let queue = []
  for result in results {
    if !passes_quality_gate(result.quality) {
      queue.push(result)
    }
  }
  queue
}

///|
/// Render a compact quality dashboard as CSV.
pub fn corpus_quality_csv(results : Array[CorpusResult]) -> String {
  let out = StringBuilder()
  out.write_string(
    "id,source_units,target_units,pairs,quality,confidence,source_coverage,target_coverage,issues\n",
  )
  for result in results {
    out.write_string(quality_to_csv_row(result.id, result.quality))
    out.write_char('\n')
  }
  out.to_string()
}

///|
/// Render aligned corpus pairs in a review-friendly TSV format.
pub fn corpus_to_tsv(results : Array[CorpusResult]) -> String {
  let out = StringBuilder()
  out.write_string(
    "document_id\tsource_start\tsource_end\ttarget_start\ttarget_end\tmove_kind\tscore\tsource_text\ttarget_text\n",
  )
  for result in results {
    for pair in result.report.pairs {
      let source = pair.source_text.replace_all(old="\n", new=" ⏎ ")
      let target = pair.target_text.replace_all(old="\n", new=" ⏎ ")
      out.write_string(
        "\{result.id}\t\{pair.source_start}\t\{pair.source_end}\t\{pair.target_start}\t\{pair.target_end}\t\{pair.move_kind}\t\{pair.score}\t\{source}\t\{target}\n",
      )
    }
  }
  out.to_string()
}

///|
/// Parse a minimal tab-separated corpus input. Blank and comment lines skip.
pub fn parse_corpus_tsv(text : String) -> Array[CorpusDocument] {
  let documents = []
  for line in text.replace_all(old="\r\n", new="\n").split("\n") {
    let trimmed = line.trim().to_owned()
    if trimmed.is_empty() || trimmed.has_prefix("#") {
      continue
    }
    let fields = trimmed.split("\t").to_array()
    if fields.length() >= 3 {
      documents.push(
        corpus_document(
          id=fields[0].to_owned(),
          source=fields[1].to_owned(),
          target=fields[2].to_owned(),
        ),
      )
    }
  }
  documents
}

///|
/// Validate a batch before running expensive alignment.
pub fn validate_corpus(documents : Array[CorpusDocument]) -> Array[String] {
  let errors = []
  let ids : Map[String, Bool] = Map([])
  for document in documents {
    if document.id.trim().is_empty() {
      errors.push("document id must not be empty")
    }
    if ids.contains(document.id) {
      errors.push("duplicate document id: \{document.id}")
    }
    ids[document.id] = true
    if document.source.trim().is_empty() {
      errors.push("\{document.id}: source is empty")
    }
    if document.target.trim().is_empty() {
      errors.push("\{document.id}: target is empty")
    }
  }
  errors
}

///|
/// Build a deterministic synthetic corpus used by performance tests.
pub fn synthetic_corpus(count : Int) -> Array[CorpusDocument] {
  let documents = []
  let safe_count = count.max(0).min(1000)
  for i in 0.. Map[String, Int] {
  let counts : Map[String, Int] = Map([])
  for result in results {
    for pair in result.report.pairs {
      counts[pair.move_kind] = counts.get_or_default(pair.move_kind, 0) + 1
    }
  }
  counts
}