///|
/// Physiological interpretation of one RR interval.
pub(all) enum IntervalDisposition {
  Normal
  TooShort
  TooLong
  NonFinite
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Detailed observation retained by the validation report.
pub(all) struct IntervalObservation {
  index : Int
  value : Double
  disposition : IntervalDisposition
  distance_from_median : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Input validation summary for a raw RR series.
pub(all) struct IntervalValidation {
  total : Int
  valid : Int
  invalid : Int
  too_short : Int
  too_long : Int
  non_finite : Int
  duplicates : Int
  monotonic_breaks : Int
  minimum : Double
  maximum : Double
  median : Double
  observations : Array[IntervalObservation]
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Classify a value against the physiological range in a configuration.
pub fn classify_interval(
  value : Double,
  config : HrvConfig,
) -> IntervalDisposition {
  if value.is_nan() || value.is_inf() {
    NonFinite
  } else if value < config.min_rr {
    TooShort
  } else if value > config.max_rr {
    TooLong
  } else {
    Normal
  }
}

///|
/// Build a validation report without mutating the input.
pub fn validate_intervals(
  intervals : Array[Double],
  config : HrvConfig,
) -> IntervalValidation {
  let n = intervals.length()
  let median = median_value(intervals)
  let mut too_short = 0
  let mut too_long = 0
  let mut non_finite = 0
  let mut valid = 0
  let mut duplicates = 0
  let mut monotonic_breaks = 0
  let observations = []
  let mut minimum = if n == 0 { 0.0 } else { intervals[0] }
  let mut maximum = minimum
  for i in 0.. valid += 1
      TooShort => too_short += 1
      TooLong => too_long += 1
      NonFinite => non_finite += 1
    }
    if i > 0 {
      if value == intervals[i - 1] {
        duplicates += 1
      }
      if value <= 0.0 && intervals[i - 1] > 0.0 {
        monotonic_breaks += 1
      }
    }
    if !value.is_nan() {
      if value < minimum || minimum.is_nan() {
        minimum = value
      }
      if value > maximum || maximum.is_nan() {
        maximum = value
      }
    }
    let delta = value - median
    let distance = if delta < 0.0 { -delta } else { delta }
    observations.push({
      index: i,
      value,
      disposition,
      distance_from_median: distance,
    })
  }
  {
    total: n,
    valid,
    invalid: n - valid,
    too_short,
    too_long,
    non_finite,
    duplicates,
    monotonic_breaks,
    minimum: if n == 0 {
      0.0
    } else {
      minimum
    },
    maximum: if n == 0 {
      0.0
    } else {
      maximum
    },
    median,
    observations,
  }
}

///|
/// Return only values accepted by the configured physiological range.
pub fn valid_intervals(
  intervals : Array[Double],
  config : HrvConfig,
) -> Array[Double] {
  let result = []
  for value in intervals {
    if classify_interval(value, config) is Normal {
      result.push(value)
    }
  }
  result
}

///|
/// Return the longest consecutive run of valid intervals.
pub fn longest_valid_run(intervals : Array[Double], config : HrvConfig) -> Int {
  let mut current = 0
  let mut longest = 0
  for value in intervals {
    if classify_interval(value, config) is Normal {
      current += 1
      if current > longest {
        longest = current
      }
    } else {
      current = 0
    }
  }
  longest
}

///|
/// Count gaps between two plausible beats using a configurable multiplier.
pub fn count_gap_candidates(
  intervals : Array[Double],
  config : HrvConfig,
  multiplier : Double,
) -> Int {
  let mut count = 0
  if intervals.length() <= 1 {
    return 0
  }
  for i in 0..<(intervals.length() - 1) {
    let left = intervals[i]
    let right = intervals[i + 1]
    if classify_interval(left, config) is Normal &&
      classify_interval(right, config) is Normal {
      let delta = left - right
      let absolute = if delta < 0.0 { -delta } else { delta }
      if absolute > multiplier * config.relative_threshold * left {
        count += 1
      }
    }
  }
  count
}

///|
/// Return a quality grade suitable for dashboards.
pub fn quality_grade(validation : IntervalValidation) -> String {
  if validation.total == 0 {
    "empty"
  } else {
    let ratio = validation.valid.to_double() / validation.total.to_double()
    if ratio >= 0.98 {
      "excellent"
    } else if ratio >= 0.90 {
      "good"
    } else if ratio >= 0.75 {
      "usable"
    } else {
      "poor"
    }
  }
}