///|
/// A record in a multi-athlete or multi-session batch.
pub(all) struct BatchRecord {
  subject_id : String
  session_id : String
  date : String
  rmssd : Double
  sdnn : Double
  mean_hr : Double
  quality_ratio : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Cohort-level distribution and quality summary.
pub(all) struct CohortSummary {
  record_count : Int
  subject_count : Int
  rmssd_mean : Double
  rmssd_median : Double
  rmssd_sd : Double
  rmssd_q1 : Double
  rmssd_q3 : Double
  mean_hr : Double
  mean_quality : Double
  low_quality_count : Int
  strongest_subject : String
  weakest_subject : String
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Subject-specific aggregate used in cohort comparison.
pub(all) struct SubjectSummary {
  subject_id : String
  record_count : Int
  rmssd_mean : Double
  rmssd_sd : Double
  quality_mean : Double
  percentile : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Return a stable subject index in an aggregate array.
fn subject_index(subjects : Array[SubjectSummary], subject_id : String) -> Int? {
  for i in 0.. Array[SubjectSummary] {
  let result = []
  for record in records {
    match subject_index(result, record.subject_id) {
      Some(index) => {
        let current = result[index]
        let count = current.record_count + 1
        let new_mean = (
            current.rmssd_mean * current.record_count.to_double() + record.rmssd
          ) /
          count.to_double()
        let delta = record.rmssd - current.rmssd_mean
        let new_variance = if count <= 1 {
          0.0
        } else {
          (
            current.rmssd_sd * current.rmssd_sd * (count - 2).to_double() +
            delta * (record.rmssd - new_mean)
          ) /
          (count - 1).to_double()
        }
        result[index] = {
          subject_id: current.subject_id,
          record_count: count,
          rmssd_mean: new_mean,
          rmssd_sd: if new_variance < 0.0 {
            0.0
          } else {
            new_variance.sqrt()
          },
          quality_mean: (
            current.quality_mean * current.record_count.to_double() +
            record.quality_ratio
          ) /
          count.to_double(),
          percentile: 0.0,
        }
      }
      None =>
        result.push({
          subject_id: record.subject_id,
          record_count: 1,
          rmssd_mean: record.rmssd,
          rmssd_sd: 0.0,
          quality_mean: record.quality_ratio,
          percentile: 0.0,
        })
    }
  }
  result
}

///|
/// Calculate percentile rank using a cohort of scalar values.
pub fn percentile_rank(values : Array[Double], value : Double) -> Double {
  if values.length() == 0 {
    return 0.0
  }
  let mut below = 0
  let mut equal = 0
  for candidate in values {
    if candidate < value {
      below += 1
    }
    if candidate == value {
      equal += 1
    }
  }
  (below.to_double() + 0.5 * equal.to_double()) /
  values.length().to_double() *
  100.0
}

///|
/// Aggregate all records and attach subject percentile ranks.
pub fn summarize_cohort(records : Array[BatchRecord]) -> CohortSummary {
  let subjects = summarize_subjects(records)
  let rmssd_values = []
  let hr_values = []
  let qualities = []
  let mut low_quality = 0
  let mut best_subject = ""
  let mut worst_subject = ""
  let mut best_value = -1.0
  let mut worst_value = 1.0e18
  for record in records {
    rmssd_values.push(record.rmssd)
    hr_values.push(record.mean_hr)
    qualities.push(record.quality_ratio)
    if record.quality_ratio < 0.75 {
      low_quality += 1
    }
  }
  for subject in subjects {
    if subject.rmssd_mean > best_value {
      best_value = subject.rmssd_mean
      best_subject = subject.subject_id
    }
    if subject.rmssd_mean < worst_value {
      worst_value = subject.rmssd_mean
      worst_subject = subject.subject_id
    }
  }
  let summary = summarize_distribution(rmssd_values)
  {
    record_count: records.length(),
    subject_count: subjects.length(),
    rmssd_mean: summary.mean,
    rmssd_median: summary.median,
    rmssd_sd: summary.standard_deviation,
    rmssd_q1: summary.q1,
    rmssd_q3: summary.q3,
    mean_hr: mean_value(hr_values),
    mean_quality: mean_value(qualities),
    low_quality_count: low_quality,
    strongest_subject: best_subject,
    weakest_subject: worst_subject,
  }
}

///|
/// Compare one subject's mean RMSSD with a cohort distribution.
pub fn compare_subject_to_cohort(
  subject : SubjectSummary,
  cohort : Array[SubjectSummary],
) -> SubjectSummary {
  let values = []
  for candidate in cohort {
    values.push(candidate.rmssd_mean)
  }
  {
    subject_id: subject.subject_id,
    record_count: subject.record_count,
    rmssd_mean: subject.rmssd_mean,
    rmssd_sd: subject.rmssd_sd,
    quality_mean: subject.quality_mean,
    percentile: percentile_rank(values, subject.rmssd_mean),
  }
}

///|
/// Return the number of records that meet a quality threshold.
pub fn count_quality_records(
  records : Array[BatchRecord],
  threshold : Double,
) -> Int {
  let mut count = 0
  for record in records {
    if record.quality_ratio >= threshold {
      count += 1
    }
  }
  count
}

///|
/// Return a deterministic cohort feature vector for dashboards.
pub fn cohort_feature_vector(summary : CohortSummary) -> Array[Double] {
  [
    summary.record_count.to_double(),
    summary.subject_count.to_double(),
    summary.rmssd_mean,
    summary.rmssd_median,
    summary.rmssd_sd,
    summary.rmssd_q1,
    summary.rmssd_q3,
    summary.mean_hr,
    summary.mean_quality,
    summary.low_quality_count.to_double(),
  ]
}