///|
/// Generates a 1D Gaussian kernel with given radius and standard deviation sigma.
pub fn generate_gaussian_kernel_1d(
  radius : Int,
  sigma : Double,
) -> Array[Double] {
  let size = 2 * radius + 1
  let kernel = Array::make(size, 0.0)
  let two_sigma_sq = 2.0 * sigma * sigma
  let mut sum = 0.0

  for i in 0.. 1.0e-12 {
    for i in 0.. Image {
  let w = img.width
  let h = img.height
  if w == 0 || h == 0 || radius <= 0 {
    return img.clone()
  }

  let kernel = generate_gaussian_kernel_1d(radius, sigma)
  let k_len = kernel.length()

  // Horizontal pass
  let temp = Image::new(w, h)
  for y in 0..= w { w - 1 } else { ix }
        sum += img.get(cx, y).to_double() * kernel[k]
      }
      temp.set(x, y, sum.round().to_int())
    }
  }

  // Vertical pass
  let res = Image::new(w, h)
  for y in 0..= h { h - 1 } else { iy }
        sum += temp.get(x, cy).to_double() * kernel[k]
      }
      res.set(x, y, sum.round().to_int())
    }
  }

  res
}

///|
/// Applies fast box blur with given kernel radius using integral image.
pub fn filter_box_blur(img : Image, radius : Int) -> Image {
  let w = img.width
  let h = img.height
  let res = Image::new(w, h)
  if w == 0 || h == 0 || radius <= 0 {
    return img.clone()
  }

  let integral = compute_integral_image(img)

  for y in 0..= w { w - 1 } else { x + radius }
      let y1 = if y + radius >= h { h - 1 } else { y + radius }

      let count = (x1 - x0 + 1) * (y1 - y0 + 1)
      let sum_val = query_integral_sum(integral, w, h, x0, y0, x1, y1)
      let avg = sum_val / count
      res.set(x, y, avg)
    }
  }

  res
}

///|
/// Applies 3x3 or 5x5 median filter for salt-and-pepper noise reduction.
pub fn filter_median(img : Image, radius : Int) -> Image {
  let w = img.width
  let h = img.height
  let res = Image::new(w, h)
  if w == 0 || h == 0 || radius <= 0 {
    return img.clone()
  }

  let r = radius
  let size = (2 * r + 1) * (2 * r + 1)
  let window = Array::make(size, 0)
  let mid = size / 2

  for y in 0..= w { w - 1 } else { ix }
          let cy = if iy < 0 { 0 } else if iy >= h { h - 1 } else { iy }
          window[count] = img.get(cx, cy)
          count += 1
        }
      }

      // Simple insertion sort on small window
      for i in 1..= 0 && window[j] > key {
          window[j + 1] = window[j]
          j -= 1
        }
        window[j + 1] = key
      }

      res.set(x, y, window[mid])
    }
  }

  res
}

///|
/// Edge-preserving bilateral filter using spatial Gaussian and photometric range Gaussian.
pub fn filter_bilateral(
  img : Image,
  radius : Int,
  sigma_space : Double,
  sigma_color : Double,
) -> Image {
  let w = img.width
  let h = img.height
  let res = Image::new(w, h)
  if w == 0 || h == 0 || radius <= 0 {
    return img.clone()
  }

  let two_sigma_s_sq = 2.0 * sigma_space * sigma_space
  let two_sigma_c_sq = 2.0 * sigma_color * sigma_color

  for y in 0..= w { w - 1 } else { ix }
          let cy = if iy < 0 { 0 } else if iy >= h { h - 1 } else { iy }
          let neighbor_val = img.get(cx, cy).to_double()

          let dist_spatial_sq = (dx * dx + dy * dy).to_double()
          let dist_color_sq = (neighbor_val - center_val) *
            (neighbor_val - center_val)

          let w_s = @math.exp(-dist_spatial_sq / two_sigma_s_sq)
          let w_c = @math.exp(-dist_color_sq / two_sigma_c_sq)
          let weight = w_s * w_c

          sum_filtered += neighbor_val * weight
          sum_weights += weight
        }
      }

      if sum_weights > 1.0e-12 {
        res.set(x, y, (sum_filtered / sum_weights).round().to_int())
      } else {
        res.set(x, y, center_val.round().to_int())
      }
    }
  }

  res
}

///|
/// Computes Sobel 3x3 horizontal (Gx) and vertical (Gy) gradients.
/// Returns (Gx, Gy) images with signed double precision in flat arrays.
pub fn filter_sobel(img : Image) -> (Array[Double], Array[Double]) {
  let w = img.width
  let h = img.height
  let size = w * h
  let gx = Array::make(size, 0.0)
  let gy = Array::make(size, 0.0)

  for y in 0.. (Array[Double], Array[Double]) {
  let w = img.width
  let h = img.height
  let size = w * h
  let gx = Array::make(size, 0.0)
  let gy = Array::make(size, 0.0)

  for y in 0.. (Array[Double], Array[Double]) {
  let size = gx.length()
  let mag = Array::make(size, 0.0)
  let angle = Array::make(size, 0.0)

  for i in 0.. Image {
  let w = img.width
  let h = img.height
  let res = Image::new(w, h)

  for y in 0..