///|
/// Polygon helpers used by ternary phase-envelope exporters.
pub(all) struct Point {
  x : Double
  y : Double
} derive(Debug, Eq, ToJson)

///|
pub fn Point::new(
  x~ : Double,
  y~ : Double,
) -> Point raise @domain.ExtractionError {
  @domain.require_finite("point x", x)
  @domain.require_finite("point y", y)
  { x, y }
}

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

///|
pub fn polygon_area(
  points : ArrayView[Point],
) -> Double raise @domain.ExtractionError {
  if points.length() < 3 {
    raise @domain.ExtractionError::InsufficientData(
      required=3,
      actual=points.length(),
    )
  }
  let mut sum = 0.0
  for index, point in points {
    let next = points[(index + 1) % points.length()]
    sum += point.x * next.y - next.x * point.y
  }
  sum.abs() / 2.0
}

///|
pub fn centroid(
  points : ArrayView[Point],
) -> Point raise @domain.ExtractionError {
  let area = polygon_area(points)
  if area == 0.0 {
    raise @domain.ExtractionError::InvalidArgument(
      message="polygon area must be non-zero",
    )
  }
  let mut cross_sum = 0.0
  let mut x_sum = 0.0
  let mut y_sum = 0.0
  for index, point in points {
    let next = points[(index + 1) % points.length()]
    let cross = point.x * next.y - next.x * point.y
    cross_sum += cross
    x_sum += (point.x + next.x) * cross
    y_sum += (point.y + next.y) * cross
  }
  let signed_area = cross_sum / 2.0
  Point::new(x=x_sum / (6.0 * signed_area), y=y_sum / (6.0 * signed_area))
}

///|
pub fn contains(polygon : ArrayView[Point], point : Point) -> Bool {
  let mut inside = false
  let mut previous = polygon.length() - 1
  for index in 0.. point.y) != (prior.y > point.y)
    if crosses {
      let intersection = (prior.x - current.x) *
        (point.y - current.y) /
        (prior.y - current.y) +
        current.x
      if point.x < intersection {
        inside = !inside
      }
    }
    previous = index
  }
  inside
}