///|
/// A named bound for one component of a filter state.
pub struct StateGuardRule {
  index : Int
  name : String
  minimum : Double
  maximum : Double
  tolerance : Double
  mut enabled : Bool
} derive(Debug)

///|
pub fn StateGuardRule::new(
  index : Int,
  name : String,
  minimum : Double,
  maximum : Double,
  tolerance : Double,
) -> StateGuardRule {
  {
    index: index.max(0),
    name,
    minimum: minimum.min(maximum),
    maximum: minimum.max(maximum),
    tolerance: tolerance.max(0.0),
    enabled: true,
  }
}

///|
pub fn StateGuardRule::index(self : StateGuardRule) -> Int {
  self.index
}

///|
pub fn StateGuardRule::name(self : StateGuardRule) -> String {
  self.name
}

///|
pub fn StateGuardRule::minimum(self : StateGuardRule) -> Double {
  self.minimum
}

///|
pub fn StateGuardRule::maximum(self : StateGuardRule) -> Double {
  self.maximum
}

///|
pub fn StateGuardRule::tolerance(self : StateGuardRule) -> Double {
  self.tolerance
}

///|
pub fn StateGuardRule::enabled(self : StateGuardRule) -> Bool {
  self.enabled
}

///|
pub fn StateGuardRule::set_enabled(
  self : StateGuardRule,
  enabled : Bool,
) -> Unit {
  self.enabled = enabled
}

///|
pub fn StateGuardRule::contains(self : StateGuardRule, value : Double) -> Bool {
  value >= self.minimum - self.tolerance &&
  value <= self.maximum + self.tolerance
}

///|
pub fn StateGuardRule::project(self : StateGuardRule, value : Double) -> Double {
  value.clamp(min=self.minimum, max=self.maximum)
}

///|
pub enum StateGuardStatus {
  StateGuardOk
  StateGuardClipped
  StateGuardRejected
  StateGuardInvalid
} derive(Debug, Eq)

///|
pub struct StateGuardIssue {
  index : Int
  name : String
  value : Double
  projected : Double
  excess : Double
  status : StateGuardStatus
} derive(Debug)

///|
pub fn StateGuardIssue::new(
  rule : StateGuardRule,
  value : Double,
  projected : Double,
  status : StateGuardStatus,
) -> StateGuardIssue {
  {
    index: rule.index(),
    name: rule.name(),
    value,
    projected,
    excess: (value - projected).abs(),
    status,
  }
}

///|
pub fn StateGuardIssue::index(self : StateGuardIssue) -> Int {
  self.index
}

///|
pub fn StateGuardIssue::name(self : StateGuardIssue) -> String {
  self.name
}

///|
pub fn StateGuardIssue::value(self : StateGuardIssue) -> Double {
  self.value
}

///|
pub fn StateGuardIssue::projected(self : StateGuardIssue) -> Double {
  self.projected
}

///|
pub fn StateGuardIssue::excess(self : StateGuardIssue) -> Double {
  self.excess
}

///|
pub fn StateGuardIssue::status(self : StateGuardIssue) -> StateGuardStatus {
  self.status
}

///|
pub struct StateGuardReport {
  original : Array[Double]
  repaired : Array[Double]
  issues : Array[StateGuardIssue]
  accepted : Bool
  changed : Bool
  score : Double
} derive(Debug)

///|
pub fn StateGuardReport::new(
  original : Array[Double],
  repaired : Array[Double],
  issues : Array[StateGuardIssue],
  accepted : Bool,
) -> StateGuardReport {
  let mut changed = false
  for i in 0.. 1.0e-12 {
      changed = true
    }
  }
  let mut penalty = 0.0
  for issue in issues {
    penalty = penalty + issue.excess()
    if issue.status() is StateGuardInvalid {
      penalty = penalty + 1.0
    }
  }
  {
    original: original.copy(),
    repaired: repaired.copy(),
    issues: issues.copy(),
    accepted,
    changed,
    score: (1.0 - penalty / (1.0 + original.length().to_double())).clamp(
      min=0.0,
      max=1.0,
    ),
  }
}

///|
pub fn StateGuardReport::original(self : StateGuardReport) -> Array[Double] {
  self.original.copy()
}

///|
pub fn StateGuardReport::repaired(self : StateGuardReport) -> Array[Double] {
  self.repaired.copy()
}

///|
pub fn StateGuardReport::issues(
  self : StateGuardReport,
) -> Array[StateGuardIssue] {
  self.issues.copy()
}

///|
pub fn StateGuardReport::accepted(self : StateGuardReport) -> Bool {
  self.accepted
}

///|
pub fn StateGuardReport::changed(self : StateGuardReport) -> Bool {
  self.changed
}

///|
pub fn StateGuardReport::score(self : StateGuardReport) -> Double {
  self.score
}

///|
pub fn StateGuardReport::is_usable(self : StateGuardReport) -> Bool {
  self.accepted && self.score >= 0.5
}

///|
/// A state sanitizer applies bounds while preserving untouched dimensions.
pub struct StateGuard {
  rules : Array[StateGuardRule]
  reject_invalid : Bool
  reject_excess : Double
  mut checks : Int
  mut rejected : Int
  mut clipped : Int
} derive(Debug)

///|
pub fn StateGuard::new(
  rules : Array[StateGuardRule],
  reject_invalid : Bool,
  reject_excess : Double,
) -> StateGuard {
  {
    rules: rules.copy(),
    reject_invalid,
    reject_excess: reject_excess.max(0.0),
    checks: 0,
    rejected: 0,
    clipped: 0,
  }
}

///|
pub fn StateGuard::rules(self : StateGuard) -> Array[StateGuardRule] {
  self.rules.copy()
}

///|
pub fn StateGuard::checks(self : StateGuard) -> Int {
  self.checks
}

///|
pub fn StateGuard::rejected(self : StateGuard) -> Int {
  self.rejected
}

///|
pub fn StateGuard::clipped(self : StateGuard) -> Int {
  self.clipped
}

///|
pub fn StateGuard::rejection_rate(self : StateGuard) -> Double {
  if self.checks == 0 {
    0.0
  } else {
    self.rejected.to_double() / self.checks.to_double()
  }
}

///|
pub fn StateGuard::check(
  self : StateGuard,
  state : Array[Double],
) -> StateGuardReport {
  self.checks = self.checks + 1
  let repaired = state.copy()
  let issues : Array[StateGuardIssue] = []
  let mut rejected = false
  for rule in self.rules {
    if !rule.enabled() {
      continue
    }
    if rule.index() < 0 || rule.index() >= state.length() {
      issues.push(StateGuardIssue::new(rule, 0.0, 0.0, StateGuardInvalid))
      rejected = true
      continue
    }
    let value = state[rule.index()]
    if value.is_nan() || value.is_inf() {
      issues.push(StateGuardIssue::new(rule, value, 0.0, StateGuardInvalid))
      rejected = true
      continue
    }
    let projected = rule.project(value)
    let excess = (value - projected).abs()
    if excess <= rule.tolerance() {
      issues.push(StateGuardIssue::new(rule, value, value, StateGuardOk))
    } else if excess <= self.reject_excess || !self.reject_invalid {
      repaired[rule.index()] = projected
      self.clipped = self.clipped + 1
      issues.push(
        StateGuardIssue::new(rule, value, projected, StateGuardClipped),
      )
    } else {
      issues.push(
        StateGuardIssue::new(rule, value, projected, StateGuardRejected),
      )
      rejected = true
    }
  }
  if rejected {
    self.rejected = self.rejected + 1
  }
  StateGuardReport::new(state, repaired, issues, !rejected)
}

///|
pub fn StateGuard::repair(
  self : StateGuard,
  state : Array[Double],
) -> Array[Double] {
  self.check(state).repaired()
}

///|
pub fn StateGuard::reset_metrics(self : StateGuard) -> Unit {
  self.checks = 0
  self.rejected = 0
  self.clipped = 0
}

///|
pub fn state_guard_rules_for_dimension(
  minimum : Array[Double],
  maximum : Array[Double],
  tolerance : Double,
) -> Array[StateGuardRule] {
  let dimension = minimum.length().min(maximum.length())
  let rules : Array[StateGuardRule] = []
  for i in 0.. StateGuard {
  let dimension = state
    .length()
    .min(covariance.rows())
    .min(minimum.length())
    .min(maximum.length())
  let lower : Array[Double] = []
  let upper : Array[Double] = []
  for i in 0.. Array[StateGuardRule] {
  let result = left.copy()
  for rule in right {
    let mut replaced = false
    for i, current in result {
      if current.index() == rule.index() {
        result[i] = rule
        replaced = true
      }
    }
    if !replaced {
      result.push(rule)
    }
  }
  result
}

///|
pub fn state_guard_issue_summary(issue : StateGuardIssue) -> String {
  let status = match issue.status() {
    StateGuardOk => "ok"
    StateGuardClipped => "clipped"
    StateGuardRejected => "rejected"
    StateGuardInvalid => "invalid"
  }
  issue.name() +
  "=" +
  issue.value().to_string() +
  ",status=" +
  status +
  ",excess=" +
  issue.excess().to_string()
}

///|
pub fn state_guard_report_summary(report : StateGuardReport) -> String {
  "accepted=" +
  report.accepted().to_string() +
  ",changed=" +
  report.changed().to_string() +
  ",issues=" +
  report.issues().length().to_string() +
  ",score=" +
  report.score().to_string()
}

///|
pub fn state_guard_is_finite(state : Array[Double]) -> Bool {
  vector_is_finite(state)
}

///|
pub fn state_guard_distance(
  original : Array[Double],
  repaired : Array[Double],
) -> Double {
  vector_distance(original, repaired)
}

///|
pub fn state_guard_changed_indices(report : StateGuardReport) -> Array[Int] {
  let original = report.original()
  let repaired = report.repaired()
  let result : Array[Int] = []
  for i in 0.. 1.0e-12 {
      result.push(i)
    }
  }
  result
}

///|
pub fn state_guard_worst_issue(report : StateGuardReport) -> StateGuardIssue? {
  let mut worst : StateGuardIssue? = None
  for issue in report.issues() {
    match worst {
      None => worst = Some(issue)
      Some(previous) =>
        if issue.excess() > previous.excess() {
          worst = Some(issue)
        }
    }
  }
  worst
}

///|
pub fn state_guard_validate_dimension(
  state : Array[Double],
  expected_dimension : Int,
) -> Bool {
  expected_dimension >= 0 &&
  state.length() == expected_dimension &&
  vector_is_finite(state)
}

///|
pub fn state_guard_normalize_weights(weights : Array[Double]) -> Array[Double] {
  let result = weights.map(value => value.max(0.0))
  let total = vector_sum(result)
  if total <= 0.0 {
    Array::make(result.length(), 1.0 / result.length().max(1).to_double())
  } else {
    result.map(value => value / total)
  }
}

///|
pub fn state_guard_weighted_distance(
  left : Array[Double],
  right : Array[Double],
  weights : Array[Double],
) -> Double {
  if left.length() != right.length() || left.length() != weights.length() {
    return 1.0e300
  }
  let mut sum = 0.0
  for i in 0..