///|
/// Procedural noise utilities for texture, dithering, and synthetic fixtures.
pub(all) enum NoiseBlend {
  AddNoise
  MultiplyNoise
  ReplaceLuma
} derive(Eq, Debug)

///|
fn hash_noise(x : Int, y : Int, seed : Int) -> Int {
  let mut value = x * 1103515245 + y * 12345 + seed * 265443576
  value = (value ^ (value >> 11)) * 1597334677
  let result = value ^ (value >> 15)
  let positive = if result < 0 { -result } else { result }
  positive % 256
}

///|
fn interpolated_noise(x : Int, y : Int, scale : Int, seed : Int) -> Int {
  let safe_scale = if scale < 1 { 1 } else { scale }
  let grid_x = x / safe_scale
  let grid_y = y / safe_scale
  let next_x = grid_x + 1
  let next_y = grid_y + 1
  let fx = x % safe_scale
  let fy = y % safe_scale
  let top = (
      hash_noise(grid_x, grid_y, seed) * (safe_scale - fx) +
      hash_noise(next_x, grid_y, seed) * fx
    ) /
    safe_scale
  let bottom = (
      hash_noise(grid_x, next_y, seed) * (safe_scale - fx) +
      hash_noise(next_x, next_y, seed) * fx
    ) /
    safe_scale
  (top * (safe_scale - fy) + bottom * fy) / safe_scale
}

///|
/// Add deterministic noise with a per-channel strength.
pub fn Canvas::noise(
  self : Canvas,
  amount : Int,
  seed : Int,
  blend : NoiseBlend,
) -> Canvas {
  let safe_amount = if amount < 0 {
    0
  } else if amount > 255 {
    255
  } else {
    amount
  }
  let result = self.copy()
  for y in 0.. 255 { 255 } else { red }) *
        factor /
        255).to_byte(),
        ((if green < 0 { 0 } else if green > 255 { 255 } else { green }) *
        factor /
        255).to_byte(),
        ((if blue < 0 { 0 } else if blue > 255 { 255 } else { blue }) *
        factor /
        255).to_byte(),
        source.alpha(),
      )
    }
  }
  result
}

///|
/// Layer two scales of value noise for a smoother texture.
pub fn Canvas::fractal_noise(self : Canvas, amount : Int, seed : Int) -> Canvas {
  let safe_amount = if amount < 0 {
    0
  } else if amount > 255 {
    255
  } else {
    amount
  }
  let result = self.copy()
  for y in 0.. 255 { 255 } else { red }).to_byte(),
        (if green < 0 { 0 } else if green > 255 { 255 } else { green }).to_byte(),
        (if blue < 0 { 0 } else if blue > 255 { 255 } else { blue }).to_byte(),
        source.alpha(),
      )
    }
  }
  result
}

///|
/// Add blue-noise-like ordered variation before palette quantization.
pub fn Canvas::noise_dither(self : Canvas, amount : Int, seed : Int) -> Canvas {
  let result = self.copy()
  for y in 0.. 255 { 255 } else { value }
      result.pixels[x + y * self.width] = rgba(
        safe.to_byte(),
        safe.to_byte(),
        safe.to_byte(),
        source.alpha(),
      )
    }
  }
  result
}