///|
/// A compact metrics snapshot used by CLI and batch reporting.
pub struct RecordMetrics {
  accession : String
  length : Int
  gc_percent : Double
  entropy : Double
  feature_count : Int
  feature_coverage : Int
  feature_coverage_percent : Double
  cds_count : Int
  gene_count : Int
  translation_count : Int
  remote_locations : Int
  fuzzy_locations : Int
  between_locations : Int
  validation_errors : Int
  validation_warnings : Int
} derive(Debug, Eq)

///|
pub fn record_metrics(record : Record) -> RecordMetrics {
  let composition = composition_report(record.origin)
  {
    accession: record.accession,
    length: composition.length,
    gc_percent: composition.gc_percent,
    entropy: composition.entropy,
    feature_count: record.features.length(),
    feature_coverage: feature_coverage(record),
    feature_coverage_percent: feature_coverage_percent(record),
    cds_count: record_cds_count(record),
    gene_count: record_gene_count(record),
    translation_count: record_translations(record).length(),
    remote_locations: record_remote_accession_count(record),
    fuzzy_locations: record_fuzzy_location_count(record),
    between_locations: record_between_location_count(record),
    validation_errors: record_error_count(record),
    validation_warnings: record_warning_count(record),
  }
}

///|
pub fn RecordMetrics::to_json_string(self : RecordMetrics) -> String {
  "{\"accession\":" +
  json_string(self.accession) +
  ",\"length\":" +
  self.length.to_string() +
  ",\"gc_percent\":" +
  self.gc_percent.to_string() +
  ",\"entropy\":" +
  self.entropy.to_string() +
  ",\"feature_count\":" +
  self.feature_count.to_string() +
  ",\"feature_coverage\":" +
  self.feature_coverage.to_string() +
  ",\"feature_coverage_percent\":" +
  self.feature_coverage_percent.to_string() +
  ",\"cds_count\":" +
  self.cds_count.to_string() +
  ",\"gene_count\":" +
  self.gene_count.to_string() +
  ",\"translation_count\":" +
  self.translation_count.to_string() +
  ",\"remote_locations\":" +
  self.remote_locations.to_string() +
  ",\"fuzzy_locations\":" +
  self.fuzzy_locations.to_string() +
  ",\"between_locations\":" +
  self.between_locations.to_string() +
  ",\"validation_errors\":" +
  self.validation_errors.to_string() +
  ",\"validation_warnings\":" +
  self.validation_warnings.to_string() +
  "}"
}

///|
pub fn RecordMetrics::to_text(self : RecordMetrics) -> String {
  "accession=" +
  self.accession +
  " length=" +
  self.length.to_string() +
  " gc=" +
  self.gc_percent.to_string() +
  " entropy=" +
  self.entropy.to_string() +
  " features=" +
  self.feature_count.to_string() +
  " coverage=" +
  self.feature_coverage_percent.to_string() +
  "%" +
  " cds=" +
  self.cds_count.to_string() +
  " genes=" +
  self.gene_count.to_string() +
  " errors=" +
  self.validation_errors.to_string()
}

///|
pub fn record_metrics_json(record : Record) -> String {
  record_metrics(record).to_json_string()
}

///|
pub fn record_metrics_text(record : Record) -> String {
  record_metrics(record).to_text()
}

///|
pub fn metric_gc(record : Record) -> Double {
  record_metrics(record).gc_percent
}

///|
pub fn metric_entropy(record : Record) -> Double {
  record_metrics(record).entropy
}

///|
pub fn metric_length(record : Record) -> Int {
  record_metrics(record).length
}

///|
pub fn metric_feature_count(record : Record) -> Int {
  record_metrics(record).feature_count
}

///|
pub fn metric_cds_count(record : Record) -> Int {
  record_metrics(record).cds_count
}

///|
pub fn metric_gene_count(record : Record) -> Int {
  record_metrics(record).gene_count
}

///|
pub fn metric_coverage(record : Record) -> Int {
  record_metrics(record).feature_coverage
}

///|
pub fn metric_coverage_percent(record : Record) -> Double {
  record_metrics(record).feature_coverage_percent
}

///|
pub fn metric_remote_count(record : Record) -> Int {
  record_metrics(record).remote_locations
}

///|
pub fn metric_fuzzy_count(record : Record) -> Int {
  record_metrics(record).fuzzy_locations
}

///|
pub fn metric_between_count(record : Record) -> Int {
  record_metrics(record).between_locations
}

///|
pub fn metric_error_count(record : Record) -> Int {
  record_metrics(record).validation_errors
}

///|
pub fn metric_warning_count(record : Record) -> Int {
  record_metrics(record).validation_warnings
}

///|
pub fn batch_metrics(records : Array[Record]) -> Array[RecordMetrics] {
  records.map(record_metrics)
}

///|
pub fn batch_metrics_json(records : Array[Record]) -> String {
  array_json(batch_metrics(records).map(fn(metric) { metric.to_json_string() }))
}

///|
pub fn batch_metric_mean_gc(records : Array[Record]) -> Double {
  if records.length() == 0 {
    0.0
  } else {
    batch_metrics(records).fold(init=0.0, fn(total, metric) {
      total + metric.gc_percent
    }) /
    records.length().to_double()
  }
}

///|
pub fn batch_metric_mean_length(records : Array[Record]) -> Double {
  if records.length() == 0 {
    0.0
  } else {
    records
    .fold(init=0, fn(total, record) { total + record.origin.length() })
    .to_double() /
    records.length().to_double()
  }
}

///|
pub fn batch_metric_max_length(records : Array[Record]) -> Int {
  records.fold(init=0, fn(best, record) {
    Int::max(best, record.origin.length())
  })
}

///|
pub fn batch_metric_min_length(records : Array[Record]) -> Int {
  if records.length() == 0 {
    0
  } else {
    records.fold(init=records[0].origin.length(), fn(best, record) {
      Int::min(best, record.origin.length())
    })
  }
}

///|
pub fn batch_metric_total_features(records : Array[Record]) -> Int {
  records.fold(init=0, fn(total, record) { total + record.features.length() })
}

///|
pub fn batch_metric_total_cds(records : Array[Record]) -> Int {
  records.fold(init=0, fn(total, record) { total + record_cds_count(record) })
}

///|
pub fn batch_metric_total_genes(records : Array[Record]) -> Int {
  records.fold(init=0, fn(total, record) { total + record_gene_count(record) })
}

///|
pub fn batch_metric_valid(records : Array[Record]) -> Int {
  records.fold(init=0, fn(total, record) {
    total + (if record_is_valid(record) { 1 } else { 0 })
  })
}

///|
pub fn batch_metric_invalid(records : Array[Record]) -> Int {
  records.length() - batch_metric_valid(records)
}

///|
pub fn batch_metric_report(records : Array[Record]) -> String {
  "records=" +
  records.length().to_string() +
  " mean_length=" +
  batch_metric_mean_length(records).to_string() +
  " mean_gc=" +
  batch_metric_mean_gc(records).to_string() +
  " max_length=" +
  batch_metric_max_length(records).to_string() +
  " min_length=" +
  batch_metric_min_length(records).to_string() +
  " features=" +
  batch_metric_total_features(records).to_string() +
  " cds=" +
  batch_metric_total_cds(records).to_string() +
  " genes=" +
  batch_metric_total_genes(records).to_string() +
  " valid=" +
  batch_metric_valid(records).to_string() +
  " invalid=" +
  batch_metric_invalid(records).to_string()
}