///|
/// Polygon orientation and simplification helpers for design geometry.
pub enum PolygonWinding {
  Clockwise
  CounterClockwise
  Degenerate
} derive(Eq, Debug, ToJson)

///|
pub fn polygon_winding(points : Array[Vec2]) -> PolygonWinding {
  let area = polygon_area(points)
  if area > 0.000000001 {
    CounterClockwise
  } else if area < -0.000000001 {
    Clockwise
  } else {
    Degenerate
  }
}

///|
pub fn ensure_counter_clockwise(points : Array[Vec2]) -> Array[Vec2] {
  match polygon_winding(points) {
    Clockwise => {
      let result = []
      for offset in 1..<=points.length() {
        result.push(points[points.length() - offset])
      }
      result
    }
    _ => points
  }
}

///|
/// Removes consecutive vertices that are closer than `epsilon`.
pub fn deduplicate_points(
  points : Array[Vec2],
  epsilon : Double,
) -> Array[Vec2] {
  let result : Array[Vec2] = []
  for point in points {
    if result.length() == 0 ||
      result[result.length() - 1].distance(point) > epsilon {
      result.push(point)
    }
  }
  if result.length() > 1 &&
    result[0].distance(result[result.length() - 1]) <= epsilon {
    let _ = result.pop()
  }
  result
}

///|
/// Simplifies a polyline using a distance-to-segment tolerance.
pub fn simplify_polyline(
  points : Array[Vec2],
  tolerance : Double,
) -> Array[Vec2] {
  if points.length() <= 2 {
    points
  } else {
    let first = points[0]
    let last = points[points.length() - 1]
    let mut max_distance = 0.0
    let mut split = 0
    for i in 1..<(points.length() - 1) {
      let distance = perpendicular_distance(points[i], first, last)
      if distance > max_distance {
        max_distance = distance
        split = i
      }
    }
    if max_distance > tolerance {
      let left = []
      let right = []
      for i in 0..<=split {
        left.push(points[i])
      }
      for i in split.. Double {
  let direction = end.sub(start)
  let length = direction.length()
  if length == 0.0 {
    point.distance(start)
  } else {
    direction.cross(point.sub(start)).abs() / length
  }
}

///|
/// Clips a polygon against one axis-aligned rectangle edge.
pub enum ClipEdge {
  Left
  Right
  Top
  Bottom
} derive(Eq, Debug, ToJson)

///|
pub fn clip_polygon_edge(
  points : Array[Vec2],
  edge : ClipEdge,
  limit : Double,
) -> Array[Vec2] {
  let result = []
  if points.length() == 0 {
    result
  } else {
    let inside = fn(point : Vec2) -> Bool {
      match edge {
        Left => point.x >= limit
        Right => point.x <= limit
        Top => point.y >= limit
        Bottom => point.y <= limit
      }
    }
    let intersection = fn(a : Vec2, b : Vec2) -> Vec2 {
      match edge {
        Left | Right => {
          let t = (limit - a.x) / (b.x - a.x)
          a.lerp(b, t)
        }
        Top | Bottom => {
          let t = (limit - a.y) / (b.y - a.y)
          a.lerp(b, t)
        }
      }
    }
    let mut previous = points[points.length() - 1]
    let mut previous_inside = inside(previous)
    for current in points {
      let current_inside = inside(current)
      if current_inside != previous_inside {
        result.push(intersection(previous, current))
      }
      if current_inside {
        result.push(current)
      }
      previous = current
      previous_inside = current_inside
    }
    result
  }
}

///|
pub fn clip_polygon(points : Array[Vec2], bounds : Rect) -> Array[Vec2] {
  let left = clip_polygon_edge(points, Left, bounds.x)
  let right = clip_polygon_edge(left, Right, bounds.right())
  let top = clip_polygon_edge(right, Top, bounds.y)
  clip_polygon_edge(top, Bottom, bounds.bottom())
}