///|
pub(all) struct Point2D {
  x : Double
  y : Double
} derive(Debug, ToJson)

///|
pub(all) struct Segment2D {
  start : Point2D
  end : Point2D
} derive(Debug, ToJson)

///|
pub fn Point2D::distance(self : Point2D, other : Point2D) -> Double {
  let dx = self.x - other.x
  let dy = self.y - other.y
  (dx * dx + dy * dy).sqrt()
}

///|
pub fn Point2D::lerp(
  self : Point2D,
  other : Point2D,
  amount : Double,
) -> Point2D {
  {
    x: self.x + (other.x - self.x) * amount,
    y: self.y + (other.y - self.y) * amount,
  }
}

///|
pub fn Segment2D::length(self : Segment2D) -> Double {
  self.start.distance(self.end)
}

///|
pub fn Segment2D::point_at(self : Segment2D, amount : Double) -> Point2D {
  self.start.lerp(self.end, amount)
}

///|
pub fn polygon_signed_area(points : ArrayView[Point2D]) -> Double {
  if points.length() < 3 {
    return 0.0
  }
  let mut area = 0.0
  for i in 0.. Double {
  let a = polygon_signed_area(points)
  if a < 0.0 {
    0.0 - a
  } else {
    a
  }
}

///|
pub fn polygon_is_clockwise(points : ArrayView[Point2D]) -> Bool {
  polygon_signed_area(points) < 0.0
}

///|
pub fn point_in_polygon(point : Point2D, polygon : ArrayView[Point2D]) -> Bool {
  if polygon.length() < 3 {
    return false
  }
  let mut inside = false
  let mut j = polygon.length() - 1
  for i in 0.. point.y) != (b.y > point.y) &&
      point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x {
      inside = !inside
    }
    j = i
  }
  inside
}

///|
pub fn bounding_box_of_points(
  points : ArrayView[Point2D],
) -> BoundingBox raise VisionFormatError {
  if points.is_empty() {
    raise VisionFormatError::EmptyInput
  }
  let mut min_x = points[0].x
  let mut max_x = min_x
  let mut min_y = points[0].y
  let mut max_y = min_y
  for point in points {
    if point.x < min_x {
      min_x = point.x
    }
    if point.x > max_x {
      max_x = point.x
    }
    if point.y < min_y {
      min_y = point.y
    }
    if point.y > max_y {
      max_y = point.y
    }
  }
  { x: min_x, y: min_y, width: max_x - min_x, height: max_y - min_y }
}

///|
pub fn expand_box(box : BoundingBox, padding : Double) -> BoundingBox {
  {
    x: box.x - padding,
    y: box.y - padding,
    width: box.width + padding * 2.0,
    height: box.height + padding * 2.0,
  }
}

///|
pub fn clamp_box(box : BoundingBox, width : Int, height : Int) -> BoundingBox {
  let left = if box.x < 0.0 { 0.0 } else { box.x }
  let top = if box.y < 0.0 { 0.0 } else { box.y }
  let right = if box.right() > width.to_double() {
    width.to_double()
  } else {
    box.right()
  }
  let bottom = if box.bottom() > height.to_double() {
    height.to_double()
  } else {
    box.bottom()
  }
  {
    x: left,
    y: top,
    width: if right > left {
      right - left
    } else {
      0.0
    },
    height: if bottom > top {
      bottom - top
    } else {
      0.0
    },
  }
}