///|
/// One detected artifact with enough context for audit logs.
pub(all) struct ArtifactEvent {
  index : Int
  value : Double
  kind : IntervalDisposition
  replacement : Double
  confidence : Double
  left_neighbor : Double
  right_neighbor : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Result of an auditable cleaning pass.
pub(all) struct CleaningResult {
  intervals : Array[Double]
  quality : QualityReport
  validation : IntervalValidation
  events : Array[ArtifactEvent]
  changed_count : Int
  max_correction : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Return a valid neighbor on the left, if any.
fn left_valid_value(
  intervals : Array[Double],
  index : Int,
  config : HrvConfig,
) -> Double? {
  let mut i = index - 1
  while i >= 0 {
    if classify_interval(intervals[i], config) is Normal {
      return Some(intervals[i])
    }
    i -= 1
  }
  None
}

///|
/// Return a valid neighbor on the right, if any.
fn right_valid_value(
  intervals : Array[Double],
  index : Int,
  config : HrvConfig,
) -> Double? {
  let mut i = index + 1
  while i < intervals.length() {
    if classify_interval(intervals[i], config) is Normal {
      return Some(intervals[i])
    }
    i += 1
  }
  None
}

///|
/// Interpolate an artifact from the nearest valid neighbors.
pub fn interpolate_artifact(
  intervals : Array[Double],
  index : Int,
  config : HrvConfig,
) -> Double {
  let left = left_valid_value(intervals, index, config)
  let right = right_valid_value(intervals, index, config)
  match (left, right) {
    (Some(l), Some(r)) => (l + r) / 2.0
    (Some(l), None) => l
    (None, Some(r)) => r
    (None, None) => config.min_rr
  }
}

///|
/// Detect artifacts and record the replacement used by a cleaning method.
pub fn inspect_cleaning(
  intervals : Array[Double],
  cleaning_method : CleaningMethod,
  config : HrvConfig,
) -> CleaningResult {
  let (cleaned, quality) = clean_rr_intervals(
    intervals, cleaning_method, config,
  )
  let validation = validate_intervals(intervals, config)
  let events = []
  let mut changed_count = 0
  let mut max_correction = 0.0
  let mut output_index = 0
  let removes = match cleaning_method {
    Remove => true
    _ => false
  }
  for i in 0.. 0.0 {
          changed_count += 1
          if correction > max_correction {
            max_correction = correction
          }
        }
        output_index += 1
      }
    } else {
      let replacement = if cleaning_method is Remove {
        0.0
      } else if output_index < cleaned.length() {
        cleaned[output_index]
      } else {
        interpolate_artifact(intervals, i, config)
      }
      let confidence = if replacement == 0.0 {
        0.0
      } else {
        1.0 /
        (
          1.0 +
          absolute_difference(intervals[i], replacement) / replacement.abs()
        )
      }
      let left = left_valid_value(intervals, i, config)
      let right = right_valid_value(intervals, i, config)
      let left_value = match left {
        Some(value) => value
        None => 0.0
      }
      let right_value = match right {
        Some(value) => value
        None => 0.0
      }
      events.push({
        index: i,
        value: intervals[i],
        kind: disposition,
        replacement,
        confidence,
        left_neighbor: left_value,
        right_neighbor: right_value,
      })
      if !removes {
        changed_count += 1
      }
      if replacement > 0.0 {
        let correction = absolute_difference(intervals[i], replacement)
        if correction > max_correction {
          max_correction = correction
        }
      }
      if !removes {
        output_index += 1
      }
    }
  }
  {
    intervals: cleaned,
    quality,
    validation,
    events,
    changed_count,
    max_correction,
  }
}

///|
/// Replace isolated invalid observations while preserving valid runs.
pub fn repair_invalid_intervals(
  intervals : Array[Double],
  config : HrvConfig,
) -> Array[Double] {
  let result = []
  for i in 0.. Array[Double] {
  let result = []
  if radius < 0 {
    return result
  }
  for i in 0.. intervals.length() {
      intervals.length()
    } else {
      i + radius + 1
    }
    for j in start.. Array[Double] {
  let result = []
  let center = median_value(intervals)
  let mad = median_absolute_deviation(intervals)
  let scale = if mad == 0.0 { 0.0 } else { mad * 1.4826 }
  let fence = (if multiplier < 0.0 { 0.0 } else { multiplier }) * scale
  for value in intervals {
    if (scale == 0.0 && value == center) ||
      (scale != 0.0 && absolute_difference(value, center) <= fence) {
      result.push(value)
    }
  }
  result
}

///|
/// Return a compact cleaning feature vector.
pub fn cleaning_feature_vector(result : CleaningResult) -> Array[Double] {
  [
    result.quality.clean_ratio,
    result.events.length().to_double(),
    result.changed_count.to_double(),
    result.max_correction,
    result.validation.duplicates.to_double(),
    result.validation.monotonic_breaks.to_double(),
    result.validation.too_short.to_double(),
    result.validation.too_long.to_double(),
    result.validation.non_finite.to_double(),
    result.validation.median,
  ]
}