// Ported from georust/geo
// https://github.com/georust/geo/blob/main/geo/src/algorithm/closest_point.rs

///|
/// Result of querying the closest point on a geometry.
pub(all) enum Closest {
  /// The query point is exactly on the geometry.
  Intersection(@type.Coord)
  /// A unique closest point that does not lie on the query.
  SinglePoint(@type.Coord)
  /// Multiple closest points equidistant — the geometry is degenerate or
  /// has been queried from a special location.
  Indeterminate
} derive(Eq, Debug)

// MARK: trait ClosestPoint + impls ------------------------------------------

///|
/// Computes the closest point on `self` to a target coordinate.
pub(open) trait ClosestPoint {
  fn closest_point(Self, @type.Coord) -> Closest
}

///|
pub impl ClosestPoint for @type.Line with fn closest_point(self, target) {
  let v = self.delta()
  let w = target - self.start()
  let v_dot_v = v.dot(v)
  if v_dot_v == 0.0 {
    return if self.start() == target {
      Intersection(self.start())
    } else {
      SinglePoint(self.start())
    }
  }
  let t = w.dot(v) / v_dot_v
  let clamped = if t < 0.0 { 0.0 } else if t > 1.0 { 1.0 } else { t }
  let proj = self.start() + v.mul(clamped)
  if proj == target {
    Intersection(proj)
  } else {
    SinglePoint(proj)
  }
}

///|
pub impl ClosestPoint for @type.LineString with fn closest_point(self, target) {
  let segments = self.lines()
  if segments.length() == 0 {
    return Indeterminate
  }
  // Fold tracks the best candidate so far. Once an `Intersection` is seen
  // the result is locked in and remains stable across the rest of the fold.
  segments
  .iter()
  .fold(init=Closest::Indeterminate, fn(acc, seg) {
    match acc {
      Intersection(_) => acc
      _ =>
        match ClosestPoint::closest_point(seg, target) {
          Intersection(c) => Intersection(c)
          SinglePoint(c) =>
            match acc {
              SinglePoint(prev) => {
                let d_new = euclidean_distance_squared_coords(c, target)
                let d_prev = euclidean_distance_squared_coords(prev, target)
                if d_new < d_prev {
                  SinglePoint(c)
                } else {
                  acc
                }
              }
              _ => SinglePoint(c)
            }
          Indeterminate => acc
        }
    }
  })
}