///|
pub(all) struct HealthCheck {
  name : String
  passed : Bool
  metric : Double
  threshold : Double
  message : String
} derive(Debug, ToJson)

///|
pub fn health_check(
  name : String,
  metric : Double,
  threshold : Double,
  message : String,
) -> HealthCheck {
  { name, passed: metric <= threshold, metric, threshold, message }
}

///|
pub fn health_charge(
  grid : Grid1D,
  density : ArrayView[Double],
  expected : Double,
  tolerance : Double,
) -> HealthCheck {
  health_check(
    "charge",
    (total_charge(grid, density) - expected).abs(),
    tolerance,
    "total charge error",
  )
}

///|
pub fn health_energy(
  initial : Double,
  final_value : Double,
  tolerance : Double,
) -> HealthCheck {
  health_check(
    "energy",
    relative_change(initial, final_value),
    tolerance,
    "relative energy drift",
  )
}

///|
pub fn health_residual(residual : Double, tolerance : Double) -> HealthCheck {
  health_check("residual", residual, tolerance, "solver residual")
}

///|
pub fn health_particles(expected : Int, actual : Int) -> HealthCheck {
  health_check(
    "particles",
    (expected - actual).abs().to_double(),
    0.0,
    "particle count",
  )
}

///|
pub fn health_report(checks : ArrayView[HealthCheck]) -> Bool {
  checks.all(fn(check) { check.passed })
}

///|
pub fn health_report_to_csv(checks : ArrayView[HealthCheck]) -> String {
  let output = StringBuilder()
  output.write_string("name,passed,metric,threshold,message\n")
  for check in checks {
    output.write_string(
      "\{check.name},\{check.passed},\{check.metric},\{check.threshold},\{check.message}\n",
    )
  }
  output.to_string()
}

///|
pub fn scenario_health(scenario : SimulationScenario) -> Array[HealthCheck] {
  let diagnostics = scenario_trace(scenario)
  if diagnostics.length() == 0 {
    []
  } else {
    let first = diagnostics[0]
    let last = diagnostics[diagnostics.length() - 1]
    [
      health_particles(first.particles, last.particles),
      health_energy(first.total_energy(), last.total_energy(), 1.0),
      health_check("time", last.time, 1.0e9, "simulation time"),
    ]
  }
}

///|
pub fn benchmark_health(record : BenchmarkRecord) -> Array[HealthCheck] {
  [
    health_check(
      "charge",
      record.charge_error,
      1.0e-8,
      "benchmark charge error",
    ),
    health_check("energy", record.energy_drift, 1.0, "benchmark energy drift"),
    health_particles(record.particles, record.final_particles),
  ]
}

///|
pub fn validation_health(report : ValidationReport) -> HealthCheck {
  health_check(
    "validation",
    if report.valid {
      0.0
    } else {
      1.0
    },
    0.0,
    validation_message(report),
  )
}