///|
fn lerp_point(a : Point, b : Point, t : Double) -> Point {
  Point(x=a.x + (b.x - a.x) * t, y=a.y + (b.y - a.y) * t)
}

///|
fn matches_mode(mode : EdgeMode, gradient : Double) -> Bool {
  match mode {
    Rising => gradient > 0.0
    Falling => gradient < 0.0
    Both => gradient != 0.0
  }
}

///|
pub fn edge_profile(
  image : GrayImage,
  start : Point,
  finish : Point,
  steps? : Int = 96,
) -> Array[ProfileSample] {
  let n = if steps < 2 { 2 } else { steps }
  let samples : Array[ProfileSample] = []
  for i in 0.. EdgePoint {
  let g0 = previous.gradient.abs()
  let g1 = current.gradient.abs()
  let total = g0 + g1
  let t = if total <= 0.0 { 1.0 } else { (threshold - g0) / total }
  let clamped = t.clamp(min=0.0, max=1.0)
  {
    point: lerp_point(previous.point, current.point, clamped),
    strength: current.gradient.abs(),
    polarity: if current.gradient >= 0.0 {
      1
    } else {
      -1
    },
  }
}

///|
pub fn ProfileSample::detect_edges(
  samples : Array[ProfileSample],
  threshold : Double,
  mode? : EdgeMode = Both,
) -> Array[EdgePoint] {
  let edges : Array[EdgePoint] = []
  for i in 1..= threshold &&
      matches_mode(mode, current.gradient) {
      edges.push(subpixel_edge(samples[i - 1], current, threshold))
    }
  }
  edges
}

///|
pub fn gauge_width(
  image : GrayImage,
  start : Point,
  finish : Point,
  threshold? : Double = 0.15,
) -> Double {
  match try_gauge_width(image, start, finish, threshold~) {
    Some(width) => width
    None => 0.0
  }
}

///|
pub fn try_gauge_width(
  image : GrayImage,
  start : Point,
  finish : Point,
  threshold? : Double = 0.15,
) -> Double? {
  let samples = edge_profile(image, start, finish)
  let edges = ProfileSample::detect_edges(samples, threshold, mode=Both)
  if edges.length() < 2 {
    None
  } else {
    Some(distance(edges[0].point, edges[edges.length() - 1].point))
  }
}