///|
/// A production-oriented training load ledger for longitudinal HRV workflows.
/// The module keeps raw session context, transparent dose components, and
/// conservative status labels together so downstream applications can explain
/// why a recommendation was produced.
pub(all) enum LoadIntensityBand {
  LoadRecovery
  LoadAerobic
  LoadTempo
  LoadThreshold
  LoadHighIntensity
  LoadMaximal
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) enum LoadRiskLevel {
  LoadStable
  LoadWatch
  LoadCaution
  LoadCritical
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct TrainingLoadEntry {
  date : String
  session_id : String
  duration_minutes : Double
  average_hr_bpm : Double
  maximum_hr_bpm : Double
  resting_hr_bpm : Double
  rpe : Double
  distance_km : Double
  elevation_m : Double
  signal_quality : Double
  band : LoadIntensityBand
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct LoadModelConfig {
  max_hr_bpm : Double
  resting_hr_floor_bpm : Double
  acute_window_days : Int
  chronic_window_days : Int
  easy_rpe : Double
  hard_rpe : Double
  quality_floor : Double
  monotony_floor : Double
  caution_ratio : Double
  critical_ratio : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
pub fn LoadModelConfig::default() -> LoadModelConfig {
  {
    max_hr_bpm: 190.0,
    resting_hr_floor_bpm: 35.0,
    acute_window_days: 7,
    chronic_window_days: 28,
    easy_rpe: 3.0,
    hard_rpe: 8.0,
    quality_floor: 0.70,
    monotony_floor: 2.0,
    caution_ratio: 1.30,
    critical_ratio: 1.60,
  }
}

///|
pub(all) struct LoadDose {
  duration_component : Double
  cardiovascular_component : Double
  perceived_effort_component : Double
  distance_component : Double
  elevation_component : Double
  quality_weight : Double
  raw_load : Double
  effective_load : Double
  intensity_score : Double
  band : LoadIntensityBand
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct DailyLoadLedger {
  date : String
  entries : Array[TrainingLoadEntry]
  doses : Array[LoadDose]
  total_load : Double
  effective_load : Double
  duration_minutes : Double
  session_count : Int
  average_intensity : Double
  quality_ratio : Double
  recovery_cost : Double
  high_intensity_minutes : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct RollingLoadProfile {
  dates : Array[String]
  daily_loads : Array[Double]
  acute_load : Double
  chronic_load : Double
  acute_chronic_ratio : Double
  exponentially_weighted_load : Double
  monotony : Double
  strain : Double
  load_trend : Double
  rest_day_count : Int
  high_load_day_count : Int
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct LoadAlert {
  date : String
  level : LoadRiskLevel
  code : String
  title : String
  explanation : String
  observed : Double
  threshold : Double
  action : String
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct TrainingLoadPlan {
  days : Array[DailyLoadLedger]
  profile : RollingLoadProfile
  alerts : Array[LoadAlert]
  total_load : Double
  total_effective_load : Double
  average_session_load : Double
  peak_day : String
  recommended_easy_days : Int
} derive(FromJson, ToJson, Debug, Eq)

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

///|
fn load_positive(value : Double) -> Double {
  if value.is_nan() || value.is_inf() || value <= 0.0 {
    0.0
  } else {
    value
  }
}

///|
fn load_nonnegative_int(value : Int) -> Int {
  if value < 0 {
    0
  } else {
    value
  }
}

///|
pub fn load_band_from_score(score : Double) -> LoadIntensityBand {
  let value = load_clamp(score, 0.0, 1.0)
  if value < 0.25 {
    LoadRecovery
  } else if value < 0.50 {
    LoadAerobic
  } else if value < 0.68 {
    LoadTempo
  } else if value < 0.82 {
    LoadThreshold
  } else if value < 0.94 {
    LoadHighIntensity
  } else {
    LoadMaximal
  }
}

///|
pub fn load_band_name(band : LoadIntensityBand) -> String {
  match band {
    LoadRecovery => "recovery"
    LoadAerobic => "aerobic"
    LoadTempo => "tempo"
    LoadThreshold => "threshold"
    LoadHighIntensity => "high_intensity"
    LoadMaximal => "maximal"
  }
}

///|
pub fn load_risk_name(level : LoadRiskLevel) -> String {
  match level {
    LoadStable => "stable"
    LoadWatch => "watch"
    LoadCaution => "caution"
    LoadCritical => "critical"
  }
}

///|
pub fn make_training_load_entry(
  date : String,
  session_id : String,
  duration_minutes : Double,
  average_hr_bpm : Double,
  maximum_hr_bpm : Double,
  resting_hr_bpm : Double,
  rpe : Double,
  distance_km : Double,
  elevation_m : Double,
  signal_quality : Double,
) -> TrainingLoadEntry {
  let safe_max = if maximum_hr_bpm > 0.0 {
    maximum_hr_bpm
  } else {
    average_hr_bpm
  }
  let score = if safe_max <= 0.0 {
    rpe / 10.0
  } else {
    average_hr_bpm / safe_max
  }
  {
    date,
    session_id,
    duration_minutes: load_clamp(duration_minutes, 0.0, 1440.0),
    average_hr_bpm: load_clamp(average_hr_bpm, 0.0, 260.0),
    maximum_hr_bpm: load_clamp(maximum_hr_bpm, 0.0, 260.0),
    resting_hr_bpm: load_clamp(resting_hr_bpm, 0.0, 180.0),
    rpe: load_clamp(rpe, 0.0, 10.0),
    distance_km: load_positive(distance_km),
    elevation_m: load_positive(elevation_m),
    signal_quality: load_clamp(signal_quality, 0.0, 1.0),
    band: load_band_from_score(score),
  }
}

///|
pub fn training_load_entry_is_valid(
  entry : TrainingLoadEntry,
  config : LoadModelConfig,
) -> Bool {
  entry.date.length() > 0 &&
  entry.session_id.length() > 0 &&
  entry.duration_minutes > 0.0 &&
  entry.duration_minutes <= 1440.0 &&
  entry.average_hr_bpm >= 0.0 &&
  entry.average_hr_bpm <= 260.0 &&
  entry.rpe >= 0.0 &&
  entry.rpe <= 10.0 &&
  entry.signal_quality >= config.quality_floor &&
  !entry.duration_minutes.is_nan() &&
  !entry.rpe.is_nan()
}

///|
pub fn load_entry_quality_weight(entry : TrainingLoadEntry) -> Double {
  load_clamp(entry.signal_quality, 0.0, 1.0)
}

///|
pub fn load_entry_intensity_score(
  entry : TrainingLoadEntry,
  config : LoadModelConfig,
) -> Double {
  let hr_score = if config.max_hr_bpm <= entry.resting_hr_bpm {
    0.0
  } else {
    (entry.average_hr_bpm - entry.resting_hr_bpm) /
    (config.max_hr_bpm - entry.resting_hr_bpm)
  }
  let rpe_score = entry.rpe / 10.0
  let max_score = if entry.maximum_hr_bpm <= config.max_hr_bpm {
    entry.maximum_hr_bpm / config.max_hr_bpm
  } else {
    1.0
  }
  (0.45 * hr_score + 0.40 * rpe_score + 0.15 * max_score).clamp(
    min=0.0,
    max=1.0,
  )
}

///|
pub fn calculate_load_dose(
  entry : TrainingLoadEntry,
  config : LoadModelConfig,
) -> LoadDose {
  let duration = load_clamp(entry.duration_minutes, 0.0, 1440.0)
  let intensity = load_entry_intensity_score(entry, config)
  let duration_component = duration * (0.35 + intensity)
  let cardiovascular_component = if entry.average_hr_bpm <= entry.resting_hr_bpm {
    0.0
  } else {
    duration *
    ((entry.average_hr_bpm - entry.resting_hr_bpm) / config.max_hr_bpm.max(1.0)).clamp(
      min=0.0,
      max=1.0,
    ) *
    100.0
  }
  let perceived_effort_component = duration * entry.rpe * 1.25
  let distance_component = entry.distance_km * (2.0 + intensity * 3.0)
  let elevation_component = entry.elevation_m / 100.0 * (0.5 + intensity)
  let raw = duration_component +
    cardiovascular_component * 0.30 +
    perceived_effort_component * 0.35 +
    distance_component * 0.40 +
    elevation_component
  let quality = load_entry_quality_weight(entry)
  let effective = raw * (0.50 + quality * 0.50)
  {
    duration_component,
    cardiovascular_component,
    perceived_effort_component,
    distance_component,
    elevation_component,
    quality_weight: quality,
    raw_load: raw,
    effective_load: effective,
    intensity_score: intensity,
    band: load_band_from_score(intensity),
  }
}

///|
pub fn load_dose_total(dose : LoadDose) -> Double {
  dose.effective_load
}

///|
pub fn load_dose_is_high(dose : LoadDose) -> Bool {
  dose.intensity_score >= 0.75 || dose.effective_load >= 350.0
}

///|
pub fn load_dose_recovery_cost(
  dose : LoadDose,
  entry : TrainingLoadEntry,
) -> Double {
  let rpe_cost = entry.rpe * entry.duration_minutes / 10.0
  let intensity_cost = dose.intensity_score * entry.duration_minutes
  (rpe_cost * 0.60 + intensity_cost * 0.40) *
  (1.10 - dose.quality_weight * 0.10)
}

///|
fn load_entry_index(entries : Array[TrainingLoadEntry], date : String) -> Int? {
  for i in 0.. Array[TrainingLoadEntry] {
  let result = []
  for entry in entries {
    result.push(entry)
  }
  result
}

///|
fn load_sort_entries(
  entries : Array[TrainingLoadEntry],
) -> Array[TrainingLoadEntry] {
  let result = load_copy_entries(entries)
  result.sort_by((left, right) => {
    if left.date < right.date {
      -1
    } else if left.date > right.date {
      1
    } else if left.session_id < right.session_id {
      -1
    } else if left.session_id > right.session_id {
      1
    } else {
      0
    }
  })
  result
}

///|
pub fn group_training_load_days(
  entries : Array[TrainingLoadEntry],
  config : LoadModelConfig,
) -> Array[DailyLoadLedger] {
  let ordered = load_sort_entries(entries)
  let days : Array[DailyLoadLedger] = []
  for entry in ordered {
    if !training_load_entry_is_valid(entry, config) {
      continue
    }
    let dose = calculate_load_dose(entry, config)
    match
      load_entry_index(
        days.map(day => {
          date: day.date,
          session_id: "",
          duration_minutes: 1.0,
          average_hr_bpm: 0.0,
          maximum_hr_bpm: 0.0,
          resting_hr_bpm: 0.0,
          rpe: 0.0,
          distance_km: 0.0,
          elevation_m: 0.0,
          signal_quality: 1.0,
          band: LoadRecovery,
        }),
        entry.date,
      ) {
      Some(index) => {
        let current = days[index]
        let count = current.session_count + 1
        let entries_copy = load_copy_entries(current.entries)
        let doses_copy = current.doses
        entries_copy.push(entry)
        doses_copy.push(dose)
        let high_minutes = if load_dose_is_high(dose) {
          current.high_intensity_minutes + entry.duration_minutes
        } else {
          current.high_intensity_minutes
        }
        let cost = load_dose_recovery_cost(dose, entry)
        days[index] = {
          date: current.date,
          entries: entries_copy,
          doses: doses_copy,
          total_load: current.total_load + dose.raw_load,
          effective_load: current.effective_load + dose.effective_load,
          duration_minutes: current.duration_minutes + entry.duration_minutes,
          session_count: count,
          average_intensity: (
            current.average_intensity * current.session_count.to_double() +
            dose.intensity_score
          ) /
          count.to_double(),
          quality_ratio: (
            current.quality_ratio * current.session_count.to_double() +
            entry.signal_quality
          ) /
          count.to_double(),
          recovery_cost: current.recovery_cost + cost,
          high_intensity_minutes: high_minutes,
        }
      }
      None => {
        let high_minutes = if load_dose_is_high(dose) {
          entry.duration_minutes
        } else {
          0.0
        }
        days.push({
          date: entry.date,
          entries: [entry],
          doses: [dose],
          total_load: dose.raw_load,
          effective_load: dose.effective_load,
          duration_minutes: entry.duration_minutes,
          session_count: 1,
          average_intensity: dose.intensity_score,
          quality_ratio: entry.signal_quality,
          recovery_cost: load_dose_recovery_cost(dose, entry),
          high_intensity_minutes: high_minutes,
        })
      }
    }
  }
  days
}

///|
fn load_day_mean(days : Array[DailyLoadLedger]) -> Double {
  let values = days.map(day => day.effective_load)
  mean_value(values)
}

///|
fn load_day_sd(days : Array[DailyLoadLedger]) -> Double {
  let values = days.map(day => day.effective_load)
  standard_deviation(values)
}

///|
pub fn calculate_load_monotony(days : Array[DailyLoadLedger]) -> Double {
  if days.length() == 0 {
    0.0
  } else {
    let sd = load_day_sd(days)
    if sd <= 0.000001 {
      0.0
    } else {
      load_day_mean(days) / sd
    }
  }
}

///|
pub fn calculate_load_strain(days : Array[DailyLoadLedger]) -> Double {
  sum_values(days.map(day => day.effective_load)) *
  calculate_load_monotony(days)
}

///|
pub fn load_rest_day_count(days : Array[DailyLoadLedger]) -> Int {
  let mut count = 0
  for day in days {
    if day.session_count == 0 || day.effective_load < 40.0 {
      count += 1
    }
  }
  count
}

///|
pub fn load_high_day_count(days : Array[DailyLoadLedger]) -> Int {
  let mut count = 0
  for day in days {
    if day.effective_load >= 300.0 || day.high_intensity_minutes >= 20.0 {
      count += 1
    }
  }
  count
}

///|
pub fn rolling_load_average(
  days : Array[DailyLoadLedger],
  end_index : Int,
  window : Int,
) -> Double {
  let safe_end = end_index.clamp(min=0, max=days.length())
  let safe_window = load_nonnegative_int(window)
  if safe_end == 0 || safe_window == 0 {
    0.0
  } else {
    let start = (safe_end - safe_window).max(0)
    let values = []
    for i in start.. Double {
  let alpha = load_clamp(decay, 0.01, 1.0)
  let mut value = 0.0
  for day in days {
    value = alpha * day.effective_load + (1.0 - alpha) * value
  }
  value
}

///|
pub fn calculate_load_ratio(
  days : Array[DailyLoadLedger],
  acute_window : Int,
  chronic_window : Int,
) -> Double {
  let acute = rolling_load_average(days, days.length(), acute_window)
  let chronic = rolling_load_average(days, days.length(), chronic_window)
  if chronic <= 0.000001 {
    0.0
  } else {
    acute / chronic
  }
}

///|
pub fn load_trend(days : Array[DailyLoadLedger]) -> Double {
  fit_linear_trend(days.map(day => day.effective_load)).slope
}

///|
pub fn build_rolling_load_profile(
  days : Array[DailyLoadLedger],
  config : LoadModelConfig,
) -> RollingLoadProfile {
  let dates = days.map(day => day.date)
  let loads = days.map(day => day.effective_load)
  let acute = rolling_load_average(
    days,
    days.length(),
    config.acute_window_days,
  )
  let chronic = rolling_load_average(
    days,
    days.length(),
    config.chronic_window_days,
  )
  let ratio = if chronic <= 0.000001 { 0.0 } else { acute / chronic }
  {
    dates,
    daily_loads: loads,
    acute_load: acute,
    chronic_load: chronic,
    acute_chronic_ratio: ratio,
    exponentially_weighted_load: exponentially_weighted_load(days, 0.25),
    monotony: calculate_load_monotony(days),
    strain: calculate_load_strain(days),
    load_trend: load_trend(days),
    rest_day_count: load_rest_day_count(days),
    high_load_day_count: load_high_day_count(days),
  }
}

///|
pub fn classify_load_risk(
  profile : RollingLoadProfile,
  config : LoadModelConfig,
) -> LoadRiskLevel {
  if profile.acute_chronic_ratio >= config.critical_ratio ||
    profile.strain >= 1800.0 {
    LoadCritical
  } else if profile.acute_chronic_ratio >= config.caution_ratio ||
    profile.monotony >= config.monotony_floor * 1.6 {
    LoadCaution
  } else if profile.acute_chronic_ratio >= 1.10 || profile.load_trend >= 25.0 {
    LoadWatch
  } else {
    LoadStable
  }
}

///|
pub fn load_alert_for_profile(
  profile : RollingLoadProfile,
  config : LoadModelConfig,
) -> LoadAlert? {
  let level = classify_load_risk(profile, config)
  match level {
    LoadStable => None
    LoadWatch =>
      Some({
        date: if profile.dates.length() == 0 {
          ""
        } else {
          profile.dates[profile.dates.length() - 1]
        },
        level,
        code: "rising_load",
        title: "Training load is rising",
        explanation: "Recent load is above the established trend; keep the next session controlled.",
        observed: profile.acute_chronic_ratio,
        threshold: 1.10,
        action: "Prefer easy aerobic work and review recovery signals.",
      })
    LoadCaution =>
      Some({
        date: if profile.dates.length() == 0 {
          ""
        } else {
          profile.dates[profile.dates.length() - 1]
        },
        level,
        code: "load_ratio_caution",
        title: "Acute load needs caution",
        explanation: "The seven-day workload is materially above the chronic reference window.",
        observed: profile.acute_chronic_ratio,
        threshold: config.caution_ratio,
        action: "Insert a recovery day before another hard session.",
      })
    LoadCritical =>
      Some({
        date: if profile.dates.length() == 0 {
          ""
        } else {
          profile.dates[profile.dates.length() - 1]
        },
        level,
        code: "load_ratio_critical",
        title: "Training load is critically high",
        explanation: "Load ratio or strain crossed the safety threshold used by this model.",
        observed: profile.acute_chronic_ratio.max(profile.strain / 1800.0),
        threshold: config.critical_ratio,
        action: "Pause high-intensity work and reassess recovery before resuming.",
      })
  }
}

///|
pub fn build_training_load_plan(
  entries : Array[TrainingLoadEntry],
  config : LoadModelConfig,
) -> TrainingLoadPlan {
  let days = group_training_load_days(entries, config)
  let profile = build_rolling_load_profile(days, config)
  let alerts = []
  match load_alert_for_profile(profile, config) {
    Some(alert) => alerts.push(alert)
    None => ()
  }
  let mut total = 0.0
  let mut effective = 0.0
  let mut peak = ""
  let mut peak_value = -1.0
  for day in days {
    total += day.total_load
    effective += day.effective_load
    if day.effective_load > peak_value {
      peak_value = day.effective_load
      peak = day.date
    }
  }
  let count = entries.length()
  {
    days,
    profile,
    alerts,
    total_load: total,
    total_effective_load: effective,
    average_session_load: if count == 0 {
      0.0
    } else {
      effective / count.to_double()
    },
    peak_day: peak,
    recommended_easy_days: if classify_load_risk(profile, config)
      is LoadCritical {
      3
    } else if profile.acute_chronic_ratio > 1.2 {
      2
    } else {
      1
    },
  }
}

///|
pub fn load_plan_is_usable(plan : TrainingLoadPlan) -> Bool {
  plan.days.length() > 0 &&
  plan.total_effective_load >= 0.0 &&
  !plan.profile.acute_chronic_ratio.is_nan() &&
  !plan.profile.strain.is_nan()
}

///|
pub fn load_plan_feature_vector(plan : TrainingLoadPlan) -> Array[Double] {
  [
    plan.total_load,
    plan.total_effective_load,
    plan.average_session_load,
    plan.profile.acute_load,
    plan.profile.chronic_load,
    plan.profile.acute_chronic_ratio,
    plan.profile.exponentially_weighted_load,
    plan.profile.monotony,
    plan.profile.strain,
    plan.profile.load_trend,
    plan.profile.rest_day_count.to_double(),
    plan.profile.high_load_day_count.to_double(),
    plan.recommended_easy_days.to_double(),
  ]
}

///|
pub fn load_dose_to_row(
  entry : TrainingLoadEntry,
  dose : LoadDose,
) -> Array[String] {
  [
    entry.date,
    entry.session_id,
    entry.duration_minutes.to_string(),
    entry.rpe.to_string(),
    entry.average_hr_bpm.to_string(),
    entry.distance_km.to_string(),
    load_band_name(dose.band),
    dose.raw_load.to_string(),
    dose.effective_load.to_string(),
    dose.quality_weight.to_string(),
  ]
}

///|
pub fn export_training_load_plan_csv(plan : TrainingLoadPlan) -> String {
  let grid = [
    [
      "date", "session_id", "duration_minutes", "rpe", "average_hr_bpm", "distance_km",
      "band", "raw_load", "effective_load", "quality_weight",
    ],
  ]
  for day in plan.days {
    for i in 0.. String {
  let grid = [
    [
      "date", "session_count", "duration_minutes", "total_load", "effective_load",
      "average_intensity", "quality_ratio", "recovery_cost", "high_intensity_minutes",
    ],
  ]
  for day in plan.days {
    grid.push([
      day.date,
      day.session_count.to_string(),
      day.duration_minutes.to_string(),
      day.total_load.to_string(),
      day.effective_load.to_string(),
      day.average_intensity.to_string(),
      day.quality_ratio.to_string(),
      day.recovery_cost.to_string(),
      day.high_intensity_minutes.to_string(),
    ])
  }
  to_csv(grid)
}

///|
pub fn export_load_alerts_csv(plan : TrainingLoadPlan) -> String {
  let grid = [
    ["date", "level", "code", "title", "observed", "threshold", "action"],
  ]
  for alert in plan.alerts {
    grid.push([
      alert.date,
      load_risk_name(alert.level),
      alert.code,
      alert.title,
      alert.observed.to_string(),
      alert.threshold.to_string(),
      alert.action,
    ])
  }
  to_csv(grid)
}

///|
pub fn select_load_peaks(
  days : Array[DailyLoadLedger],
  limit : Int,
) -> Array[DailyLoadLedger] {
  let result = load_copy_days(days)
  result.sort_by((left, right) => {
    if left.effective_load > right.effective_load {
      -1
    } else if left.effective_load < right.effective_load {
      1
    } else {
      0
    }
  })
  if limit >= 0 && result.length() > limit {
    result.truncate(limit)
  }
  result
}

///|
fn load_copy_days(days : Array[DailyLoadLedger]) -> Array[DailyLoadLedger] {
  let result = []
  for day in days {
    result.push(day)
  }
  result
}

///|
pub fn load_days_in_range(
  days : Array[DailyLoadLedger],
  start_date : String,
  end_date : String,
) -> Array[DailyLoadLedger] {
  let result = []
  for day in days {
    if day.date >= start_date && day.date <= end_date {
      result.push(day)
    }
  }
  result
}

///|
pub fn load_plan_summary_line(plan : TrainingLoadPlan) -> String {
  "\{plan.days.length()} days, \{plan.total_effective_load.to_string()} effective load, ratio \{plan.profile.acute_chronic_ratio.to_string()}, risk \{load_risk_name(classify_load_risk(plan.profile, LoadModelConfig::default()))}"
}

///|
pub fn load_recommended_intensity(
  plan : TrainingLoadPlan,
  readiness_score : Double,
) -> Double {
  let risk_factor = match
    classify_load_risk(plan.profile, LoadModelConfig::default()) {
    LoadStable => 1.0
    LoadWatch => 0.80
    LoadCaution => 0.55
    LoadCritical => 0.30
  }
  load_clamp(readiness_score / 100.0 * risk_factor, 0.10, 1.0)
}

///|
pub fn load_schedule_is_monotonic(days : Array[DailyLoadLedger]) -> Bool {
  for i in 1..= days[i].date {
      return false
    }
  }
  true
}

///|
pub fn load_plan_quality(plan : TrainingLoadPlan) -> Double {
  if plan.days.length() == 0 {
    0.0
  } else {
    mean_value(plan.days.map(day => day.quality_ratio))
  }
}

///|
pub fn load_plan_duration(plan : TrainingLoadPlan) -> Double {
  sum_values(plan.days.map(day => day.duration_minutes))
}

///|
pub fn load_plan_hard_minutes(plan : TrainingLoadPlan) -> Double {
  sum_values(plan.days.map(day => day.high_intensity_minutes))
}

///|
pub fn load_plan_recovery_cost(plan : TrainingLoadPlan) -> Double {
  sum_values(plan.days.map(day => day.recovery_cost))
}

///|
pub fn load_plan_with_quality_floor(
  entries : Array[TrainingLoadEntry],
  minimum_quality : Double,
) -> TrainingLoadPlan {
  let config = LoadModelConfig::default()
  let adjusted = {
    max_hr_bpm: config.max_hr_bpm,
    resting_hr_floor_bpm: config.resting_hr_floor_bpm,
    acute_window_days: config.acute_window_days,
    chronic_window_days: config.chronic_window_days,
    easy_rpe: config.easy_rpe,
    hard_rpe: config.hard_rpe,
    quality_floor: minimum_quality.clamp(min=0.0, max=1.0),
    monotony_floor: config.monotony_floor,
    caution_ratio: config.caution_ratio,
    critical_ratio: config.critical_ratio,
  }
  build_training_load_plan(entries, adjusted)
}

///|
pub fn load_entry_from_workout(
  session : WorkoutSession,
  session_id : String,
) -> TrainingLoadEntry {
  make_training_load_entry(
    session.date,
    session_id,
    session.duration_minutes,
    0.0,
    0.0,
    60.0,
    session.intensity,
    0.0,
    0.0,
    1.0,
  )
}

///|
pub fn load_plan_from_workouts(
  sessions : Array[WorkoutSession],
) -> TrainingLoadPlan {
  let entries = []
  for i in 0.. Bool {
  match level {
    LoadStable => false
    LoadWatch => false
    LoadCaution => true
    LoadCritical => true
  }
}

///|
pub fn load_risk_score(level : LoadRiskLevel) -> Double {
  match level {
    LoadStable => 0.0
    LoadWatch => 0.35
    LoadCaution => 0.70
    LoadCritical => 1.0
  }
}

///|
pub fn load_profile_row(profile : RollingLoadProfile) -> Array[String] {
  [
    profile.acute_load.to_string(),
    profile.chronic_load.to_string(),
    profile.acute_chronic_ratio.to_string(),
    profile.exponentially_weighted_load.to_string(),
    profile.monotony.to_string(),
    profile.strain.to_string(),
    profile.load_trend.to_string(),
    profile.rest_day_count.to_string(),
    profile.high_load_day_count.to_string(),
  ]
}

///|
pub fn load_profile_csv(profile : RollingLoadProfile) -> String {
  let grid = [
    [
      "acute_load", "chronic_load", "acute_chronic_ratio", "ewma_load", "monotony",
      "strain", "load_trend", "rest_day_count", "high_load_day_count",
    ],
    load_profile_row(profile),
  ]
  to_csv(grid)
}

///|
pub fn load_plan_alert_count(plan : TrainingLoadPlan) -> Int {
  plan.alerts.length()
}

///|
pub fn load_plan_peak_value(plan : TrainingLoadPlan) -> Double {
  if plan.days.length() == 0 {
    0.0
  } else {
    let peaks = select_load_peaks(plan.days, 1)
    if peaks.length() == 0 {
      0.0
    } else {
      peaks[0].effective_load
    }
  }
}

///|
pub fn load_plan_has_high_intensity(plan : TrainingLoadPlan) -> Bool {
  plan.profile.high_load_day_count > 0
}

///|
pub fn load_plan_is_recovering(plan : TrainingLoadPlan) -> Bool {
  plan.profile.load_trend < 0.0 && plan.profile.acute_chronic_ratio < 1.0
}

///|
pub fn load_plan_next_day_budget(
  plan : TrainingLoadPlan,
  readiness_score : Double,
) -> Double {
  let base = if plan.profile.chronic_load <= 0.0 {
    100.0
  } else {
    plan.profile.chronic_load * 0.90
  }
  base * load_recommended_intensity(plan, readiness_score)
}

///|
pub fn load_plan_compare(
  current : TrainingLoadPlan,
  previous : TrainingLoadPlan,
) -> Array[Double] {
  [
    current.total_effective_load - previous.total_effective_load,
    current.profile.acute_load - previous.profile.acute_load,
    current.profile.chronic_load - previous.profile.chronic_load,
    current.profile.acute_chronic_ratio - previous.profile.acute_chronic_ratio,
    current.profile.load_trend - previous.profile.load_trend,
    current.profile.strain - previous.profile.strain,
  ]
}

///|
pub fn load_plan_is_more_stressful(
  current : TrainingLoadPlan,
  previous : TrainingLoadPlan,
) -> Bool {
  let change = load_plan_compare(current, previous)
  change[0] > 0.0 && change[3] >= 0.0
}

///|
pub fn load_plan_recovery_message(plan : TrainingLoadPlan) -> String {
  let level = classify_load_risk(plan.profile, LoadModelConfig::default())
  match level {
    LoadStable => "Current load is within the recent reference range."
    LoadWatch => "Load is rising; keep the next session controlled."
    LoadCaution => "Add an easy or rest day and reassess morning recovery."
    LoadCritical =>
      "Avoid high intensity until recovery and signal quality improve."
  }
}