///|
/// Samples a path at approximately uniform arc-length intervals.
pub fn sample_path(path : Path, samples : Int) -> Array[Vec2] {
  let control = path.control_points()
  if samples <= 0 || control.length() == 0 {
    []
  } else if control.length() == 1 {
    let result = []
    for _ in 0.. Vec2 {
  let mut walked = 0.0
  for i in 0..<(points.length() - 1) {
    let left = points[i]
    let right = points[i + 1]
    let length = left.distance(right)
    if target <= walked + length {
      let amount = if length == 0.0 { 0.0 } else { (target - walked) / length }
      return left.lerp(right, amount)
    }
    walked += length
  }
  points[points.length() - 1]
}

///|
/// Returns points that satisfy a deterministic blue-noise-like minimum distance.
pub fn poisson_disc(
  width : Double,
  height : Double,
  minimum_distance : Double,
  candidates : Int,
  seed : UInt,
) -> Array[Vec2] {
  let result : Array[Vec2] = []
  if minimum_distance > 0.0 && candidates > 0 {
    let random = Random::new(seed)
    let attempts = candidates * 12
    for _ in 0..= candidates {
        break
      }
      let point = Vec2::new(random.range(0.0, width), random.range(0.0, height))
      let mut valid = true
      for existing in result {
        if existing.distance(point) < minimum_distance {
          valid = false
        }
      }
      if valid {
        result.push(point)
      }
    }
  }
  result
}

///|
/// Creates a weighted random walk in a bounded canvas.
pub fn random_walk(
  start : Vec2,
  steps : Int,
  step_length : Double,
  sampler : ValueNoise,
  bounds : Rect,
) -> Array[Vec2] {
  let result = [start]
  let mut current = start
  for i in 0.. Double {
  match mode {
    Nearest => field.get(x.to_int(), y.to_int()).unwrap_or(0.0)
    Bilinear => {
      let x0 = x.to_int()
      let y0 = y.to_int()
      let fx = x - x0.to_double()
      let fy = y - y0.to_double()
      let a = field.get(x0, y0).unwrap_or(0.0)
      let b = field.get(x0 + 1, y0).unwrap_or(a)
      let c = field.get(x0, y0 + 1).unwrap_or(a)
      let d = field.get(x0 + 1, y0 + 1).unwrap_or(a)
      a + (b - a) * fx + (c - a) * fy + (a - b - c + d) * fx * fy
    }
    CatmullRom => sample_field(field, x, y, Bilinear)
  }
}

///|
pub fn contour_points(
  field : ScalarField,
  threshold : Double,
) -> Array[Array[Vec2]] {
  let contours = []
  if field.width > 1 && field.height > 1 {
    for y in 0..<(field.height - 1) {
      for x in 0..<(field.width - 1) {
        let top = field.get(x, y).unwrap_or(0.0)
        let right = field.get(x + 1, y).unwrap_or(0.0)
        let bottom = field.get(x, y + 1).unwrap_or(0.0)
        let left = field.get(x, y + 1).unwrap_or(0.0)
        let points : Array[Vec2] = []
        if (top < threshold) != (right < threshold) {
          points.push(Vec2::new(x.to_double() + 0.5, y.to_double()))
        }
        if (right < threshold) != (bottom < threshold) {
          points.push(Vec2::new(x.to_double() + 1.0, y.to_double() + 0.5))
        }
        if (bottom < threshold) != (left < threshold) {
          points.push(Vec2::new(x.to_double() + 0.5, y.to_double() + 1.0))
        }
        if points.length() > 0 {
          contours.push(points)
        }
      }
    }
  }
  contours
}