///|
/// Respiration-related features estimated from RR variability.

///|
/// Respiratory modulation summary.
pub(all) struct RespirationSummary {
  rate_bpm : Double
  peak_frequency_hz : Double
  modulation_depth_ms : Double
  coherence : Double
  phase_consistency : Double
  confidence : Double
  cycles : Int
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Respiratory analysis configuration.
pub(all) struct RespirationConfig {
  sample_rate_hz : Double
  minimum_rate_bpm : Double
  maximum_rate_bpm : Double
  minimum_cycles : Int
  band_width_hz : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Default adult resting respiration search range.
pub fn RespirationConfig::default() -> RespirationConfig {
  {
    sample_rate_hz: 4.0,
    minimum_rate_bpm: 6.0,
    maximum_rate_bpm: 30.0,
    minimum_cycles: 3,
    band_width_hz: 0.04,
  }
}

///|
/// Calculate a smoothed RR modulation signal.
pub fn respiratory_modulation(
  intervals : Array[Double],
  radius : Int,
) -> Array[Double] {
  let baseline = rolling_median(
    intervals,
    if radius < 1 {
      3
    } else {
      radius * 2 + 1
    },
  )
  let result = []
  let limit = if baseline.length() < intervals.length() {
    baseline.length()
  } else {
    intervals.length()
  }
  for i in 0.. Double {
  let modulation = respiratory_modulation(intervals, 2)
  if modulation.length() == 0 {
    0.0
  } else {
    quantile_value(modulation, 0.75) - quantile_value(modulation, 0.25)
  }
}

///|
/// Estimate an RR-respiration phase series from a candidate frequency.
pub fn respiratory_phase_series(
  intervals : Array[Double],
  frequency_hz : Double,
  sample_rate_hz : Double,
) -> Array[Double] {
  if frequency_hz <= 0.0 || sample_rate_hz <= 0.0 {
    return []
  }
  let phase_step = 2.0 * 3.141592653589793 * frequency_hz / sample_rate_hz
  let result = []
  let mut phase = 0.0
  let mean = mean_value(intervals)
  let scale = standard_deviation(intervals)
  for value in intervals {
    let normalized = if scale == 0.0 { 0.0 } else { (value - mean) / scale }
    result.push((@math.sin(phase) * normalized).clamp(min=-1.0, max=1.0))
    phase += phase_step
  }
  result
}

///|
/// Calculate a normalized correlation between RR modulation and a sinusoid.
pub fn respiratory_coherence(
  intervals : Array[Double],
  frequency_hz : Double,
  sample_rate_hz : Double,
) -> Double {
  let phase = respiratory_phase_series(intervals, frequency_hz, sample_rate_hz)
  let modulation = respiratory_modulation(intervals, 2)
  let n = if phase.length() < modulation.length() {
    phase.length()
  } else {
    modulation.length()
  }
  if n == 0 {
    return 0.0
  }
  let left = []
  let right = []
  for i in 0.. Double {
  if frequency_hz <= 0.0 || sample_rate_hz <= 0.0 || intervals.length() == 0 {
    return 0.0
  }
  let modulation = respiratory_modulation(intervals, 2)
  let mean = mean_value(modulation)
  let deviation = standard_deviation(modulation)
  if deviation == 0.0 {
    return 0.0
  }
  let step = 2.0 * 3.141592653589793 * frequency_hz / sample_rate_hz
  let mut phase = 0.0
  let mut total = 0.0
  for value in modulation {
    let z = (value - mean) / deviation
    total += (z * @math.sin(phase)).abs().clamp(min=0.0, max=1.0)
    phase += step
  }
  total / intervals.length().to_double()
}

///|
/// Estimate the number of respiratory cycles in a candidate frequency.
pub fn estimate_respiratory_cycles(
  intervals : Array[Double],
  frequency_hz : Double,
  sample_rate_hz : Double,
) -> Int {
  if frequency_hz <= 0.0 || sample_rate_hz <= 0.0 {
    return 0
  }
  (intervals.length().to_double() / sample_rate_hz * frequency_hz).to_int()
}

///|
/// Estimate respiration from the dominant respiratory spectral peak.
pub fn estimate_respiration(
  intervals : Array[Double],
  config : RespirationConfig,
) -> RespirationSummary {
  let peak = respiratory_peak_frequency(intervals, config.sample_rate_hz)
  let rate = respiratory_rate_bpm(peak)
  let in_range = rate >= config.minimum_rate_bpm &&
    rate <= config.maximum_rate_bpm
  let cycles = estimate_respiratory_cycles(
    intervals,
    peak,
    config.sample_rate_hz,
  )
  let coherence = respiratory_coherence(intervals, peak, config.sample_rate_hz)
  let consistency = respiratory_phase_consistency(
    intervals,
    peak,
    config.sample_rate_hz,
  )
  let confidence = if !in_range || cycles < config.minimum_cycles {
    0.0
  } else {
    (0.5 * coherence + 0.5 * consistency).clamp(min=0.0, max=1.0)
  }
  {
    rate_bpm: if in_range {
      rate
    } else {
      0.0
    },
    peak_frequency_hz: if in_range {
      peak
    } else {
      0.0
    },
    modulation_depth_ms: respiratory_modulation_depth(intervals),
    coherence,
    phase_consistency: consistency,
    confidence,
    cycles,
  }
}

///|
/// Return a respiratory rate from a direct breathing trace.
pub fn estimate_trace_rate(
  trace : Array[Double],
  sample_rate_hz : Double,
) -> Double {
  if trace.length() <= 2 || sample_rate_hz <= 0.0 {
    return 0.0
  }
  let spectrum = calculate_periodogram(trace, sample_rate_hz)
  let selected = []
  for bin in spectrum {
    let rate = bin.frequency_hz * 60.0
    if rate >= 4.0 && rate <= 40.0 {
      selected.push(bin)
    }
  }
  if selected.length() == 0 {
    0.0
  } else {
    respiratory_rate_bpm(dominant_spectrum_bin(selected).frequency_hz)
  }
}

///|
/// Calculate a respiration-aware LF/HF modulation ratio.
pub fn respiratory_band_ratio(
  intervals : Array[Double],
  sample_rate_hz : Double,
) -> Double {
  let spectrum = calculate_periodogram(intervals, sample_rate_hz)
  let lf = spectral_band_power(spectrum, {
    name: "lf",
    lower_hz: 0.04,
    upper_hz: 0.15,
  })
  let respiration = spectral_band_power(spectrum, {
    name: "resp",
    lower_hz: 0.10,
    upper_hz: 0.50,
  })
  if lf == 0.0 {
    0.0
  } else {
    respiration / lf
  }
}

///|
/// Return a compact respiration feature vector.
pub fn respiration_feature_vector(
  summary : RespirationSummary,
) -> Array[Double] {
  [
    summary.rate_bpm,
    summary.peak_frequency_hz,
    summary.modulation_depth_ms,
    summary.coherence,
    summary.phase_consistency,
    summary.confidence,
    summary.cycles.to_double(),
  ]
}

///|
/// Return whether respiration can be used as a report annotation.
pub fn respiration_is_usable(summary : RespirationSummary) -> Bool {
  summary.rate_bpm > 0.0 && summary.cycles >= 3 && summary.confidence >= 0.25
}