///|
fn component_neighbors(x : Int, y : Int) -> Array[Point] {
  [
    Point::Point(x=Double::from_int(x + 1), y=Double::from_int(y)),
    Point::Point(x=Double::from_int(x - 1), y=Double::from_int(y)),
    Point::Point(x=Double::from_int(x), y=Double::from_int(y + 1)),
    Point::Point(x=Double::from_int(x), y=Double::from_int(y - 1)),
  ]
}

///|
fn flood_component(
  mask : BinaryMask,
  labels : Array[Int],
  start_x : Int,
  start_y : Int,
  label : Int,
) -> Component {
  let queue : Array[Point] = [
    Point::Point(x=Double::from_int(start_x), y=Double::from_int(start_y)),
  ]
  let mut cursor = 0
  let mut area = 0
  let mut sum_x = 0.0
  let mut sum_y = 0.0
  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 perimeter = 0.0
  labels[start_y * mask.width + start_x] = label
  while cursor < queue.length() {
    let point = queue[cursor]
    cursor += 1
    let x = point.x.to_int()
    let y = point.y.to_int()
    area += 1
    sum_x += point.x
    sum_y += point.y
    min_x = min_x.min(x)
    min_y = min_y.min(y)
    max_x = max_x.max(x)
    max_y = max_y.max(y)
    let mut exposed = 0
    for neighbor in component_neighbors(x, y) {
      let nx = neighbor.x.to_int()
      let ny = neighbor.y.to_int()
      if !mask.inside(nx, ny) || !mask.at(nx, ny) {
        exposed += 1
      } else if labels[ny * mask.width + nx] == 0 {
        labels[ny * mask.width + nx] = label
        queue.push(neighbor)
      }
    }
    perimeter += Double::from_int(exposed)
  }
  let center = Point::Point(
    x=sum_x / Double::from_int(area),
    y=sum_y / Double::from_int(area),
  )
  let circularity = if perimeter <= 0.0 {
    0.0
  } else {
    4.0 * 3.141592653589793 * Double::from_int(area) / (perimeter * perimeter)
  }
  {
    label,
    area,
    bounds: { min_x, min_y, max_x, max_y },
    centroid: center,
    perimeter,
    circularity,
  }
}

///|
pub fn label_components(mask : BinaryMask) -> Array[Component] {
  let labels = Array::make(mask.width * mask.height, 0)
  let components : Array[Component] = []
  let mut label = 0
  for y in 0.. Array[Int] {
  let labels = Array::make(mask.width * mask.height, 0)
  let mut label = 0
  for y in 0.. BinaryMask {
  let result = BinaryMask(width=mask.width, height=mask.height)
  if label <= 0 {
    result
  } else {
    let labels = component_labels(mask)
    for y in 0.. Array[Point] {
  let labels = component_labels(mask)
  let result : Array[Point] = []
  for y in 0.. Component {
  let components = label_components(mask)
  if components.is_empty() {
    {
      label: 0,
      area: 0,
      bounds: { min_x: 0, min_y: 0, max_x: -1, max_y: -1 },
      centroid: Point::Point(x=0.0, y=0.0),
      perimeter: 0.0,
      circularity: 0.0,
    }
  } else {
    let mut result = components[0]
    for component in components[1:] {
      if component.area > result.area {
        result = component
      }
    }
    result
  }
}

///|
pub fn filter_components_by_area(
  mask : BinaryMask,
  minimum : Int,
  maximum : Int,
) -> BinaryMask {
  let result = BinaryMask(width=mask.width, height=mask.height)
  let components = label_components(mask)
  for component in components {
    if component.area >= minimum && component.area <= maximum {
      let selected = component_mask(mask, component.label)
      for y in 0.. Double {
  let width = component.bounds.max_x - component.bounds.min_x + 1
  let height = component.bounds.max_y - component.bounds.min_y + 1
  if width <= 0 || height <= 0 {
    0.0
  } else {
    Double::from_int(component.area) / Double::from_int(width * height)
  }
}

///|
pub fn component_aspect_ratio(component : Component) -> Double {
  let width = (component.bounds.max_x - component.bounds.min_x + 1).to_double()
  let height = (component.bounds.max_y - component.bounds.min_y + 1).to_double()
  if height <= 0.0 {
    0.0
  } else {
    width / height
  }
}

///|
pub fn components_sorted_by_area(
  mask : BinaryMask,
  descending? : Bool = true,
) -> Array[Component] {
  let result : Array[Component] = []
  for component in label_components(mask) {
    let mut index = 0
    while index < result.length() {
      let before = if descending {
        result[index].area >= component.area
      } else {
        result[index].area <= component.area
      }
      if !before {
        break
      }
      index += 1
    }
    result.insert(index, component)
  }
  result
}