///|
/// Layered raster compositing primitives for thumbnails and visual regression.
pub(all) struct RasterLayerPlus {
  name : String
  raster : Raster
  opacity : Double
  blend : TextureBlend
  offset : Vec2
  visible : Bool
} derive(Debug)

///|
pub(all) struct RasterCompositeReport {
  width : Int
  height : Int
  layer_count : Int
  visible_layers : Int
  source_pixels : Int
  output_pixels : Int
  checksum : UInt
  mean_alpha : Double
} derive(Eq, Debug, ToJson)

///|
pub fn RasterLayerPlus::new(name : String, raster : Raster) -> RasterLayerPlus {
  {
    name,
    raster,
    opacity: 1.0,
    blend: TextureNormal,
    offset: Vec2::zero(),
    visible: true,
  }
}

///|
pub fn RasterLayerPlus::opacity(
  self : RasterLayerPlus,
  value : Double,
) -> RasterLayerPlus {
  { ..self, opacity: value.max(0.0).min(1.0) }
}

///|
pub fn RasterLayerPlus::blend_mode(
  self : RasterLayerPlus,
  mode : TextureBlend,
) -> RasterLayerPlus {
  { ..self, blend: mode }
}

///|
pub fn RasterLayerPlus::translate(
  self : RasterLayerPlus,
  offset : Vec2,
) -> RasterLayerPlus {
  { ..self, offset, }
}

///|
pub fn RasterLayerPlus::hide(self : RasterLayerPlus) -> RasterLayerPlus {
  { ..self, visible: false }
}

///|
pub fn RasterLayerPlus::show(self : RasterLayerPlus) -> RasterLayerPlus {
  { ..self, visible: true }
}

///|
pub fn raster_plus_pixel(
  raster : Raster,
  x : Int,
  y : Int,
  fallback : Color,
) -> Color {
  raster.get(x, y).unwrap_or(fallback)
}

///|
pub fn raster_plus_stamp(base : Raster, layer : RasterLayerPlus) -> Raster {
  let result = base.copy()
  if layer.visible {
    for y in 0.. Raster {
  let result = Raster::new(width, height, fill=background)
  for layer in layers {
    let stamped = raster_plus_stamp(result, layer)
    for y in 0.. Raster {
  let width = bounds.width.max(0.0).to_int()
  let height = bounds.height.max(0.0).to_int()
  let result = Raster::new(width, height)
  for y in 0.. Raster {
  let width = raster.width + padding.horizontal().to_int().max(0)
  let height = raster.height + padding.vertical().to_int().max(0)
  let result = Raster::new(width, height, fill~)
  for y in 0.. Raster {
  let result = Raster::new(width, height)
  if raster.width > 0 && raster.height > 0 {
    for y in 0.. Raster {
  let result = Raster::new(width, height)
  if raster.width > 0 && raster.height > 0 {
    for y in 0.. Raster {
  let width = left.width.min(right.width)
  let height = left.height.min(right.height)
  let result = Raster::new(width, height)
  for y in 0.. Double {
  let width = left.width.min(right.width)
  let height = left.height.min(right.height)
  let mut total = 0.0
  let mut count = 0
  for y in 0.. UInt {
  let mut hash : UInt = 2166136261
  for color in raster.pixels {
    hash = (hash ^ (color.r * 255.0).to_int().reinterpret_as_uint()) * 16777619
    hash = (hash ^ (color.g * 255.0).to_int().reinterpret_as_uint()) * 16777619
    hash = (hash ^ (color.b * 255.0).to_int().reinterpret_as_uint()) * 16777619
  }
  hash
}

///|
pub fn raster_plus_report(
  raster : Raster,
  layers : Array[RasterLayerPlus],
) -> RasterCompositeReport {
  let mut visible = 0
  let mut source = 0
  let mut alpha = 0.0
  for layer in layers {
    source += layer.raster.pixels.length()
    if layer.visible {
      visible += 1
    }
    for color in layer.raster.pixels {
      alpha += color.a
    }
  }
  let pixel_count = raster.pixels.length()
  {
    width: raster.width,
    height: raster.height,
    layer_count: layers.length(),
    visible_layers: visible,
    source_pixels: source,
    output_pixels: pixel_count,
    checksum: raster_plus_checksum(raster),
    mean_alpha: if source == 0 {
      0.0
    } else {
      alpha / source.to_double()
    },
  }
}

///|
pub fn raster_plus_report_csv(report : RasterCompositeReport) -> String {
  "width,height,layer_count,visible_layers,source_pixels,output_pixels,checksum,mean_alpha\n" +
  report.width.to_string() +
  "," +
  report.height.to_string() +
  "," +
  report.layer_count.to_string() +
  "," +
  report.visible_layers.to_string() +
  "," +
  report.source_pixels.to_string() +
  "," +
  report.output_pixels.to_string() +
  "," +
  report.checksum.to_string() +
  "," +
  report.mean_alpha.to_string() +
  "\n"
}

///|
pub fn raster_plus_draw_path(
  raster : Raster,
  path : Path,
  color : Color,
) -> Raster {
  let result = raster.copy()
  let points = path.flatten(subdivisions=8)
  if points.length() > 1 {
    for index in 0..<(points.length() - 1) {
      let a = points[index]
      let b = points[index + 1]
      let distance = a.distance(b).max(1.0)
      let steps = distance.to_int().max(1)
      for step in 0..<=steps {
        let amount = step.to_double() / steps.to_double()
        let point = a.lerp(b, amount)
        let _ = result.blend_pixel(point.x.to_int(), point.y.to_int(), color)
      }
    }
  }
  result
}

///|
pub fn raster_plus_draw_motifs(
  raster : Raster,
  motifs : Array[Motif],
) -> Raster {
  let mut result = raster
  for motif in motifs {
    result = raster_plus_draw_path(
      result,
      motif.transformed_path(),
      Color::rgba(0.0, 0.0, 0.0, a=motif.style.opacity),
    )
  }
  result
}

///|
pub fn raster_plus_outline(
  raster : Raster,
  width : Int,
  color : Color,
) -> Raster {
  let result = raster.copy()
  let radius = width.max(1)
  for y in 0.. 0.0 {
        for oy in -radius..<=radius {
          for ox in -radius..<=radius {
            if ox * ox + oy * oy <= radius * radius &&
              result.in_bounds(x + ox, y + oy) {
              let _ = result.blend_pixel(x + ox, y + oy, color)
            }
          }
        }
      }
    }
  }
  result
}

///|
pub fn raster_plus_alpha_mask(raster : Raster, mask : Raster) -> Raster {
  let result = raster.copy()
  let width = raster.width.min(mask.width)
  let height = raster.height.min(mask.height)
  for y in 0.. (Raster, RasterCompositeReport) {
  let width = layers.fold(init=0, fn(best, layer) {
    best.max(layer.raster.width + layer.offset.x.to_int())
  })
  let height = layers.fold(init=0, fn(best, layer) {
    best.max(layer.raster.height + layer.offset.y.to_int())
  })
  let result = raster_plus_composite(width, height, background, layers)
  (result, raster_plus_report(result, layers))
}