///|
pub fn centroid_of_points(points : Array[Point]) -> Point {
  polygon_centroid(points)
}

///|
pub fn bounding_box_of_points(points : Array[Point]) -> Bounds {
  polygon_bounds(points)
}

///|
pub fn translate_points(
  points : Array[Point],
  dx : Double,
  dy : Double,
) -> Array[Point] {
  points.map(fn(point) { Point::Point(x=point.x + dx, y=point.y + dy) })
}

///|
pub fn scale_points(
  points : Array[Point],
  center : Point,
  factor : Double,
) -> Array[Point] {
  points.map(fn(point) {
    Point::Point(
      x=center.x + (point.x - center.x) * factor,
      y=center.y + (point.y - center.y) * factor,
    )
  })
}

///|
pub fn reflect_points(points : Array[Point], line : Line) -> Array[Point] {
  points.map(fn(point) { reflect_point(point, line) })
}

///|
pub fn point_at_distance(
  start : Point,
  finish : Point,
  distance_from_start : Double,
) -> Point {
  let length = distance(start, finish)
  if length <= 0.0000001 {
    start
  } else {
    let t = distance_from_start / length
    Point::Point(x=lerp(start.x, finish.x, t), y=lerp(start.y, finish.y, t))
  }
}

///|
pub fn interpolate_points(points : Array[Point], count : Int) -> Array[Point] {
  if points.length() < 2 || count <= 0 {
    []
  } else {
    let result : Array[Point] = []
    let total = line_length(points)
    for i in 0..= target {
          selected = point_at_distance(
            points[j - 1],
            points[j],
            target - accumulated,
          )
          break
        }
        accumulated += segment
        selected = points[j]
      }
      result.push(selected)
    }
    result
  }
}

///|
pub fn polyline_length(points : Array[Point], closed? : Bool = false) -> Double {
  let length = line_length(points)
  if closed && points.length() > 1 {
    length + distance(points[0], points[points.length() - 1])
  } else {
    length
  }
}

///|
pub fn polyline_tangent(points : Array[Point], index : Int) -> Vector2 {
  if points.length() < 2 {
    Vector2::Vector2(x=0.0, y=0.0)
  } else {
    let i = index.clamp(min=0, max=points.length() - 1)
    let first = points[(i - 1).max(0)]
    let last = points[(i + 1).min(points.length() - 1)]
    Vector2::Vector2(x=last.x - first.x, y=last.y - first.y).normalized()
  }
}

///|
pub fn polyline_normal(points : Array[Point], index : Int) -> Vector2 {
  let tangent = polyline_tangent(points, index)
  Vector2::Vector2(x=-tangent.y, y=tangent.x)
}

///|
pub fn point_line_distance(
  first : Point,
  second : Point,
  point : Point,
) -> Double {
  line_from_points(first, second).distance_to(point)
}

///|
pub fn segment_projection(
  first : Point,
  second : Point,
  point : Point,
) -> Point {
  let direction = Vector2::Vector2(x=second.x - first.x, y=second.y - first.y)
  let denominator = direction.dot(direction)
  if denominator <= 0.0000001 {
    first
  } else {
    let relative = Vector2::Vector2(x=point.x - first.x, y=point.y - first.y)
    let t = clamp_double(relative.dot(direction) / denominator, 0.0, 1.0)
    Point::Point(x=first.x + direction.x * t, y=first.y + direction.y * t)
  }
}

///|
pub fn segment_distance(first : Point, second : Point, point : Point) -> Double {
  distance(segment_projection(first, second, point), point)
}

///|
pub fn triangle_area(first : Point, second : Point, third : Point) -> Double {
  (second.x - first.x) * (third.y - first.y) -
  (second.y - first.y) * (third.x - first.x)
}

///|
pub fn triangle_orientation(
  first : Point,
  second : Point,
  third : Point,
) -> Int {
  let area = triangle_area(first, second, third)
  if area > 0.0 {
    1
  } else if area < 0.0 {
    -1
  } else {
    0
  }
}

///|
pub fn point_inside_polygon(point : Point, polygon : Array[Point]) -> Bool {
  if polygon.length() < 3 {
    false
  } else {
    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 + 0.0000000001) + a.x {
        inside = !inside
      }
      j = i
    }
    inside
  }
}

///|
pub fn polygon_distance(point : Point, polygon : Array[Point]) -> Double {
  if polygon.is_empty() {
    0.0
  } else {
    let mut result = distance(point, polygon[0])
    for i in 1.. Point {
  Point::Point(
    x=Double::from_int(bounds.min_x + bounds.max_x) / 2.0,
    y=Double::from_int(bounds.min_y + bounds.max_y) / 2.0,
  )
}

///|
pub fn line_angle_from_points(first : Point, second : Point) -> Double {
  measure_angle(first, second)
}

///|
pub fn angle_difference(first : Double, second : Double) -> Double {
  normalize_angle(first - second).abs()
}

///|
pub fn point_set_spread(points : Array[Point]) -> Double {
  if points.is_empty() {
    0.0
  } else {
    let center = polygon_centroid(points)
    maximum_value(points.map(fn(point) { distance(point, center) }))
  }
}