// Copyright (c) 2025 lws
// Color utilities — color space conversions and pixel manipulations

//-----------------------------------------------------------------------------
// Alpha blending and color operations
//-----------------------------------------------------------------------------

///|
/// Blend this color with another color using alpha compositing (over operator)
/// The result replaces the background color with this (foreground) color on top
/// `alpha` is in 0..255 range for per-pixel control
pub fn Color::blend(self : Color, other : Color) -> Color {
  let src_a = self.a
  let dst_a = 255 - src_a
  let r = (self.r * src_a + other.r * dst_a) / 255
  let g = (self.g * src_a + other.g * dst_a) / 255
  let b = (self.b * src_a + other.b * dst_a) / 255
  let a = src_a + other.a * dst_a / 255
  Color::new(r, g, b, a)
}

///|
/// Alpha blend with a specific alpha override (0..255)
pub fn Color::blend_alpha(self : Color, other : Color, alpha : Int) -> Color {
  let effective_a = self.a * alpha / 255
  let src_a = effective_a
  let dst_a = 255 - effective_a
  let r = (self.r * src_a + other.r * dst_a) / 255
  let g = (self.g * src_a + other.g * dst_a) / 255
  let b = (self.b * src_a + other.b * dst_a) / 255
  let a = effective_a + other.a * dst_a / 255
  Color::new(r, g, b, a)
}

///|
/// Invert this color (255 - each channel), preserves alpha
pub fn Color::invert(self : Color) -> Color {
  Color::new(255 - self.r, 255 - self.g, 255 - self.b, self.a)
}

///|
/// Linear interpolation between two colors (t in 0..255 fixed-point)
/// Returns an integer-blended color without floating-point
pub fn Color::lerp(self : Color, other : Color, t : Int) -> Color {
  let inv_t = 255 - t
  let r = (self.r * inv_t + other.r * t) / 255
  let g = (self.g * inv_t + other.g * t) / 255
  let b = (self.b * inv_t + other.b * t) / 255
  let a = (self.a * inv_t + other.a * t) / 255
  Color::new(r, g, b, a)
}

//-----------------------------------------------------------------------------
// HSL color space conversions
//-----------------------------------------------------------------------------

///|
/// Convert an RGB color to HSL (Hue 0..360, Saturation 0..100, Lightness 0..100)
/// Uses integer arithmetic to avoid floating-point precision issues
pub fn Color::to_hsl(self : Color) -> (Int, Int, Int) {
  let r = self.r
  let g = self.g
  let b = self.b

  // Find min and max
  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

  // Lightness
  let l = (max_val + min_val) * 50 / 255

  // Gray (no saturation)
  if delta == 0 {
    return (0, 0, l)
  }

  // Saturation
  let s = if l > 50 {
    delta * 255 * 100 / (510 - (max_val + min_val))
  } else {
    delta * 255 * 100 / (max_val + min_val)
  }

  // Hue
  let h = if max_val == r {
    60 * (g - b) * 100 / (delta * 255)
  } else if max_val == g {
    120 + 60 * (b - r) * 100 / (delta * 255)
  } else {
    240 + 60 * (r - g) * 100 / (delta * 255)
  }
  let hue = if h < 0 { h + 360 } else { h }
  (hue, s, l)
}

///|
/// Create a Color from HSL values (Hue 0..360, Saturation 0..100, Lightness 0..100)
pub fn Color::from_hsl(hue : Int, saturation : Int, lightness : Int) -> Color {
  let h = hue % 360
  let s = if saturation > 100 { 100 } else { saturation }
  let l = if lightness > 100 { 100 } else { lightness }

  if s == 0 {
    let v = l * 255 / 100
    return Color::from_rgb(v, v, v)
  }

  let hue_f = h
  let s_f = s
  let l_f = l

  let q = if l_f < 50 {
    l_f * (100 + s_f) / 100
  } else {
    l_f + s_f - l_f * s_f / 100
  }
  let p = 2 * l_f - q

  let r = hue_to_rgb(p, q, hue_f + 120)
  let g = hue_to_rgb(p, q, hue_f)
  let b = hue_to_rgb(p, q, hue_f - 120)

  let tr = r * 255 / 100
  let tg = g * 255 / 100
  let tb = b * 255 / 100

  Color::from_rgb(tr, tg, tb)
}

///|
/// HSL helper: convert hue position to RGB component
fn hue_to_rgb(p : Int, q : Int, t_raw : Int) -> Int {
  let mut t = t_raw
  if t < 0 {
    t = t + 360
  }
  if t >= 360 {
    t = t - 360
  }
  if t < 60 {
    return p + (q - p) * t / 60
  } else if t < 180 {
    return q
  } else if t < 240 {
    return p + (q - p) * (240 - t) / 60
  } else {
    return p
  }
}

//-----------------------------------------------------------------------------
// HSV color space conversions
//-----------------------------------------------------------------------------

///|
/// Convert RGB to HSV (Hue 0..360, Saturation 0..100, Value 0..100)
pub fn Color::to_hsv(self : Color) -> (Int, Int, Int) {
  let r = self.r
  let g = self.g
  let b = self.b

  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

  // Value
  let v = max_val * 100 / 255

  // Gray
  if delta == 0 {
    return (0, 0, v)
  }

  // Saturation
  let s = delta * 100 / max_val

  // Hue
  let h = if max_val == r {
    60 * (g - b) * 100 / (delta * 255)
  } else if max_val == g {
    120 + 60 * (b - r) * 100 / (delta * 255)
  } else {
    240 + 60 * (r - g) * 100 / (delta * 255)
  }
  let hue = if h < 0 { h + 360 } else { h }
  (hue, s, v)
}

///|
/// Create a Color from HSV values (Hue 0..360, Saturation 0..100, Value 0..100)
pub fn Color::from_hsv(hue : Int, saturation : Int, value : Int) -> Color {
  let h = hue % 360
  let s = if saturation > 100 { 100 } else { saturation }
  let v = if value > 100 { 100 } else { value }

  if s == 0 {
    let gv = v * 255 / 100
    return Color::from_rgb(gv, gv, gv)
  }

  let region = h / 60
  let remainder = h % 60

  let p_val = v * (100 - s) / 100
  let q_val = v * (100 - s * remainder / 60) / 100
  let t_val = v * (100 - s * (60 - remainder) / 60) / 100

  let (r_val, g_val, b_val) = match region {
    0 => (v, t_val, p_val)
    1 => (q_val, v, p_val)
    2 => (p_val, v, t_val)
    3 => (p_val, q_val, v)
    4 => (t_val, p_val, v)
    _ => (v, p_val, q_val)
  }

  let r = r_val * 255 / 100
  let g = g_val * 255 / 100
  let b = b_val * 255 / 100
  Color::from_rgb(r, g, b)
}

//-----------------------------------------------------------------------------
// Perceptual color distance
//-----------------------------------------------------------------------------

///|
/// Compute a simple perceptual color distance (weighted Euclidean)
/// Uses simplified CIE76-like formula with weighted RGB components.
/// Returns 0..441 for maximum distance (black vs white at max alpha).
pub fn Color::distance(self : Color, other : Color) -> Int {
  let dr = self.r - other.r
  let dg = self.g - other.g
  let db = self.b - other.b
  let da = self.a - other.a

  // Weighted by human perception sensitivity (green most, blue least)
  let rmean = (self.r + other.r) / 2
  let r_weight = (512 + rmean) / 256 // red gets more weight when both are red
  let g_weight = 4 // green is most visible
  let b_weight = (767 - rmean) / 256 // blue gets less weight when colors are red

  let squared = dr * dr * r_weight +
    dg * dg * g_weight +
    db * db * b_weight +
    da * da * 2
  squared / 16 // Normalized to reasonable range
}

//-----------------------------------------------------------------------------
// Image-level color utilities
//-----------------------------------------------------------------------------

///|
/// Compute the average color of the image
pub fn Image::average_color(self : Image) -> Color {
  let pixel_count = self.width * self.height
  let mut r_sum = 0
  let mut g_sum = 0
  let mut b_sum = 0
  let mut a_sum = 0

  match self.format {
    PixelFormat::Gray8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let v = self.data[i].to_int()
        r_sum = r_sum + v
        g_sum = g_sum + v
        b_sum = b_sum + v
        a_sum = a_sum + 255
      }
    PixelFormat::GrayA8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let src = i * 2
        let v = self.data[src].to_int()
        let a = self.data[src + 1].to_int()
        r_sum = r_sum + v
        g_sum = g_sum + v
        b_sum = b_sum + v
        a_sum = a_sum + a
      }
    PixelFormat::RGB8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let src = i * 3
        r_sum = r_sum + self.data[src].to_int()
        g_sum = g_sum + self.data[src + 1].to_int()
        b_sum = b_sum + self.data[src + 2].to_int()
        a_sum = a_sum + 255
      }
    PixelFormat::RGBA8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let src = i * 4
        r_sum = r_sum + self.data[src].to_int()
        g_sum = g_sum + self.data[src + 1].to_int()
        b_sum = b_sum + self.data[src + 2].to_int()
        a_sum = a_sum + self.data[src + 3].to_int()
      }
  }

  if pixel_count == 0 {
    return Color::default()
  }

  let avg_r = r_sum / pixel_count
  let avg_g = g_sum / pixel_count
  let avg_b = b_sum / pixel_count
  let avg_a = a_sum / pixel_count
  Color::new(avg_r, avg_g, avg_b, avg_a)
}

///|
/// Compute a 256-bin luminance histogram from the image
/// Returns an array[256] of pixel counts, indexed by luminance value (0-255)
pub fn Image::histogram(self : Image) -> Array[Int] {
  let bins = Array::make(256, 0)
  let pixel_count = self.width * self.height

  match self.format {
    PixelFormat::Gray8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let v = self.data[i].to_int()
        bins[v] = bins[v] + 1
      }
    PixelFormat::GrayA8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let v = self.data[i * 2].to_int()
        bins[v] = bins[v] + 1
      }
    PixelFormat::RGB8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let src = i * 3
        let r = self.data[src].to_int()
        let g = self.data[src + 1].to_int()
        let b = self.data[src + 2].to_int()
        // ITU-R BT.601 luminance
        let lum = (r * 299 + g * 587 + b * 114) / 1000
        bins[lum] = bins[lum] + 1
      }
    PixelFormat::RGBA8 =>
      for i = 0; i < pixel_count; i = i + 1 {
        let src = i * 4
        let r = self.data[src].to_int()
        let g = self.data[src + 1].to_int()
        let b = self.data[src + 2].to_int()
        let lum = (r * 299 + g * 587 + b * 114) / 1000
        bins[lum] = bins[lum] + 1
      }
  }

  bins
}

///|
/// Brighten the image by adding a delta to all channels
/// Positive delta brightens, negative darkens. Clamped to 0..255.
/// Returns a new Image (does not modify self).
pub fn Image::brighten(self : Image, delta : Int) -> Image {
  let pixel_count = self.width * self.height
  let bpp = self.bytes_per_pixel()
  let out_size = pixel_count * bpp
  let _buf = Array::make(out_size, Byte::default())

  for i = 0; i < out_size; i = i + 1 {
    // Only adjust RGB channels, skip alpha
    let is_alpha = match self.format {
      PixelFormat::GrayA8 => i % 2 == 1
      PixelFormat::RGBA8 => i % 4 == 3
      _ => false
    }
    if is_alpha {
      _buf[i] = self.data[i]
    } else {
      let v = self.data[i].to_int() + delta
      if v > 255 {
        _buf[i] = b'\xFF'
      } else if v < 0 {
        _buf[i] = b'\x00'
      } else {
        _buf[i] = v.to_byte()
      }
    }
  }

  Image::new(self.width, self.height, self.format, Bytes::from_array(_buf))
}

///|
/// Convert an image to grayscale in-place (returns Gray8 or GrayA8)
pub fn Image::to_grayscale(self : Image) -> Image {
  match self.format {
    PixelFormat::Gray8 | PixelFormat::GrayA8 => self
    _ => {
      let pixel_count = self.width * self.height
      let _buf = Array::make(pixel_count, Byte::default())
      match self.format {
        PixelFormat::RGB8 =>
          for i = 0; i < pixel_count; i = i + 1 {
            let src = i * 3
            let r = self.data[src].to_int()
            let g = self.data[src + 1].to_int()
            let b = self.data[src + 2].to_int()
            let lum = (r * 299 + g * 587 + b * 114) / 1000
            _buf[i] = lum.to_byte()
          }
        PixelFormat::RGBA8 =>
          for i = 0; i < pixel_count; i = i + 1 {
            let src = i * 4
            let r = self.data[src].to_int()
            let g = self.data[src + 1].to_int()
            let b = self.data[src + 2].to_int()
            let lum = (r * 299 + g * 587 + b * 114) / 1000
            _buf[i] = lum.to_byte()
          }
        _ =>
          // Should not reach here
          for i = 0; i < pixel_count; i = i + 1 {
            _buf[i] = b'\x00'
          }
      }
      Image::new(
        self.width,
        self.height,
        PixelFormat::Gray8,
        Bytes::from_array(_buf),
      )
    }
  }
}