///|
/// A named batch of motifs ready to add to a composition layer.
pub(all) struct PatternBatch {
  name : String
  motifs : Array[Motif]
  bounds : Rect
  seed : UInt
} derive(Debug)

///|
pub fn PatternBatch::new(
  name : String,
  bounds : Rect,
  seed : UInt,
) -> PatternBatch {
  { name, motifs: [], bounds, seed }
}

///|
pub fn PatternBatch::add(self : PatternBatch, motif : Motif) -> PatternBatch {
  self.motifs.push(motif)
  self
}

///|
pub fn PatternBatch::append_all(
  self : PatternBatch,
  motifs : Array[Motif],
) -> PatternBatch {
  for motif in motifs {
    self.motifs.push(motif)
  }
  self
}

///|
pub fn PatternBatch::count(self : PatternBatch) -> Int {
  self.motifs.length()
}

///|
pub fn PatternBatch::translate(
  self : PatternBatch,
  offset : Vec2,
) -> PatternBatch {
  let result = PatternBatch::new(self.name, self.bounds, self.seed)
  for motif in self.motifs {
    let _ = result.add(
      motif.placed(
        motif.anchor.x + offset.x,
        motif.anchor.y + offset.y,
        motif.scale,
        motif.rotation,
      ),
    )
  }
  result
}

///|
pub fn PatternBatch::to_layer(
  self : PatternBatch,
  opacity : Double,
) -> PatternLayer {
  let layer = PatternLayer::new(self.name, opacity~)
  for motif in self.motifs {
    layer.add(motif)
  }
  layer
}

///|
fn batch(
  name : String,
  bounds : Rect,
  seed : UInt,
  motifs : Array[Motif],
) -> PatternBatch {
  PatternBatch::new(name, bounds, seed).append_all(motifs)
}

///|
fn make_motif(
  name : String,
  path : Path,
  color : Color,
  width : Double,
  opacity : Double,
) -> Motif {
  Motif::new(
    name,
    path,
    SvgStyle::new(
      fill=color.to_hex(),
      stroke=color.to_hex(),
      stroke_width=width,
      opacity~,
    ),
  )
}

///|
/// Generates an evenly spaced dot grid with a scalar size field.
pub fn catalog_dot_field(
  bounds : Rect,
  columns : Int,
  rows : Int,
  minimum : Double,
  maximum : Double,
  palette : Palette,
  seed : UInt,
) -> PatternBatch {
  let motifs = []
  let random = Random::new(seed)
  if columns > 0 && rows > 0 {
    for row in 0.. PatternBatch {
  let bounds = Rect::new(
    center.x - outer_radius,
    center.y - outer_radius,
    outer_radius * 2.0,
    outer_radius * 2.0,
  )
  let motifs = []
  if rings > 0 && petals > 0 {
    for ring in 0.. PatternBatch {
  let motifs = []
  if count > 0 {
    for index in 0.. PatternBatch {
  let bounds = Rect::new(
    center.x - radius,
    center.y - radius,
    radius * 2.0,
    radius * 2.0,
  )
  let motifs = []
  let safe_spacing = spacing.max(0.1)
  if lines > 0 {
    for index in 0.. PatternBatch {
  let bounds = Rect::new(
    center.x - radius,
    center.y - radius,
    radius * 2.0,
    radius * 2.0,
  )
  let motifs = []
  if rays > 0 {
    for index in 0.. PatternBatch {
  let motifs = []
  if count > 0 {
    for index in 0.. 0.0 && frame.height > 0.0 {
        let color = palette
          .at((index + seed.reinterpret_as_int()) % palette.colors.length())
          .unwrap_or(Color::rgb8(0, 0, 0))
        motifs.push(
          make_motif(
            "catalog-frame",
            Path::rounded_rect(
              frame.width,
              frame.height,
              inset.max(0.0),
              x=frame.x,
              y=frame.y,
            ),
            color,
            1.0 + index.to_double() * 0.1,
            0.8,
          ),
        )
      }
    }
  }
  batch("nested-frames", bounds, seed, motifs)
}

///|
/// Generates diagonal hatch bands clipped conceptually to a rectangular canvas.
pub fn catalog_hatch_bundle(
  bounds : Rect,
  spacing : Double,
  angle : Double,
  count : Int,
  palette : Palette,
  seed : UInt,
) -> PatternBatch {
  let motifs = []
  let safe_spacing = spacing.max(0.1)
  if count > 0 {
    let diagonal = bounds.width + bounds.height
    for index in 0.. PatternBatch {
  let points = poisson_disc(
    bounds.width,
    bounds.height,
    point_radius.max(0.1) * 3.0,
    count,
    seed,
  )
  let motifs = []
  for index, local_point in points {
    let point = local_point.add(Vec2::new(bounds.x, bounds.y))
    let color = palette
      .at(index % palette.colors.length())
      .unwrap_or(Color::rgb8(255, 255, 255))
    motifs.push(
      make_motif(
        "catalog-star",
        Path::star(point.x, point.y, 5, point_radius, point_radius * 2.0),
        color,
        0.6,
        1.0,
      ),
    )
    for previous in 0.. PatternBatch {
  let motifs = []
  if columns > 0 && rows > 0 {
    for row in 0.. PatternBatch {
  let random = Random::new(seed)
  let motifs = []
  if count > 0 {
    for index in 0.. PatternBatch {
  let radius = minimum_radius + rings.max(0).to_double() * radius_step.max(0.0)
  let bounds = Rect::new(
    center.x - radius,
    center.y - radius,
    radius * 2.0,
    radius * 2.0,
  )
  let motifs = []
  if rings > 0 && satellites > 0 {
    for ring in 1..<=rings {
      let orbit = minimum_radius + (ring - 1).to_double() * radius_step
      motifs.push(
        make_motif(
          "catalog-orbit",
          Path::circle(orbit, cx=center.x, cy=center.y),
          palette
          .at(ring % palette.colors.length())
          .unwrap_or(Color::rgb8(0, 0, 0)),
          0.7,
          0.6,
        ),
      )
      for satellite in 0.. PatternBatch {
  let radius = end_radius.max(start_radius)
  let bounds = Rect::new(
    center.x - radius,
    center.y - radius,
    radius * 2.0,
    radius * 2.0,
  )
  let motifs = []
  if count > 0 {
    for index in 0.. PatternBatch {
  let random = Random::new(seed)
  let motifs = []
  if count > 0 {
    let step = bounds.width / count.to_double()
    for index in 0.. PatternBatch {
  let motifs = []
  let points = [
    Vec2::new(bounds.x + gap, bounds.y + gap),
    Vec2::new(bounds.right() - gap, bounds.y + gap),
    Vec2::new(bounds.right() - gap, bounds.bottom() - gap),
    Vec2::new(bounds.x + gap, bounds.bottom() - gap),
  ]
  for point in points {
    let horizontal = Path::new()
      .move_to(point.x - size, point.y)
      .line_to(point.x + size, point.y)
    let vertical = Path::new()
      .move_to(point.x, point.y - size)
      .line_to(point.x, point.y + size)
    motifs.push(make_motif("catalog-mark", horizontal, color, 0.7, 0.8))
    motifs.push(make_motif("catalog-mark", vertical, color, 0.7, 0.8))
  }
  batch("registration-marks", bounds, seed, motifs)
}

///|
/// Generates a repeated chevron border around a rectangular canvas.
pub fn catalog_chevron_border(
  bounds : Rect,
  spacing : Double,
  depth : Double,
  color : Color,
  seed : UInt,
) -> PatternBatch {
  let motifs = []
  let step = spacing.max(1.0)
  let count = ((bounds.width + bounds.height) * 2.0 / step).to_int()
  for index in 0.. PatternBatch {
  let motifs = []
  if columns > 0 && rows > 0 {
    for row in 0.. PatternBatch {
  let motifs = []
  if columns > 0 && rows > 0 {
    let width = bounds.width / columns.to_double()
    let height = bounds.height / rows.to_double()
    for row in 0.. PatternBatch {
  let motifs = []
  if columns > 0 && rows > 0 {
    for row in 0.. String {
  "name=\{batch.name},motifs=\{batch.motifs.length()},bounds=\{batch.bounds.width}x\{batch.bounds.height},seed=\{batch.seed}"
}

///|
/// Adds a batch to a composition with one stable layer name.
pub fn add_pattern_batch(
  composition : Composition,
  batch : PatternBatch,
  opacity : Double,
) -> Unit {
  composition.add_layer(batch.to_layer(opacity))
}