///|
fn percentile(sorted : Array[Double], p : Double) -> Double {
  if sorted.is_empty() {
    0.0
  } else {
    let last = sorted.length() - 1
    let idx = (last.to_double() * p).round().to_int().clamp(min=0, max=last)
    sorted[idx]
  }
}

///|
pub fn ThermalMatrix::stats(
  matrix : ThermalMatrix,
) -> ThermalStats raise ThermalError {
  if matrix.values.is_empty() {
    raise ThermalError::EmptyMatrix
  }
  let range = matrix.range()
  let count = matrix.values.length()
  let sum = matrix.values.fold(init=0.0, fn(acc, value) { acc + value })
  let mean = sum / count.to_double()
  let variance_sum = matrix.values.fold(init=0.0, fn(acc, value) {
    let d = value - mean
    acc + d * d
  })
  let sorted = matrix.values.copy()
  sorted.sort()
  {
    count,
    min: range.min,
    max: range.max,
    mean,
    stddev: (variance_sum / count.to_double()).sqrt(),
    p50: percentile(sorted, 0.50),
    p90: percentile(sorted, 0.90),
    p95: percentile(sorted, 0.95),
  }
}

///|
pub fn ThermalMatrix::inspect_threshold(
  matrix : ThermalMatrix,
  threshold~ : Double,
  hotspot_limit? : Int = 8,
) -> InspectionReport raise ThermalError {
  {
    frame_width: matrix.width,
    frame_height: matrix.height,
    threshold,
    stats: matrix.stats(),
    hotspots: matrix.detect_hotspots(min_temp=threshold, limit=hotspot_limit),
    regions: matrix.threshold_regions(min_temp=threshold),
  }
}

///|
pub fn InspectionReport::to_markdown(report : InspectionReport) -> String {
  let lines : Array[String] = [
    "# Thermal Inspection Report",
    "",
    "- Frame: \{report.frame_width} x \{report.frame_height}",
    "- Threshold: \{report.threshold} C",
    "- Temperature range: \{report.stats.min} .. \{report.stats.max} C",
    "- Mean: \{report.stats.mean} C",
    "- Stddev: \{report.stats.stddev} C",
    "- P90 / P95: \{report.stats.p90} / \{report.stats.p95} C",
    "",
    "## Hotspots",
  ]
  if report.hotspots.is_empty() {
    lines.push("No hotspot above threshold.")
  } else {
    for spot in report.hotspots {
      lines.push(
        "- (\{spot.point.x}, \{spot.point.y}) \{spot.temperature} C, contrast \{spot.contrast} C",
      )
    }
  }
  lines.push("")
  lines.push("## Regions")
  if report.regions.is_empty() {
    lines.push("No threshold region.")
  } else {
    for region in report.regions {
      lines.push(
        "- #\{region.id}: area \{region.area()}, bbox (\{region.min_x},\{region.min_y})-(\{region.max_x},\{region.max_y}), peak \{region.max_temp} C at (\{region.peak.x},\{region.peak.y})",
      )
    }
  }
  lines.join("\n")
}