///|
/// Privacy-friendly cohort comparison primitives.
/// The module accepts already de-identified observations and returns ranges,
/// ranks, and quality-aware flags without retaining personal identifiers.
pub(all) struct CohortObservation {
  reference : String
  rmssd_ms : Double
  mean_rr_ms : Double
  resting_hr_bpm : Double
  readiness_score : Double
  training_load : Double
  signal_quality : Double
  age_band : String
  activity_band : String
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct CohortMetricRange {
  metric : String
  sample_count : Int
  minimum : Double
  lower_quartile : Double
  median : Double
  upper_quartile : Double
  maximum : Double
  mean : Double
  standard_deviation : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct CohortRank {
  reference : String
  metric : String
  value : Double
  percentile : Double
  z_score : Double
  quality_adjusted : Bool
  label : String
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct CohortComparison {
  reference : String
  quality_ok : Bool
  ranks : Array[CohortRank]
  flags : Array[String]
  similarity_score : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct CohortComparisonSummary {
  observations : Int
  quality_eligible : Int
  quality_ratio : Double
  ranges : Array[CohortMetricRange]
  comparisons : Array[CohortComparison]
  feature_vector : Array[Double]
} derive(FromJson, ToJson, Debug, Eq)

///|
fn cohort_bound(value : Double, low : Double, high : Double) -> Double {
  if value.is_nan() || value.is_inf() {
    low
  } else {
    value.clamp(min=low, max=high)
  }
}

///|
pub fn make_cohort_observation(
  reference : String,
  rmssd_ms : Double,
  mean_rr_ms : Double,
  resting_hr_bpm : Double,
  readiness_score : Double,
  training_load : Double,
  signal_quality : Double,
  age_band : String,
  activity_band : String,
) -> CohortObservation {
  {
    reference,
    rmssd_ms: cohort_bound(rmssd_ms, 0.0, 1000.0),
    mean_rr_ms: cohort_bound(mean_rr_ms, 0.0, 3000.0),
    resting_hr_bpm: cohort_bound(resting_hr_bpm, 0.0, 240.0),
    readiness_score: cohort_bound(readiness_score, 0.0, 100.0),
    training_load: cohort_bound(training_load, 0.0, 100000.0),
    signal_quality: cohort_bound(signal_quality, 0.0, 1.0),
    age_band,
    activity_band,
  }
}

///|
pub fn cohort_observation_is_eligible(
  observation : CohortObservation,
  quality_floor : Double,
) -> Bool {
  observation.reference.length() > 0 &&
  observation.rmssd_ms > 0.0 &&
  observation.mean_rr_ms > 0.0 &&
  observation.resting_hr_bpm > 0.0 &&
  observation.signal_quality >= quality_floor
}

///|
fn cohort_metric_values(
  observations : Array[CohortObservation],
  metric : String,
) -> Array[Double] {
  match metric {
    "rmssd" => observations.map(item => item.rmssd_ms)
    "mean_rr" => observations.map(item => item.mean_rr_ms)
    "resting_hr" => observations.map(item => item.resting_hr_bpm)
    "readiness" => observations.map(item => item.readiness_score)
    "training_load" => observations.map(item => item.training_load)
    "quality" => observations.map(item => item.signal_quality)
    _ => []
  }
}

///|
fn cohort_min(values : Array[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    let mut result = values[0]
    for value in values {
      if value < result {
        result = value
      }
    }
    result
  }
}

///|
fn cohort_max(values : Array[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    let mut result = values[0]
    for value in values {
      if value > result {
        result = value
      }
    }
    result
  }
}

///|
pub fn cohort_metric_range(
  observations : Array[CohortObservation],
  metric : String,
) -> CohortMetricRange {
  let values = cohort_metric_values(observations, metric)
  {
    metric,
    sample_count: values.length(),
    minimum: cohort_min(values),
    lower_quartile: quantile_value(values, 0.25),
    median: median_value(values),
    upper_quartile: quantile_value(values, 0.75),
    maximum: cohort_max(values),
    mean: mean_value(values),
    standard_deviation: standard_deviation(values),
  }
}

///|
fn cohort_percentile(values : Array[Double], value : Double) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    let lower = values.filter(item => item <= value).length()
    lower.to_double() / values.length().to_double()
  }
}

///|
fn cohort_z_score(values : Array[Double], value : Double) -> Double {
  let sd = standard_deviation(values)
  if sd <= 0.000001 {
    0.0
  } else {
    (value - mean_value(values)) / sd
  }
}

///|
fn cohort_label(percentile : Double, higher_is_better : Bool) -> String {
  let score = if higher_is_better { percentile } else { 1.0 - percentile }
  if score >= 0.85 {
    "high"
  } else if score >= 0.40 {
    "typical"
  } else {
    "low"
  }
}

///|
pub fn cohort_rank(
  observation : CohortObservation,
  observations : Array[CohortObservation],
  metric : String,
  higher_is_better : Bool,
  quality_floor : Double,
) -> CohortRank {
  let eligible = observations.filter(item => {
    cohort_observation_is_eligible(item, quality_floor)
  })
  let values = cohort_metric_values(eligible, metric)
  let value = match metric {
    "rmssd" => observation.rmssd_ms
    "mean_rr" => observation.mean_rr_ms
    "resting_hr" => observation.resting_hr_bpm
    "readiness" => observation.readiness_score
    "training_load" => observation.training_load
    "quality" => observation.signal_quality
    _ => 0.0
  }
  let percentile = cohort_percentile(values, value)
  {
    reference: observation.reference,
    metric,
    value,
    percentile,
    z_score: cohort_z_score(values, value),
    quality_adjusted: observation.signal_quality >= quality_floor,
    label: cohort_label(percentile, higher_is_better),
  }
}

///|
pub fn cohort_compare(
  observation : CohortObservation,
  observations : Array[CohortObservation],
  quality_floor : Double,
) -> CohortComparison {
  let quality_ok = cohort_observation_is_eligible(observation, quality_floor)
  let metrics = [
    ("rmssd", true),
    ("mean_rr", true),
    ("resting_hr", false),
    ("readiness", true),
    ("training_load", false),
  ]
  let ranks = []
  for pair in metrics {
    ranks.push(
      cohort_rank(observation, observations, pair.0, pair.1, quality_floor),
    )
  }
  let flags = []
  for rank in ranks {
    if rank.label == "low" {
      flags.push("\{rank.metric} is below the comparison range")
    } else if rank.label == "high" && rank.metric == "training_load" {
      flags.push("training load is above the comparison range")
    }
  }
  if !quality_ok {
    flags.push("observation is below the quality floor")
  }
  let similarity = if ranks.length() == 0 {
    0.0
  } else {
    mean_value(
      ranks.map(rank => {
        (1.0 - (rank.percentile - 0.5).abs() * 2.0).clamp(min=0.0, max=1.0)
      }),
    )
  }
  {
    reference: observation.reference,
    quality_ok,
    ranks,
    flags,
    similarity_score: similarity,
  }
}

///|
pub fn cohort_ranges(
  observations : Array[CohortObservation],
  quality_floor : Double,
) -> Array[CohortMetricRange] {
  let eligible = observations.filter(item => {
    cohort_observation_is_eligible(item, quality_floor)
  })
  [
    cohort_metric_range(eligible, "rmssd"),
    cohort_metric_range(eligible, "mean_rr"),
    cohort_metric_range(eligible, "resting_hr"),
    cohort_metric_range(eligible, "readiness"),
    cohort_metric_range(eligible, "training_load"),
    cohort_metric_range(eligible, "quality"),
  ]
}

///|
pub fn build_cohort_summary(
  observations : Array[CohortObservation],
  quality_floor : Double,
) -> CohortComparisonSummary {
  let eligible = observations.filter(item => {
    cohort_observation_is_eligible(item, quality_floor)
  })
  let comparisons = []
  for observation in observations {
    comparisons.push(cohort_compare(observation, observations, quality_floor))
  }
  let ranges = cohort_ranges(observations, quality_floor)
  let feature_vector = [
    observations.length().to_double(),
    eligible.length().to_double(),
    if observations.length() == 0 {
      0.0
    } else {
      eligible.length().to_double() / observations.length().to_double()
    },
  ]
  for range in ranges {
    feature_vector.push(range.median)
    feature_vector.push(range.standard_deviation)
  }
  {
    observations: observations.length(),
    quality_eligible: eligible.length(),
    quality_ratio: if observations.length() == 0 {
      0.0
    } else {
      eligible.length().to_double() / observations.length().to_double()
    },
    ranges,
    comparisons,
    feature_vector,
  }
}

///|
pub fn cohort_summary_is_usable(summary : CohortComparisonSummary) -> Bool {
  summary.observations > 0 &&
  summary.quality_eligible > 0 &&
  summary.ranges.length() >= 3
}

///|
pub fn cohort_summary_csv(summary : CohortComparisonSummary) -> String {
  let grid = [
    [
      "metric", "sample_count", "minimum", "lower_quartile", "median", "upper_quartile",
      "maximum", "mean", "standard_deviation",
    ],
  ]
  for range in summary.ranges {
    grid.push([
      range.metric,
      range.sample_count.to_string(),
      range.minimum.to_string(),
      range.lower_quartile.to_string(),
      range.median.to_string(),
      range.upper_quartile.to_string(),
      range.maximum.to_string(),
      range.mean.to_string(),
      range.standard_deviation.to_string(),
    ])
  }
  to_csv(grid)
}

///|
pub fn cohort_comparisons_csv(comparisons : Array[CohortComparison]) -> String {
  let grid = [
    [
      "reference", "metric", "value", "percentile", "z_score", "label", "flagged",
    ],
  ]
  for comparison in comparisons {
    for rank in comparison.ranks {
      grid.push([
        comparison.reference,
        rank.metric,
        rank.value.to_string(),
        rank.percentile.to_string(),
        rank.z_score.to_string(),
        rank.label,
        comparison.flags.length().to_string(),
      ])
    }
  }
  to_csv(grid)
}

///|
pub fn cohort_findings(comparison : CohortComparison) -> Array[String] {
  comparison.flags
}

///|
pub fn cohort_percentile_for(
  summary : CohortComparisonSummary,
  reference : String,
  metric : String,
) -> Double {
  for comparison in summary.comparisons {
    if comparison.reference == reference {
      for rank in comparison.ranks {
        if rank.metric == metric {
          return rank.percentile
        }
      }
    }
  }
  0.0
}

///|
pub fn cohort_similarity_for(
  summary : CohortComparisonSummary,
  reference : String,
) -> Double {
  for comparison in summary.comparisons {
    if comparison.reference == reference {
      return comparison.similarity_score
    }
  }
  0.0
}

///|
pub fn cohort_quality_adjusted_score(
  observation : CohortObservation,
  summary : CohortComparisonSummary,
) -> Double {
  let similarity = cohort_similarity_for(summary, observation.reference)
  similarity * observation.signal_quality
}

///|
pub fn cohort_training_load_flag(
  observation : CohortObservation,
  summary : CohortComparisonSummary,
) -> Bool {
  cohort_percentile_for(summary, observation.reference, "training_load") >= 0.85
}

///|
pub fn cohort_recovery_flag(
  observation : CohortObservation,
  summary : CohortComparisonSummary,
) -> Bool {
  cohort_percentile_for(summary, observation.reference, "readiness") <= 0.15
}

///|
pub fn cohort_range_row(range : CohortMetricRange) -> Array[String] {
  [
    range.metric,
    range.sample_count.to_string(),
    range.minimum.to_string(),
    range.median.to_string(),
    range.maximum.to_string(),
    range.mean.to_string(),
    range.standard_deviation.to_string(),
  ]
}

///|
pub fn cohort_summary_feature_vector(
  summary : CohortComparisonSummary,
) -> Array[Double] {
  let result = []
  for value in summary.feature_vector {
    result.push(value)
  }
  result.push(summary.comparisons.length().to_double())
  result.push(
    summary.comparisons
    .filter(item => item.flags.length() > 0)
    .length()
    .to_double(),
  )
  result
}