///|
/// Options for the end-to-end HRV analysis pipeline.
pub(all) struct AnalysisOptions {
  cleaning_method : CleaningMethod
  sample_rate_hz : Double
  remove_trend : Bool
  window_function : WindowFunction
  segment_size : Int
  segment_hop : Int
  nonlinear_enabled : Bool
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Full report for a single RR recording.
pub(all) struct AnalysisReport {
  raw_count : Int
  cleaned_intervals : Array[Double]
  raw_validation : IntervalValidation
  quality : QualityReport
  metrics : HrvMetrics
  distribution : DistributionStats
  poincare : PoincareMetrics
  geometric : GeometricMetrics
  heart_rate : HeartRateSummary
  frequency : FrequencyMetrics
  nonlinear : NonlinearMetrics
  segments : Array[SegmentSummary]
  feature_vector : Array[Double]
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Conservative defaults suitable for short wearable recordings.
pub fn AnalysisOptions::default() -> AnalysisOptions {
  {
    cleaning_method: InterpolateLocalMedian,
    sample_rate_hz: 4.0,
    remove_trend: true,
    window_function: Hann,
    segment_size: 60,
    segment_hop: 30,
    nonlinear_enabled: true,
  }
}

///|
/// Analyze an RR sequence through cleaning, quality, time, frequency, and
/// non-linear layers.
pub fn analyze_rr(
  intervals : Array[Double],
  config : HrvConfig,
  options : AnalysisOptions,
) -> AnalysisReport {
  let raw_validation = validate_intervals(intervals, config)
  let (cleaned, quality) = clean_rr_intervals(
    intervals,
    options.cleaning_method,
    config,
  )
  let metrics = calculate_metrics(cleaned, config, quality)
  let distribution = summarize_distribution(cleaned)
  let poincare = calculate_poincare(cleaned)
  let geometric = calculate_geometric_metrics(cleaned, 7.8125)
  let heart_rate = summarize_heart_rate(cleaned, 0.0)
  let frequency = calculate_frequency_metrics(cleaned, options.sample_rate_hz)
  let nonlinear = if options.nonlinear_enabled {
    calculate_nonlinear_metrics(cleaned)
  } else {
    {
      sample_entropy: 0.0,
      approximate_entropy: 0.0,
      dfa_alpha: 0.0,
      turning_point_ratio: 0.0,
      recurrence_rate: 0.0,
      histogram_entropy: 0.0,
      lag1_autocorrelation: autocorrelation(cleaned, 1),
      complexity_index: 0.0,
    }
  }
  let segments = summarize_segments(
    cleaned,
    options.segment_size,
    options.segment_hop,
    config,
  )
  let feature_vector = []
  for value in advanced_feature_vector(cleaned) {
    feature_vector.push(value)
  }
  for value in frequency_feature_vector(cleaned, options.sample_rate_hz) {
    feature_vector.push(value)
  }
  if options.nonlinear_enabled {
    for value in nonlinear_feature_vector(cleaned) {
      feature_vector.push(value)
    }
  }
  {
    raw_count: intervals.length(),
    cleaned_intervals: cleaned,
    raw_validation,
    quality,
    metrics,
    distribution,
    poincare,
    geometric,
    heart_rate,
    frequency,
    nonlinear,
    segments,
    feature_vector,
  }
}

///|
/// Analyze using the package defaults.
pub fn analyze_rr_default(intervals : Array[Double]) -> AnalysisReport {
  let config = HrvConfig::default()
  analyze_rr(intervals, config, AnalysisOptions::default())
}

///|
/// Compare two reports using normalized feature distance.
pub fn compare_analysis_reports(
  left : AnalysisReport,
  right : AnalysisReport,
) -> Double {
  normalized_sequence_distance(left.feature_vector, right.feature_vector)
}

///|
/// Return a short human-readable quality summary.
pub fn analysis_quality_summary(report : AnalysisReport) -> String {
  "\{quality_grade(report.raw_validation)}: \{report.quality.valid_beats}/\{report.quality.total_beats} valid intervals"
}

///|
/// Return the first n cleaned intervals for preview cards.
pub fn analysis_preview(report : AnalysisReport, limit : Int) -> Array[Double] {
  let result = []
  let bound = if limit < 0 {
    0
  } else if limit > report.cleaned_intervals.length() {
    report.cleaned_intervals.length()
  } else {
    limit
  }
  for i in 0..