///|
/// Represents an 8-bit RGB color.
pub(all) struct ColorRGB {
  r : Int
  g : Int
  b : Int
} derive(Debug, Eq, Default)

///|
/// Creates a new ColorRGB.
pub fn ColorRGB::new(r : Int, g : Int, b : Int) -> ColorRGB {
  {
    r: if r < 0 {
      0
    } else if r > 255 {
      255
    } else {
      r
    },
    g: if g < 0 {
      0
    } else if g > 255 {
      255
    } else {
      g
    },
    b: if b < 0 {
      0
    } else if b > 255 {
      255
    } else {
      b
    },
  }
}

///|
/// Converts RGB to 8-bit grayscale intensity using standard ITU-R BT.601 weights.
pub fn ColorRGB::to_grayscale(self : ColorRGB) -> Int {
  let gray = 0.299 * self.r.to_double() +
    0.587 * self.g.to_double() +
    0.114 * self.b.to_double()
  gray.round().to_int()
}

///|
/// Represents HSV color with H in [0, 360), S in [0, 1], V in [0, 1].
pub(all) struct ColorHSV {
  h : Double
  s : Double
  v : Double
} derive(Debug, Eq, Default)

///|
/// Creates a new ColorHSV.
pub fn ColorHSV::new(h : Double, s : Double, v : Double) -> ColorHSV {
  { h, s, v }
}

///|
/// Converts RGB to HSV.
pub fn ColorRGB::to_hsv(self : ColorRGB) -> ColorHSV {
  let r = self.r.to_double() / 255.0
  let g = self.g.to_double() / 255.0
  let b = self.b.to_double() / 255.0

  let max_val = if r > g {
    if r > b {
      r
    } else {
      b
    }
  } else if g > b {
    g
  } else {
    b
  }
  let min_val = if r < g {
    if r < b {
      r
    } else {
      b
    }
  } else if g < b {
    g
  } else {
    b
  }
  let delta = max_val - min_val

  let mut h = 0.0
  if delta > 1.0e-12 {
    if max_val == r {
      h = 60.0 * ((g - b) / delta % 6.0)
    } else if max_val == g {
      h = 60.0 * ((b - r) / delta + 2.0)
    } else {
      h = 60.0 * ((r - g) / delta + 4.0)
    }
    if h < 0.0 {
      h += 360.0
    }
  }

  let s = if max_val > 1.0e-12 { delta / max_val } else { 0.0 }
  let v = max_val

  { h, s, v }
}

///|
/// Converts HSV to RGB.
pub fn ColorHSV::to_rgb(self : ColorHSV) -> ColorRGB {
  let c = self.v * self.s
  let x = c * (1.0 - (self.h / 60.0 % 2.0 - 1.0).abs())
  let m = self.v - c

  let (r1, g1, b1) = if self.h < 60.0 {
    (c, x, 0.0)
  } else if self.h < 120.0 {
    (x, c, 0.0)
  } else if self.h < 180.0 {
    (0.0, c, x)
  } else if self.h < 240.0 {
    (0.0, x, c)
  } else if self.h < 300.0 {
    (x, 0.0, c)
  } else {
    (c, 0.0, x)
  }

  ColorRGB::new(
    ((r1 + m) * 255.0).round().to_int(),
    ((g1 + m) * 255.0).round().to_int(),
    ((b1 + m) * 255.0).round().to_int(),
  )
}

///|
/// Represents a 24-bit RGB Color Image.
pub(all) struct ColorImage {
  width : Int
  height : Int
  data : Array[ColorRGB]
} derive(Debug, Eq)

///|
/// Creates a new ColorImage.
pub fn ColorImage::new(width : Int, height : Int) -> ColorImage {
  let size = width * height
  let data = Array::make(size, ColorRGB::new(0, 0, 0))
  { width, height, data }
}

///|
/// Gets RGB pixel at (x, y).
pub fn ColorImage::get(self : ColorImage, x : Int, y : Int) -> ColorRGB {
  if x < 0 || x >= self.width || y < 0 || y >= self.height {
    ColorRGB::new(0, 0, 0)
  } else {
    self.data[y * self.width + x]
  }
}

///|
/// Sets RGB pixel at (x, y).
pub fn ColorImage::set(
  self : ColorImage,
  x : Int,
  y : Int,
  color : ColorRGB,
) -> Unit {
  if x >= 0 && x < self.width && y >= 0 && y < self.height {
    self.data[y * self.width + x] = color
  }
}

///|
/// Converts 24-bit ColorImage to 8-bit grayscale Image.
pub fn ColorImage::to_grayscale(self : ColorImage) -> Image {
  let gray = Image::new(self.width, self.height)
  for y in 0.. ColorImage {
  let col = ColorImage::new(self.width, self.height)
  for y in 0.. Image {
  let w = img.width
  let h = img.height
  let total = w * h
  if total == 0 {
    return img.clone()
  }

  // Compute histogram
  let hist = Array::make(256, 0)
  for y in 0.. 0 {
      cdf_min = cdf[i]
      break
    }
  }

  let denom = (total - cdf_min).to_double()
  let lut = Array::make(256, 0)
  if denom > 1.0e-12 {
    for i in 0..<256 {
      let val = ((cdf[i] - cdf_min).to_double() / denom * 255.0)
        .round()
        .to_int()
      lut[i] = if val < 0 { 0 } else if val > 255 { 255 } else { val }
    }
  }

  let res = Image::new(w, h)
  for y in 0.. Image {
  let w = img.width
  let h = img.height
  if w < 16 || h < 16 {
    return equalize_histogram(img)
  }

  let tile_w = w / grid_cols
  let tile_h = h / grid_rows
  let num_tiles = grid_cols * grid_rows
  let tile_size = tile_w * tile_h
  let clip_val = (clip_limit * (tile_size.to_double() / 256.0)).round().to_int()

  // Compute clipped CDF for each tile
  let tile_cdfs = Array::make(num_tiles * 256, 0.0)

  for ty in 0.. clip_val {
          excess += hist[i] - clip_val
          hist[i] = clip_val
        }
      }
      let bonus = excess / 256
      for i in 0..<256 {
        hist[i] += bonus
      }

      // Build CDF
      let tile_idx = (ty * grid_cols + tx) * 256
      let mut acc = 0.0
      for i in 0..<256 {
        acc += hist[i].to_double()
        tile_cdfs[tile_idx + i] = acc / actual_tile_pixels.to_double() * 255.0
      }
    }
  }

  // Bilinear interpolation across tiles
  let res = Image::new(w, h)
  for y in 0.. Int {
        if tx < 0 {
          0
        } else if tx >= grid_cols {
          grid_cols - 1
        } else {
          tx
        }
      }
      let clamp_ty = fn(ty : Int) -> Int {
        if ty < 0 {
          0
        } else if ty >= grid_rows {
          grid_rows - 1
        } else {
          ty
        }
      }

      let p = img.get(x, y)

      let c00 = tile_cdfs[(clamp_ty(ty0) * grid_cols + clamp_tx(tx0)) * 256 + p]
      let c10 = tile_cdfs[(clamp_ty(ty0) * grid_cols + clamp_tx(tx1)) * 256 + p]
      let c01 = tile_cdfs[(clamp_ty(ty1) * grid_cols + clamp_tx(tx0)) * 256 + p]
      let c11 = tile_cdfs[(clamp_ty(ty1) * grid_cols + clamp_tx(tx1)) * 256 + p]

      let top = c00 + fx * (c10 - c00)
      let bot = c01 + fx * (c11 - c01)
      let val = (top + fy * (bot - top)).round().to_int()

      let pixel = if val < 0 { 0 } else if val > 255 { 255 } else { val }
      res.set(x, y, pixel)
    }
  }

  res
}