///|
/// Structured operational reports for CLI output, dashboards, and archives.
/// Values remain machine-readable while headline text stays concise enough for
/// a coach or operator to act on.
pub(all) enum ReportSectionKind {
  ReportOverview
  ReportQuality
  ReportRecovery
  ReportTraining
  ReportForecast
  ReportDecision
  ReportActions
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct OperationalReportMetric {
  key : String
  label : String
  value : Double
  unit : String
  status : String
  confidence : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct OperationalReportSection {
  kind : ReportSectionKind
  title : String
  summary : String
  metrics : Array[OperationalReportMetric]
  rows : Array[Array[String]]
  warnings : Array[String]
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct OperationalReport {
  report_id : String
  generated_at : String
  subject_id : String
  headline : String
  overall_score : Double
  confidence : Double
  sections : Array[OperationalReportSection]
  feature_vector : Array[Double]
  warnings : Array[String]
} derive(FromJson, ToJson, Debug, Eq)

///|
pub fn report_section_name(kind : ReportSectionKind) -> String {
  match kind {
    ReportOverview => "overview"
    ReportQuality => "quality"
    ReportRecovery => "recovery"
    ReportTraining => "training"
    ReportForecast => "forecast"
    ReportDecision => "decision"
    ReportActions => "actions"
  }
}

///|
fn report_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 report_metric(
  key : String,
  label : String,
  value : Double,
  unit : String,
  status : String,
  confidence : Double,
) -> OperationalReportMetric {
  {
    key,
    label,
    value,
    unit,
    status,
    confidence: report_bound(confidence, 0.0, 1.0),
  }
}

///|
pub fn report_section(
  kind : ReportSectionKind,
  title : String,
  summary : String,
  metrics : Array[OperationalReportMetric],
  rows : Array[Array[String]],
  warnings : Array[String],
) -> OperationalReportSection {
  { kind, title, summary, metrics, rows, warnings }
}

///|
pub fn report_overview_section(
  report : AnalysisReport,
  gate : QualityGateResult,
) -> OperationalReportSection {
  report_section(
    ReportOverview,
    "Analysis overview",
    "End-to-end HRV analysis and quality gate summary.",
    [
      report_metric(
        "raw_count",
        "Raw intervals",
        report.raw_count.to_double(),
        "beats",
        "observed",
        1.0,
      ),
      report_metric(
        "clean_count",
        "Clean intervals",
        report.cleaned_intervals.length().to_double(),
        "beats",
        "observed",
        gate.score,
      ),
      report_metric(
        "mean_rr",
        "Mean RR",
        report.metrics.mean_rr,
        "ms",
        "observed",
        gate.score,
      ),
      report_metric(
        "rmssd",
        "RMSSD",
        report.metrics.rmssd,
        "ms",
        "observed",
        gate.score,
      ),
      report_metric(
        "quality",
        "Quality score",
        gate.score,
        "ratio",
        if gate.passed {
          "pass"
        } else {
          "fail"
        },
        gate.score,
      ),
    ],
    [],
    gate.reasons,
  )
}

///|
pub fn report_recovery_section(
  report : LongitudinalRecoveryReport,
) -> OperationalReportSection {
  let rows = []
  for assessment in report.assessments {
    rows.push([
      assessment.record.date,
      assessment.readiness_score.to_string(),
      recovery_status_name(assessment.status),
      assessment.confidence.to_string(),
    ])
  }
  report_section(
    ReportRecovery,
    "Recovery trend",
    recovery_report_message(report),
    [
      report_metric(
        "current_score",
        "Current readiness",
        report.current_score,
        "score",
        recovery_status_name(report.current_status),
        report.quality_ratio,
      ),
      report_metric(
        "quality_ratio",
        "Usable-day ratio",
        report.quality_ratio,
        "ratio",
        "observed",
        report.quality_ratio,
      ),
      report_metric(
        "missing_days",
        "Missing days",
        report.missing_days.to_double(),
        "days",
        if report.missing_days > 0 {
          "watch"
        } else {
          "complete"
        },
        1.0,
      ),
      report_metric(
        "stable_streak",
        "Stable streak",
        report.stable_streak.to_double(),
        "days",
        "observed",
        report.quality_ratio,
      ),
    ],
    rows,
    if report.trajectory == RecoveryWorsening {
      ["Recovery trend is worsening."]
    } else {
      []
    },
  )
}

///|
pub fn report_training_section(
  plan : TrainingLoadPlan,
) -> OperationalReportSection {
  let rows = []
  for day in plan.days {
    rows.push([
      day.date,
      day.effective_load.to_string(),
      day.duration_minutes.to_string(),
      day.quality_ratio.to_string(),
    ])
  }
  report_section(
    ReportTraining,
    "Training load",
    load_plan_recovery_message(plan),
    [
      report_metric(
        "acute_load",
        "Acute load",
        plan.profile.acute_load,
        "load",
        "observed",
        load_plan_quality(plan),
      ),
      report_metric(
        "chronic_load",
        "Chronic load",
        plan.profile.chronic_load,
        "load",
        "reference",
        load_plan_quality(plan),
      ),
      report_metric(
        "load_ratio",
        "Acute/chronic ratio",
        plan.profile.acute_chronic_ratio,
        "ratio",
        if plan.profile.acute_chronic_ratio > 1.30 {
          "warning"
        } else {
          "stable"
        },
        load_plan_quality(plan),
      ),
      report_metric(
        "strain",
        "Strain",
        plan.profile.strain,
        "load",
        "observed",
        load_plan_quality(plan),
      ),
    ],
    rows,
    if plan.alerts.length() > 0 {
      [load_plan_recovery_message(plan)]
    } else {
      []
    },
  )
}

///|
pub fn report_decision_section(plan : DecisionPlan) -> OperationalReportSection {
  let rows = []
  for action in plan.actions {
    rows.push([
      action.priority.to_string(),
      action.title,
      action.intensity_ceiling.to_string(),
      action.requires_recheck.to_string(),
    ])
  }
  report_section(
    ReportDecision,
    "Decision support",
    plan.headline,
    [
      report_metric(
        "decision_score",
        "Decision score",
        plan.score,
        "score",
        decision_severity_name(plan.level),
        plan.confidence,
      ),
      report_metric(
        "risk_score",
        "Risk score",
        decision_plan_risk_score(plan),
        "ratio",
        decision_severity_name(plan.level),
        plan.confidence,
      ),
      report_metric(
        "intensity_ceiling",
        "Intensity ceiling",
        decision_plan_intensity_ceiling(plan),
        "ratio",
        "recommended",
        plan.confidence,
      ),
    ],
    rows,
    plan.findings.map(finding => finding.evidence),
  )
}

///|
pub fn report_forecast_section(
  bundle : ForecastBundle,
) -> OperationalReportSection {
  report_section(
    ReportForecast,
    "Forecast",
    bundle.recommendation,
    [
      report_metric(
        "recovery_slope",
        "Recovery slope",
        bundle.recovery.slope,
        "score/day",
        if bundle.recovery.slope < 0.0 {
          "declining"
        } else {
          "stable"
        },
        bundle.recovery.coverage,
      ),
      report_metric(
        "load_slope",
        "Load slope",
        bundle.load.slope,
        "load/day",
        if bundle.load.slope > 5.0 {
          "rising"
        } else {
          "stable"
        },
        bundle.load.coverage,
      ),
      report_metric(
        "forecast_mae",
        "Holdout MAE",
        bundle.backtest.mean_absolute_error,
        "score",
        if bundle.backtest.stable {
          "stable"
        } else {
          "watch"
        },
        bundle.recovery.coverage,
      ),
      report_metric(
        "directional_accuracy",
        "Directional accuracy",
        bundle.backtest.directional_accuracy,
        "ratio",
        "backtest",
        bundle.recovery.coverage,
      ),
    ],
    [],
    if !bundle.backtest.stable {
      [
        "Forecast error is high; use the interval rather than the point estimate.",
      ]
    } else {
      []
    },
  )
}

///|
pub fn report_action_section(
  actions : Array[DecisionAction],
) -> OperationalReportSection {
  let rows = []
  for action in actions {
    rows.push([
      action.priority.to_string(),
      action.title,
      action.instruction,
      action.duration_minutes.to_string(),
      action.intensity_ceiling.to_string(),
    ])
  }
  report_section(
    ReportActions,
    "Recommended actions",
    "Prioritized actions emitted by the decision layer.",
    [],
    rows,
    [],
  )
}

///|
fn report_section_append(
  sections : Array[OperationalReportSection],
  section : OperationalReportSection,
) -> Unit {
  sections.push(section)
}

///|
pub fn build_operational_report(
  report_id : String,
  generated_at : String,
  subject_id : String,
  analysis : AnalysisReport?,
  gate : QualityGateResult,
  recovery : LongitudinalRecoveryReport?,
  training : TrainingLoadPlan?,
  forecast : ForecastBundle?,
  decision : DecisionPlan?,
) -> OperationalReport {
  let sections = []
  let warnings = []
  match analysis {
    Some(value) =>
      report_section_append(sections, report_overview_section(value, gate))
    None => warnings.push("analysis report is unavailable")
  }
  match recovery {
    Some(value) =>
      report_section_append(sections, report_recovery_section(value))
    None => warnings.push("longitudinal recovery is unavailable")
  }
  match training {
    Some(value) =>
      report_section_append(sections, report_training_section(value))
    None => warnings.push("training load is unavailable")
  }
  match forecast {
    Some(value) =>
      report_section_append(sections, report_forecast_section(value))
    None => warnings.push("forecast is unavailable")
  }
  match decision {
    Some(value) => {
      report_section_append(sections, report_decision_section(value))
      report_section_append(sections, report_action_section(value.actions))
    }
    None => warnings.push("decision support is unavailable")
  }
  let score = match decision {
    Some(value) => value.score
    None => if gate.passed { gate.score * 100.0 } else { gate.score * 50.0 }
  }
  let confidence = match decision {
    Some(value) => value.confidence
    None => gate.score
  }
  let features = []
  features.push(score)
  features.push(confidence)
  features.push(gate.score)
  features.push(sections.length().to_double())
  match recovery {
    Some(value) =>
      for feature in recovery_report_feature_vector(value) {
        features.push(feature)
      }
    None => ()
  }
  match training {
    Some(value) =>
      for feature in load_plan_feature_vector(value) {
        features.push(feature)
      }
    None => ()
  }
  {
    report_id,
    generated_at,
    subject_id,
    headline: match decision {
      Some(value) => value.headline
      None => "Operational report generated with limited context."
    },
    overall_score: report_bound(score, 0.0, 100.0),
    confidence: report_bound(confidence, 0.0, 1.0),
    sections,
    feature_vector: features,
    warnings,
  }
}

///|
pub fn operational_report_is_usable(report : OperationalReport) -> Bool {
  report.sections.length() >= 2 &&
  report.overall_score >= 0.0 &&
  report.overall_score <= 100.0 &&
  report.confidence >= 0.0
}

///|
pub fn operational_report_feature_vector(
  report : OperationalReport,
) -> Array[Double] {
  let result = []
  for value in report.feature_vector {
    result.push(value)
  }
  result.push(report.overall_score)
  result.push(report.confidence)
  result.push(report.warnings.length().to_double())
  result
}

///|
pub fn operational_report_to_csv(report : OperationalReport) -> String {
  let grid = [
    ["section", "metric", "label", "value", "unit", "status", "confidence"],
  ]
  for section in report.sections {
    for metric in section.metrics {
      grid.push([
        report_section_name(section.kind),
        metric.key,
        metric.label,
        metric.value.to_string(),
        metric.unit,
        metric.status,
        metric.confidence.to_string(),
      ])
    }
  }
  to_csv(grid)
}

///|
pub fn operational_report_rows_csv(report : OperationalReport) -> String {
  let grid = [["section", "title", "row"]]
  for section in report.sections {
    for row in section.rows {
      grid.push([
        report_section_name(section.kind),
        section.title,
        row.join("|"),
      ])
    }
  }
  to_csv(grid)
}

///|
pub fn operational_report_markdown(report : OperationalReport) -> String {
  let builder = StringBuilder::new()
  builder.write_string("# ")
  builder.write_string(report.headline)
  builder.write_string("\n\n")
  builder.write_string("Score: ")
  builder.write_string(report.overall_score.to_string())
  builder.write_string("  Confidence: ")
  builder.write_string(report.confidence.to_string())
  builder.write_string("\n\n")
  for section in report.sections {
    builder.write_string("## ")
    builder.write_string(section.title)
    builder.write_string("\n\n")
    builder.write_string(section.summary)
    builder.write_string("\n\n")
    for metric in section.metrics {
      builder.write_string("- ")
      builder.write_string(metric.label)
      builder.write_string(": ")
      builder.write_string(metric.value.to_string())
      builder.write_string(" ")
      builder.write_string(metric.unit)
      builder.write_string("\n")
    }
    builder.write_string("\n")
  }
  builder.to_string()
}

///|
pub fn operational_report_section(
  report : OperationalReport,
  kind : ReportSectionKind,
) -> OperationalReportSection? {
  for section in report.sections {
    if section.kind == kind {
      return Some(section)
    }
  }
  None
}

///|
pub fn operational_report_warning_count(report : OperationalReport) -> Int {
  let mut count = report.warnings.length()
  for section in report.sections {
    count += section.warnings.length()
  }
  count
}

///|
pub fn operational_report_headline(report : OperationalReport) -> String {
  "\{report.headline} score=\{report.overall_score.to_string()} confidence=\{report.confidence.to_string()}"
}