///|
/// Aggregate result for CDS translation validation in a single record.
pub struct TranslationAuditSummary {
  accession : String
  cds_count : Int
  checked_count : Int
  passed_count : Int
  failed_count : Int
  missing_translation_count : Int
  total_nucleotide_bases : Int
} derive(Debug, Eq)

///|
/// Summarize every CDS, including entries that do not provide `/translation`.
pub fn translation_audit(record : Record) -> TranslationAuditSummary {
  let checks = record.check_cds_translations()
  let cds = record.features.filter(fn(feature) { feature.key == "CDS" })
  let checked = checks.length()
  let passed = checks.fold(init=0, fn(total, item) {
    total + (if item.ok { 1 } else { 0 })
  })
  let bases = cds.fold(init=0, fn(total, feature) {
    total + record.sequence_for(feature).length()
  })
  {
    accession: record.accession,
    cds_count: cds.length(),
    checked_count: checked,
    passed_count: passed,
    failed_count: checked - passed,
    missing_translation_count: cds.length() - checked,
    total_nucleotide_bases: bases,
  }
}

///|
/// Return false when any supplied translation disagrees with extracted DNA.
pub fn translations_are_consistent(record : Record) -> Bool {
  translation_audit(record).failed_count == 0
}

///|
/// Return the checked CDS findings as stable JSON objects.
pub fn translation_checks_json(record : Record) -> String {
  "[" +
  record
  .check_cds_translations()
  .map(fn(item) {
    "{\"feature_index\":" +
    item.feature_index.to_string() +
    ",\"location\":" +
    json_string(item.location) +
    ",\"expected\":" +
    json_string(item.expected) +
    ",\"actual\":" +
    json_string(item.actual) +
    ",\"ok\":" +
    item.ok.to_string() +
    "}"
  })
  .join(",") +
  "]"
}

///|
/// Render one row per CDS for human review and spreadsheet import.
pub fn TranslationAuditSummary::to_tsv(
  self : TranslationAuditSummary,
) -> String {
  "accession\tcds_count\tchecked\tpassed\tfailed\tmissing\tnucleotide_bases\n" +
  self.accession +
  "\t" +
  self.cds_count.to_string() +
  "\t" +
  self.checked_count.to_string() +
  "\t" +
  self.passed_count.to_string() +
  "\t" +
  self.failed_count.to_string() +
  "\t" +
  self.missing_translation_count.to_string() +
  "\t" +
  self.total_nucleotide_bases.to_string() +
  "\n"
}

///|
pub fn TranslationAuditSummary::to_json_string(
  self : TranslationAuditSummary,
) -> String {
  "{\"accession\":" +
  json_string(self.accession) +
  ",\"cds_count\":" +
  self.cds_count.to_string() +
  ",\"checked_count\":" +
  self.checked_count.to_string() +
  ",\"passed_count\":" +
  self.passed_count.to_string() +
  ",\"failed_count\":" +
  self.failed_count.to_string() +
  ",\"missing_translation_count\":" +
  self.missing_translation_count.to_string() +
  ",\"total_nucleotide_bases\":" +
  self.total_nucleotide_bases.to_string() +
  "}"
}

///|
/// Compute the proportion of supplied translations that passed validation.
pub fn translation_pass_rate(record : Record) -> Double {
  let summary = translation_audit(record)
  if summary.checked_count == 0 {
    100.0
  } else {
    summary.passed_count.to_double() * 100.0 / summary.checked_count.to_double()
  }
}

///|
/// Summarize translation checks across a multi-record batch.
pub fn batch_translation_audits(
  records : Array[Record],
) -> Array[TranslationAuditSummary] {
  records.map(translation_audit)
}

///|
/// Count all CDS features with a supplied translation in a batch.
pub fn batch_checked_translation_count(records : Array[Record]) -> Int {
  batch_translation_audits(records).fold(init=0, fn(total, item) {
    total + item.checked_count
  })
}

///|
/// Count all failed CDS translation checks in a batch.
pub fn batch_failed_translation_count(records : Array[Record]) -> Int {
  batch_translation_audits(records).fold(init=0, fn(total, item) {
    total + item.failed_count
  })
}

///|
/// Render only failed translation checks, making review output concise.
pub fn failed_translation_checks_tsv(record : Record) -> String {
  let out = StringBuilder()
  out.write_string("feature_index\tlocation\texpected\tactual\n")
  for check in record.check_cds_translations() {
    if !check.ok {
      out.write_string(check.feature_index.to_string())
      out.write_char('\t')
      out.write_string(check.location)
      out.write_char('\t')
      out.write_string(check.expected)
      out.write_char('\t')
      out.write_string(check.actual)
      out.write_char('\n')
    }
  }
  out.to_string()
}

///|
/// Return a compact batch status string for monitoring jobs.
pub fn batch_translation_status(records : Array[Record]) -> String {
  let checked = batch_checked_translation_count(records)
  let failed = batch_failed_translation_count(records)
  let status = if failed == 0 { "ok" } else { "failed" }
  "status=" +
  status +
  " records=" +
  records.length().to_string() +
  " checked=" +
  checked.to_string() +
  " failed=" +
  failed.to_string()
}

///|
/// Select records whose CDS annotations are fully self-consistent.
pub fn records_with_consistent_translations(
  records : Array[Record],
) -> Array[Record] {
  records.filter(translations_are_consistent)
}

///|
/// Return the total amount of nucleotide material represented by CDS checks.
pub fn batch_coding_bases(records : Array[Record]) -> Int {
  batch_translation_audits(records).fold(init=0, fn(total, item) {
    total + item.total_nucleotide_bases
  })
}