///|
/// Sleep and overnight recovery summaries built on RR and HRV measurements.

///|
/// Coarse sleep stage labels suitable for wearable summaries.
pub(all) enum SleepStage {
  Awake
  Light
  Deep
  Rem
  Unknown
} derive(FromJson, ToJson, Debug, Eq)

///|
/// One scored sleep epoch.
pub(all) struct SleepEpoch {
  start_minute : Double
  duration_minutes : Double
  stage : SleepStage
  mean_rr : Double
  rmssd : Double
  quality_ratio : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Overnight recovery summary.
pub(all) struct SleepRecoverySummary {
  total_minutes : Double
  asleep_minutes : Double
  awake_minutes : Double
  deep_minutes : Double
  rem_minutes : Double
  light_minutes : Double
  sleep_efficiency : Double
  stage_transition_count : Int
  overnight_mean_rr : Double
  overnight_rmssd : Double
  overnight_quality : Double
  recovery_delta : Double
  fragmentation_index : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Return whether an epoch contributes to sleep duration.
pub fn sleep_stage_is_asleep(stage : SleepStage) -> Bool {
  match stage {
    Awake | Unknown => false
    Light | Deep | Rem => true
  }
}

///|
/// Return a stable numeric code for a sleep stage.
pub fn sleep_stage_code(stage : SleepStage) -> Int {
  match stage {
    Awake => 0
    Light => 1
    Deep => 2
    Rem => 3
    Unknown => -1
  }
}

///|
/// Infer a coarse stage from RR-derived recovery markers.
pub fn classify_sleep_epoch(
  mean_rr : Double,
  rmssd : Double,
  movement_score : Double,
  quality_ratio : Double,
) -> SleepStage {
  if quality_ratio < 0.5 {
    return Unknown
  }
  if movement_score > 0.65 {
    return Awake
  }
  if rmssd <= 0.0 || mean_rr <= 0.0 {
    return Unknown
  }
  if rmssd > 70.0 && movement_score < 0.20 {
    Deep
  } else if movement_score > 0.35 {
    Rem
  } else {
    Light
  }
}

///|
/// Count transitions between consecutive scored epochs.
pub fn sleep_stage_transitions(epochs : Array[SleepEpoch]) -> Int {
  if epochs.length() <= 1 {
    return 0
  }
  let mut count = 0
  for i in 1.. Double {
  let mut total = 0.0
  for epoch in epochs {
    if epoch.stage == stage && epoch.duration_minutes > 0.0 {
      total += epoch.duration_minutes
    }
  }
  total
}

///|
/// Calculate the proportion of epochs with usable signal quality.
pub fn sleep_quality_ratio(epochs : Array[SleepEpoch]) -> Double {
  if epochs.length() == 0 {
    return 0.0
  }
  let mut total = 0.0
  for epoch in epochs {
    total += epoch.quality_ratio.clamp(min=0.0, max=1.0)
  }
  total / epochs.length().to_double()
}

///|
/// Aggregate RR and RMSSD across epoch summaries using duration weights.
pub fn weighted_sleep_hrv(epochs : Array[SleepEpoch]) -> (Double, Double) {
  let mut rr = 0.0
  let mut rmssd = 0.0
  let mut weight = 0.0
  for epoch in epochs {
    let duration = if epoch.duration_minutes < 0.0 {
      0.0
    } else {
      epoch.duration_minutes
    }
    let quality = epoch.quality_ratio.clamp(min=0.0, max=1.0)
    let contribution = duration * quality
    rr += epoch.mean_rr * contribution
    rmssd += epoch.rmssd * contribution
    weight += contribution
  }
  if weight == 0.0 {
    (0.0, 0.0)
  } else {
    (rr / weight, rmssd / weight)
  }
}

///|
/// Calculate sleep efficiency from epoch durations.
pub fn calculate_sleep_efficiency(epochs : Array[SleepEpoch]) -> Double {
  let mut total = 0.0
  let mut asleep = 0.0
  for epoch in epochs {
    let duration = if epoch.duration_minutes < 0.0 {
      0.0
    } else {
      epoch.duration_minutes
    }
    total += duration
    if sleep_stage_is_asleep(epoch.stage) {
      asleep += duration
    }
  }
  if total == 0.0 {
    0.0
  } else {
    asleep / total
  }
}

///|
/// Calculate an epoch fragmentation index per hour of recording.
pub fn calculate_fragmentation_index(epochs : Array[SleepEpoch]) -> Double {
  let durations = []
  for epoch in epochs {
    durations.push(
      if epoch.duration_minutes < 0.0 {
        0.0
      } else {
        epoch.duration_minutes
      },
    )
  }
  let total = sum_values(durations)
  if total == 0.0 {
    0.0
  } else {
    sleep_stage_transitions(epochs).to_double() / (total / 60.0)
  }
}

///|
/// Compare overnight RMSSD to a personal baseline.
pub fn overnight_recovery_delta(
  overnight_rmssd : Double,
  baseline_rmssd : Double,
) -> Double {
  if baseline_rmssd == 0.0 {
    0.0
  } else {
    (overnight_rmssd - baseline_rmssd) / baseline_rmssd * 100.0
  }
}

///|
/// Build a complete overnight recovery summary.
pub fn summarize_sleep_recovery(
  epochs : Array[SleepEpoch],
  baseline_rmssd : Double,
) -> SleepRecoverySummary {
  let durations = []
  for epoch in epochs {
    durations.push(
      if epoch.duration_minutes < 0.0 {
        0.0
      } else {
        epoch.duration_minutes
      },
    )
  }
  let total = sum_values(durations)
  let asleep = sleep_stage_minutes(epochs, Light) +
    sleep_stage_minutes(epochs, Deep) +
    sleep_stage_minutes(epochs, Rem)
  let awake = sleep_stage_minutes(epochs, Awake)
  let (mean_rr, rmssd) = weighted_sleep_hrv(epochs)
  {
    total_minutes: total,
    asleep_minutes: asleep,
    awake_minutes: awake,
    deep_minutes: sleep_stage_minutes(epochs, Deep),
    rem_minutes: sleep_stage_minutes(epochs, Rem),
    light_minutes: sleep_stage_minutes(epochs, Light),
    sleep_efficiency: calculate_sleep_efficiency(epochs),
    stage_transition_count: sleep_stage_transitions(epochs),
    overnight_mean_rr: mean_rr,
    overnight_rmssd: rmssd,
    overnight_quality: sleep_quality_ratio(epochs),
    recovery_delta: overnight_recovery_delta(rmssd, baseline_rmssd),
    fragmentation_index: calculate_fragmentation_index(epochs),
  }
}

///|
/// Return the first sleep onset epoch, if one exists.
pub fn sleep_onset(epochs : Array[SleepEpoch]) -> SleepEpoch? {
  for epoch in epochs {
    if sleep_stage_is_asleep(epoch.stage) {
      return Some(epoch)
    }
  }
  None
}

///|
/// Return the last epoch that contributes to sleep duration.
pub fn sleep_offset(epochs : Array[SleepEpoch]) -> SleepEpoch? {
  let mut result : SleepEpoch? = None
  for epoch in epochs {
    if sleep_stage_is_asleep(epoch.stage) {
      result = Some(epoch)
    }
  }
  result
}

///|
/// Return awake intervals longer than a configurable threshold.
pub fn prolonged_awakenings(
  epochs : Array[SleepEpoch],
  threshold_minutes : Double,
) -> Array[SleepEpoch] {
  let result = []
  let threshold = if threshold_minutes < 0.0 { 0.0 } else { threshold_minutes }
  for epoch in epochs {
    if epoch.stage == Awake && epoch.duration_minutes >= threshold {
      result.push(epoch)
    }
  }
  result
}

///|
/// Calculate a sleep regularity score from start-time deltas.
pub fn sleep_regularity(start_minutes : Array[Double]) -> Double {
  if start_minutes.length() <= 1 {
    return 0.0
  }
  let deltas = []
  for i in 0..<(start_minutes.length() - 1) {
    deltas.push(absolute_difference(start_minutes[i + 1], start_minutes[i]))
  }
  let deviation = standard_deviation(deltas)
  (1.0 - deviation / 120.0).clamp(min=0.0, max=1.0)
}

///|
/// Create a compact sleep feature vector.
pub fn sleep_feature_vector(summary : SleepRecoverySummary) -> Array[Double] {
  [
    summary.total_minutes,
    summary.asleep_minutes,
    summary.awake_minutes,
    summary.deep_minutes,
    summary.rem_minutes,
    summary.light_minutes,
    summary.sleep_efficiency,
    summary.stage_transition_count.to_double(),
    summary.overnight_mean_rr,
    summary.overnight_rmssd,
    summary.overnight_quality,
    summary.recovery_delta,
    summary.fragmentation_index,
  ]
}

///|
/// Return whether an overnight summary is safe to display.
pub fn sleep_summary_is_usable(summary : SleepRecoverySummary) -> Bool {
  summary.asleep_minutes >= 0.0 &&
  summary.sleep_efficiency >= 0.0 &&
  summary.sleep_efficiency <= 1.0 &&
  summary.overnight_quality > 0.0
}