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

///|
/// Returns the fraction (0..=1) along the line `line` corresponding to the
/// closest point to `target`. Returns `None` when the line is degenerate
/// (zero length).
pub fn line_locate_point(line : @type.Line, target : @type.Coord) -> Double? {
  let v = line.delta()
  let w = target - line.start()
  let v_dot_v = v.dot(v)
  if v_dot_v == 0.0 {
    return None
  }
  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 }
  Some(clamped)
}

///|
/// Returns the normalised position (0..=1) of the closest point on a
/// linestring to `target`, where the position is measured by total length.
pub fn line_string_locate_point(
  ls : @type.LineString,
  target : @type.Coord,
) -> Double? {
  let segments = ls.lines()
  if segments.length() == 0 {
    return None
  }
  let total_length = euclidean_length_line_string(ls)
  if total_length == 0.0 {
    return None
  }
  // Fold over the segments tracking (accumulated_length, best_position, best_dist²).
  let acc = segments
    .iter()
    .fold(init=(0.0, 0.0, @double.infinity), fn(acc, seg) {
      let (accumulated, best_position, best_dist) = acc
      let seg_length = euclidean_length_line(seg)
      let location_in_seg = line_locate_point(seg, target).unwrap_or(0.0)
      let projection = seg.start() + seg.delta().mul(location_in_seg)
      let dist = euclidean_distance_squared_coords(projection, target)
      let next_accumulated = accumulated + seg_length
      if dist < best_dist {
        let next_position = (accumulated + location_in_seg * seg_length) /
          total_length
        (next_accumulated, next_position, dist)
      } else {
        (next_accumulated, best_position, best_dist)
      }
    })
  let (_, best_position, _) = acc
  Some(best_position)
}