///|
/// Interface for external or custom 2D noise samplers
pub trait NoiseSampler {
  fn sample_2d(self : Self, x : Double, y : Double) -> Double
}

///|
/// A default trigonometric pseudo-noise generator that implements NoiseSampler
/// Useful as a zero-dependency fallback.
pub(all) struct TrigNoise {
  frequency : Double
} derive(Eq, Debug, ToJson)

///|
/// Create a new TrigNoise sampler
pub fn TrigNoise::new(frequency : Double) -> TrigNoise {
  { frequency, }
}

///|
/// Implementation of NoiseSampler for TrigNoise
pub impl NoiseSampler for TrigNoise with fn sample_2d(self, x, y) {
  let nx = x * self.frequency
  let ny = y * self.frequency
  // Combine multiple sin/cos waves at different octaves/scales
  @math.sin(nx) * 0.5 +
  @math.cos(ny) * 0.3 +
  @math.sin(nx * 2.3 + ny * 1.7) * 0.2
}

///|
/// Wrap a simple function as a NoiseSampler
pub(all) struct FnNoise {
  f : (Double, Double) -> Double
}

///|
/// Create a NoiseSampler from a function closure
pub fn FnNoise::new(f : (Double, Double) -> Double) -> FnNoise {
  { f, }
}

///|
/// Implementation of NoiseSampler for FnNoise
pub impl NoiseSampler for FnNoise with fn sample_2d(self, x, y) {
  (self.f)(x, y)
}

///|
extend ValueNoise with NoiseSampler::{sample_2d}

///|
extend TrigNoise with NoiseSampler::{sample_2d}

///|
/// Domain warping: distorts the (x, y) coordinates using noise
pub fn[N : NoiseSampler] warp_point(
  sampler : N,
  x : Double,
  y : Double,
  warp_strength : Double,
  warp_scale : Double,
) -> (Double, Double) {
  let dx = sampler.sample_2d(x * warp_scale + 5.2, y * warp_scale + 1.3) *
    warp_strength
  let dy = sampler.sample_2d(x * warp_scale + 1.7, y * warp_scale + 9.1) *
    warp_strength
  (x + dx, y + dy)
}

///|
/// Computes a flow field direction angle (in radians) at (x, y)
pub fn[N : NoiseSampler] flow_field_angle(
  sampler : N,
  x : Double,
  y : Double,
  scale : Double,
) -> Double {
  let val = sampler.sample_2d(x * scale, y * scale)
  // map [-1.0, 1.0] range to [0.0, 2.0 * PI]
  let norm = (val + 1.0) / 2.0
  norm * 2.0 * @math.PI
}