///|
/// Baseline state used by repeated morning measurements.
pub(all) struct MorningBaselineState {
  count : Int
  center : Double
  deviation : Double
  lower : Double
  upper : Double
  trend_slope : Double
  latest_status : String
  missing_days : Int
} derive(FromJson, ToJson, Debug, Eq)

///|
/// An explainable alert emitted for a morning series.
pub(all) struct MorningAlert {
  date : String
  severity : String
  code : String
  message : String
  value : Double
  baseline : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Build a baseline state from the historical values before today.
pub fn baseline_state(
  history : Array[MorningMeasurement],
  today_index : Int,
  config : HrvConfig,
) -> MorningBaselineState {
  let values = []
  let dates = []
  let bound = if today_index < 0 {
    0
  } else if today_index > history.length() {
    history.length()
  } else {
    today_index
  }
  for i in 0.. robust.upper {
    "High"
  } else {
    "Normal"
  }
  {
    count: values.length(),
    center: robust.center,
    deviation: robust.spread,
    lower: robust.lower,
    upper: robust.upper,
    trend_slope: trend.slope,
    latest_status: status,
    missing_days: missing_calendar_days(dates),
  }
}

///|
/// Detect low/high RMSSD and date-quality alerts.
pub fn detect_morning_alerts(
  history : Array[MorningMeasurement],
  config : HrvConfig,
) -> Array[MorningAlert] {
  let result = []
  for i in 0.. 1 && measurement.rmssd < state.lower {
      result.push({
        date: measurement.date,
        severity: if measurement.rmssd < state.lower - state.deviation {
          "critical"
        } else {
          "warning"
        },
        code: "rmssd_below_baseline",
        message: "RMSSD is below the personal morning range",
        value: measurement.rmssd,
        baseline: state.center,
      })
    } else if state.count > 1 && measurement.rmssd > state.upper {
      result.push({
        date: measurement.date,
        severity: "info",
        code: "rmssd_above_baseline",
        message: "RMSSD is above the personal morning range",
        value: measurement.rmssd,
        baseline: state.center,
      })
    }
    if parse_date(measurement.date) is None {
      result.push({
        date: measurement.date,
        severity: "error",
        code: "invalid_date",
        message: "Morning measurement date is not YYYY-MM-DD",
        value: measurement.rmssd,
        baseline: state.center,
      })
    }
  }
  result
}

///|
/// Produce a robust status for the most recent measurement.
pub fn latest_morning_status(
  history : Array[MorningMeasurement],
  config : HrvConfig,
) -> MorningBaselineState {
  baseline_state(history, history.length(), config)
}

///|
/// Return a seven-day exponentially weighted RMSSD baseline.
pub fn ewma_morning_baseline(
  history : Array[MorningMeasurement],
  alpha : Double,
) -> Array[Double] {
  let values = []
  for measurement in history {
    values.push(measurement.rmssd)
  }
  exponential_moving_average(values, alpha)
}

///|
/// Score the current history using a robust baseline and trend.
pub fn score_latest_morning(
  history : Array[MorningMeasurement],
  config : HrvConfig,
) -> ReadinessScore {
  if history.length() == 0 {
    return calculate_readiness(
      0.0,
      calculate_robust_baseline([], 3.5, config.normal_range_sd_factor),
      0.0,
      0.0,
    )
  }
  let prior = []
  for i in 0..<(history.length() - 1) {
    prior.push(history[i].rmssd)
  }
  let baseline = calculate_robust_baseline(
    prior,
    3.5,
    config.normal_range_sd_factor,
  )
  let trend = fit_linear_trend(prior).slope
  calculate_readiness(history[history.length() - 1].rmssd, baseline, trend, 1.0)
}