///|
/// Border handling mode for convolution and interpolation operations.
///
/// - `Replicate`: clamp coordinates to the nearest edge pixel.
/// - `Reflect`: mirror coordinates with edge duplication.
/// - `Wrap`: tile coordinates periodically.
/// - `Constant(r, g, b, a)`: use the given RGBA color outside the image.
pub(all) enum BorderMode {
  Replicate
  Reflect
  Wrap
  Constant(Byte, Byte, Byte, Byte)
}

///|
/// Clamp coordinates to valid range (replicate border).
fn clamp_coord(v : Int, max_v : Int) -> Int {
  if v < 0 {
    0
  } else if v > max_v {
    max_v
  } else {
    v
  }
}

///|
/// Reflect coordinates (mirror with edge duplication).
fn reflect_coord(v : Int, max_v : Int) -> Int {
  if max_v == 0 {
    0
  } else {
    let size = max_v + 1
    let twice = size * 2
    let mut r = v
    if r < 0 {
      r = -r - 1
    }
    r = r % twice
    if r > max_v {
      twice - 1 - r
    } else {
      r
    }
  }
}

///|
/// Wrap coordinates (tile).
fn wrap_coord(v : Int, max_v : Int) -> Int {
  if max_v == 0 {
    0
  } else {
    let size = max_v + 1
    let mut r = v % size
    if r < 0 {
      r = r + size
    }
    r
  }
}

///|
/// Get pixel at coordinates with specified border mode.
pub fn get_pixel(
  img : Image,
  y : Int,
  x : Int,
  mode : BorderMode,
) -> (Byte, Byte, Byte, Byte) {
  match mode {
    Replicate => {
      let cy = clamp_coord(y, img.h - 1)
      let cx = clamp_coord(x, img.w - 1)
      let o = img.offset(cy, cx)
      (img.data[o], img.data[o + 1], img.data[o + 2], img.data[o + 3])
    }
    Reflect => {
      let cy = reflect_coord(y, img.h - 1)
      let cx = reflect_coord(x, img.w - 1)
      let o = img.offset(cy, cx)
      (img.data[o], img.data[o + 1], img.data[o + 2], img.data[o + 3])
    }
    Wrap => {
      let cy = wrap_coord(y, img.h - 1)
      let cx = wrap_coord(x, img.w - 1)
      let o = img.offset(cy, cx)
      (img.data[o], img.data[o + 1], img.data[o + 2], img.data[o + 3])
    }
    Constant(r, g, b, a) =>
      if y >= 0 && y < img.h && x >= 0 && x < img.w {
        let o = img.offset(y, x)
        (img.data[o], img.data[o + 1], img.data[o + 2], img.data[o + 3])
      } else {
        (r, g, b, a)
      }
  }
}

///|
/// Get luma at coordinates with specified border mode.
pub fn luma_at_mode(img : Image, y : Int, x : Int, mode : BorderMode) -> Double {
  let (r, g, b, _) = get_pixel(img, y, x, mode)
  ((r.to_int() * 77 + g.to_int() * 150 + b.to_int() * 29) >> 8).to_double()
}

///|
/// Get luma at coordinates with replicate border mode.
pub fn luma_at(img : Image, y : Int, x : Int) -> Double {
  luma_at_mode(img, y, x, Replicate)
}

///|
/// Bilinear interpolation of luma at fractional coordinates with specified border mode.
pub fn bilinear_luma(
  img : Image,
  fy : Double,
  fx : Double,
  mode : BorderMode,
) -> Double {
  let y0 = fy.floor().to_int()
  let x0 = fx.floor().to_int()
  let y1 = y0 + 1
  let x1 = x0 + 1
  let dy = fy - y0.to_double()
  let dx = fx - x0.to_double()
  let v00 = luma_at_mode(img, y0, x0, mode)
  let v01 = luma_at_mode(img, y0, x1, mode)
  let v10 = luma_at_mode(img, y1, x0, mode)
  let v11 = luma_at_mode(img, y1, x1, mode)
  v00 * (1.0 - dy) * (1.0 - dx) +
  v01 * (1.0 - dy) * dx +
  v10 * dy * (1.0 - dx) +
  v11 * dy * dx
}