///|
/// A sustained heart-rate episode.
pub(all) struct RateEpisode {
  start : Int
  end : Int
  peak_bpm : Double
  minimum_bpm : Double
  average_bpm : Double
  duration_seconds : Double
  kind : String
} derive(FromJson, ToJson, Debug, Eq)

///|
/// A contiguous cluster of artifacts.
pub(all) struct ArtifactCluster {
  start : Int
  end : Int
  count : Int
  ratio : Double
  severity : String
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Return whether a heart rate is within a closed zone.
pub fn heart_rate_in_zone(
  rate : Double,
  lower_bpm : Double,
  upper_bpm : Double,
) -> Bool {
  rate >= lower_bpm && rate <= upper_bpm
}

///|
/// Detect sustained tachycardia or bradycardia episodes.
pub fn detect_rate_episodes(
  intervals : Array[Double],
  lower_bpm : Double,
  upper_bpm : Double,
  minimum_beats : Int,
) -> Array[RateEpisode] {
  let result = []
  let rates = intervals_to_heart_rate(intervals)
  let minimum = if minimum_beats < 1 { 1 } else { minimum_beats }
  let mut start = -1
  let mut kind = ""
  for i in 0.. upper_bpm {
      "tachycardia"
    } else {
      ""
    }
    if current_kind != "" && current_kind == kind {
      ()
    } else if current_kind != "" {
      if start >= 0 && i - start >= minimum {
        result.push(make_rate_episode(intervals, rates, start, i, kind))
      }
      start = i
      kind = current_kind
    } else {
      if start >= 0 && i - start >= minimum {
        result.push(make_rate_episode(intervals, rates, start, i, kind))
      }
      start = -1
      kind = ""
    }
  }
  if start >= 0 && rates.length() - start >= minimum {
    result.push(
      make_rate_episode(intervals, rates, start, rates.length(), kind),
    )
  }
  result
}

///|
/// Construct one episode summary from a half-open interval.
fn make_rate_episode(
  intervals : Array[Double],
  rates : Array[Double],
  start : Int,
  end : Int,
  kind : String,
) -> RateEpisode {
  let values = []
  let mut peak = rates[start]
  let mut minimum = rates[start]
  let mut duration = 0.0
  for i in start.. peak {
      peak = rates[i]
    }
    if rates[i] < minimum {
      minimum = rates[i]
    }
    duration += intervals[i] / 1000.0
  }
  {
    start,
    end,
    peak_bpm: peak,
    minimum_bpm: minimum,
    average_bpm: mean_value(values),
    duration_seconds: duration,
    kind,
  }
}

///|
/// Detect clusters of invalid values in an RR stream.
pub fn detect_artifact_clusters(
  intervals : Array[Double],
  config : HrvConfig,
  minimum_count : Int,
) -> Array[ArtifactCluster] {
  let result = []
  let minimum = if minimum_count < 1 { 1 } else { minimum_count }
  let mut start = -1
  let mut count = 0
  for i in 0..= 0 && count >= minimum {
        result.push(make_artifact_cluster(start, i, count, intervals.length()))
      }
      start = -1
      count = 0
    } else {
      if start < 0 {
        start = i
      }
      count += 1
    }
  }
  if start >= 0 && count >= minimum {
    result.push(
      make_artifact_cluster(
        start,
        intervals.length(),
        count,
        intervals.length(),
      ),
    )
  }
  result
}

///|
/// Create one artifact cluster with a severity grade.
fn make_artifact_cluster(
  start : Int,
  end : Int,
  count : Int,
  total : Int,
) -> ArtifactCluster {
  let ratio = if total == 0 {
    0.0
  } else {
    count.to_double() / total.to_double()
  }
  {
    start,
    end,
    count,
    ratio,
    severity: if ratio >= 0.25 {
      "critical"
    } else if ratio >= 0.10 {
      "warning"
    } else {
      "minor"
    },
  }
}

///|
/// Calculate the amount of time spent in a heart-rate zone.
pub fn time_in_heart_rate_zone(
  intervals : Array[Double],
  lower_bpm : Double,
  upper_bpm : Double,
) -> Double {
  let rates = intervals_to_heart_rate(intervals)
  let mut seconds = 0.0
  for i in 0.. Double {
  let start = episode.end
  let end = if start + observation_beats > intervals.length() {
    intervals.length()
  } else {
    start + observation_beats
  }
  if end - start <= 1 {
    0.0
  } else {
    let values = []
    for i in start.. Double {
  let mut duration = 0.0
  for interval in intervals {
    if classify_interval(interval, config) is Normal {
      duration += interval / 1000.0
    }
  }
  duration
}