///|
/// Individual image channels used by masks, color grading, and analysis.
pub(all) enum ColorChannel {
  RedChannel
  GreenChannel
  BlueChannel
  AlphaChannel
  LumaChannel
} derive(Eq, Debug)

///|
pub(all) struct ChannelStats {
  channel : ColorChannel
  minimum : Byte
  maximum : Byte
  mean : Int
  nonzero : Int
  histogram : Array[Int]
} derive(Debug)

///|
pub fn ChannelStats::channel(self : ChannelStats) -> ColorChannel {
  self.channel
}

///|
pub fn ChannelStats::minimum(self : ChannelStats) -> Byte {
  self.minimum
}

///|
pub fn ChannelStats::maximum(self : ChannelStats) -> Byte {
  self.maximum
}

///|
pub fn ChannelStats::mean(self : ChannelStats) -> Int {
  self.mean
}

///|
pub fn ChannelStats::nonzero(self : ChannelStats) -> Int {
  self.nonzero
}

///|
pub fn ChannelStats::histogram(self : ChannelStats) -> ArrayView[Int] {
  self.histogram[:]
}

///|
fn selected_channel_value(color : Rgba, channel : ColorChannel) -> Byte {
  match channel {
    RedChannel => color.red()
    GreenChannel => color.green()
    BlueChannel => color.blue()
    AlphaChannel => color.alpha()
    LumaChannel => color.luma()
  }
}

///|
fn channel_color(color : Rgba, channel : ColorChannel, value : Byte) -> Rgba {
  match channel {
    RedChannel => rgba(value, color.green(), color.blue(), color.alpha())
    GreenChannel => rgba(color.red(), value, color.blue(), color.alpha())
    BlueChannel => rgba(color.red(), color.green(), value, color.alpha())
    AlphaChannel => color.with_alpha(value)
    LumaChannel => rgba(value, value, value, color.alpha())
  }
}

///|
/// Extract a channel as a grayscale canvas.
pub fn Canvas::extract_channel(self : Canvas, channel : ColorChannel) -> Canvas {
  let result = Canvas::new(
    width=self.width,
    height=self.height,
    fill=rgba(0, 0, 0, 0),
  )
  for i in 0.. Canvas {
  let result = self.copy()
  let width = if result.width < source.width {
    result.width
  } else {
    source.width
  }
  let height = if result.height < source.height {
    result.height
  } else {
    source.height
  }
  for y in 0.. Canvas {
  let result = self.copy()
  for i in 0.. 255 { 255 } else { mapped }
    result.pixels[i] = channel_color(result.pixels[i], channel, safe.to_byte())
  }
  result
}

///|
/// Apply a 256-entry lookup table to one channel.
pub fn Canvas::lookup_channel(
  self : Canvas,
  channel : ColorChannel,
  table : ArrayView[Byte],
) -> Canvas {
  let result = self.copy()
  for i in 0..= table.length() {
      table[table.length() - 1]
    } else {
      table[old]
    }
    result.pixels[i] = channel_color(result.pixels[i], channel, value)
  }
  result
}

///|
fn smoothstep_value(value : Int, low : Int, high : Int) -> Byte {
  if high <= low {
    if value < low {
      0
    } else {
      255
    }
  } else if value <= low {
    0
  } else if value >= high {
    255
  } else {
    let t = (value - low) * 1000 / (high - low)
    let curve = t * t * (3000 - 2 * t) / 1000000
    (curve * 255 / 1000).to_byte()
  }
}

///|
/// Stretch a channel between black and white points.
pub fn Canvas::levels(
  self : Canvas,
  channel : ColorChannel,
  black : Int,
  white : Int,
) -> Canvas {
  let result = self.copy()
  for i in 0.. Canvas {
  let safe_gamma = if gamma_per_mille < 1 { 1 } else { gamma_per_mille }
  let table : Array[Byte] = []
  for value in 0..<=256 {
    let scaled = value * 1000 / 255
    let exponent = if safe_gamma > 2000 { 2000 } else { safe_gamma }
    let mapped = if scaled == 0 {
      0
    } else {
      let repeated = scaled * scaled / 1000
      if exponent < 1000 {
        (repeated + scaled) / 2
      } else {
        repeated
      }
    }
    let output = if mapped > 1000 { 255 } else { mapped * 255 / 1000 }
    table.push(output.to_byte())
  }
  self.lookup_channel(channel, table[:])
}

///|
/// Calculate histogram statistics for one color channel.
pub fn Canvas::channel_stats(
  self : Canvas,
  channel : ColorChannel,
) -> ChannelStats {
  let histogram = Array::make(256, 0)
  let mut minimum = 255
  let mut maximum = 0
  let mut total = 0
  let mut nonzero = 0
  for color in self.pixels {
    let value = selected_channel_value(color, channel).to_int()
    histogram[value] += 1
    if value < minimum {
      minimum = value
    }
    if value > maximum {
      maximum = value
    }
    total += value
    if value > 0 {
      nonzero += 1
    }
  }
  ChannelStats::{
    channel,
    minimum: (if self.pixels.length() == 0 { 0 } else { minimum }).to_byte(),
    maximum: maximum.to_byte(),
    mean: if self.pixels.length() == 0 {
      0
    } else {
      total / self.pixels.length()
    },
    nonzero,
    histogram,
  }
}

///|
/// Calculate all five standard channels in one pass.
pub fn Canvas::all_channel_stats(self : Canvas) -> Array[ChannelStats] {
  [RedChannel, GreenChannel, BlueChannel, AlphaChannel, LumaChannel].map(fn(
    channel,
  ) {
    self.channel_stats(channel)
  })
}

///|
/// Mix an RGB channel image with a tint while retaining the source alpha.
pub fn Canvas::colorize_channel(
  self : Canvas,
  channel : ColorChannel,
  tint : Rgba,
  strength : Int,
) -> Canvas {
  let safe_strength = if strength < 0 {
    0
  } else if strength > 1000 {
    1000
  } else {
    strength
  }
  let result = self.copy()
  for i in 0.. Canvas {
  let result = Canvas::new(
    width=self.width,
    height=self.height,
    fill=rgba(0, 0, 0, 0),
  )
  for i in 0..= low.to_int() &&
      value.to_int() <= high.to_int() {
      rgba(255, 255, 255, 255)
    } else {
      rgba(0, 0, 0, 0)
    }
  }
  result
}

///|
/// Combine two masks with a minimum operation.
pub fn mask_intersection(left : Canvas, right : Canvas) -> Canvas {
  let width = if left.width < right.width { left.width } else { right.width }
  let height = if left.height < right.height {
    left.height
  } else {
    right.height
  }
  let result = Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
  for y in 0.. Canvas {
  let width = if left.width < right.width { left.width } else { right.width }
  let height = if left.height < right.height {
    left.height
  } else {
    right.height
  }
  let result = Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
  for y in 0.. b {
          a
        } else {
          b
        },
      )
    }
  }
  result
}

///|
/// Subtract one alpha mask from another.
pub fn mask_subtract(left : Canvas, right : Canvas) -> Canvas {
  let width = if left.width < right.width { left.width } else { right.width }
  let height = if left.height < right.height {
    left.height
  } else {
    right.height
  }
  let result = Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
  for y in 0.. b {
          (a - b).to_byte()
        } else {
          0
        },
      )
    }
  }
  result
}