///|
fn is_hot_enough(value : Double, min_temp : Double, max_temp : Double?) -> Bool {
  if value < min_temp {
    false
  } else {
    match max_temp {
      Some(limit) => value <= limit
      None => true
    }
  }
}

///|
fn push_neighbor(
  stack : Array[ThermalPoint],
  x~ : Int,
  y~ : Int,
  width~ : Int,
  height~ : Int,
) -> Unit {
  if x >= 0 && y >= 0 && x < width && y < height {
    stack.push(ThermalPoint::new(x~, y~))
  }
}

///|
fn build_region(
  matrix : ThermalMatrix,
  start : ThermalPoint,
  visited : Array[Bool],
  id~ : Int,
  min_temp~ : Double,
  max_temp? : Double,
) -> ThermalRegion {
  let stack : Array[ThermalPoint] = [start]
  let pixels : Array[ThermalPoint] = []
  let mut min_x = start.x
  let mut min_y = start.y
  let mut max_x = start.x
  let mut max_y = start.y
  let mut peak = start
  let mut sum = 0.0
  let mut min_seen = matrix.unsafe_get(x=start.x, y=start.y)
  let mut max_seen = min_seen
  for ;; {
    match stack.pop() {
      None => break
      Some(point) => {
        let idx = point.y * matrix.width + point.x
        if !visited[idx] {
          visited[idx] = true
          let value = matrix.unsafe_get(x=point.x, y=point.y)
          if is_hot_enough(value, min_temp, max_temp) {
            pixels.push(point)
            sum = sum + value
            min_x = Int::min(min_x, point.x)
            min_y = Int::min(min_y, point.y)
            max_x = Int::max(max_x, point.x)
            max_y = Int::max(max_y, point.y)
            min_seen = Double::min(min_seen, value)
            if value > max_seen {
              max_seen = value
              peak = point
            }
            push_neighbor(
              stack,
              x=point.x - 1,
              y=point.y,
              width=matrix.width,
              height=matrix.height,
            )
            push_neighbor(
              stack,
              x=point.x + 1,
              y=point.y,
              width=matrix.width,
              height=matrix.height,
            )
            push_neighbor(
              stack,
              x=point.x,
              y=point.y - 1,
              width=matrix.width,
              height=matrix.height,
            )
            push_neighbor(
              stack,
              x=point.x,
              y=point.y + 1,
              width=matrix.width,
              height=matrix.height,
            )
          }
        }
      }
    }
  }
  {
    id,
    pixels,
    min_x,
    min_y,
    max_x,
    max_y,
    min_temp: min_seen,
    max_temp: max_seen,
    average_temp: sum / pixels.length().to_double(),
    peak,
  }
}

///|
pub fn ThermalMatrix::threshold_regions(
  matrix : ThermalMatrix,
  min_temp~ : Double,
  max_temp? : Double,
) -> Array[ThermalRegion] {
  let visited = Array::make(matrix.values.length(), false)
  let regions : Array[ThermalRegion] = []
  for y in 0.. Int {
  region.pixels.length()
}