///|
/// A reusable motif with a local path, visual style and placement metadata.
pub(all) struct Motif {
  name : String
  path : Path
  style : SvgStyle
  anchor : Vec2
  scale : Double
  rotation : Double
} derive(Eq, Debug, ToJson)

///|
pub fn Motif::new(name : String, path : Path, style : SvgStyle) -> Motif {
  { name, path, style, anchor: Vec2::zero(), scale: 1.0, rotation: 0.0 }
}

///|
pub fn Motif::placed(
  self : Motif,
  x : Double,
  y : Double,
  scale : Double,
  rotation : Double,
) -> Motif {
  { ..self, anchor: Vec2::new(x, y), scale, rotation }
}

///|
pub fn Motif::transformed_path(self : Motif) -> Path {
  let transform = Matrix2D::translate(self.anchor.x, self.anchor.y)
    .multiply(Matrix2D::rotate(self.rotation))
    .multiply(Matrix2D::scale(self.scale, self.scale))
  self.path.transform(transform)
}

///|
/// A design layer allows motifs to be grouped and reordered without rebuilding SVG.
pub(all) struct PatternLayer {
  name : String
  opacity : Double
  mut visible : Bool
  motifs : Array[Motif]
} derive(Eq, Debug, ToJson)

///|
pub fn PatternLayer::new(
  name : String,
  opacity? : Double = 1.0,
) -> PatternLayer {
  { name, opacity, visible: true, motifs: [] }
}

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

///|
pub fn PatternLayer::hide(self : PatternLayer) -> Unit {
  self.visible = false
}

///|
pub fn PatternLayer::show(self : PatternLayer) -> Unit {
  self.visible = true
}

///|
/// A complete procedural composition with named layers and export metadata.
pub(all) struct Composition {
  width : Double
  height : Double
  background : Color
  layers : Array[PatternLayer]
} derive(Eq, Debug, ToJson)

///|
pub fn Composition::new(
  width : Double,
  height : Double,
  background? : Color = Color::rgb8(255, 255, 255),
) -> Composition {
  { width, height, background, layers: [] }
}

///|
pub fn Composition::add_layer(self : Composition, layer : PatternLayer) -> Unit {
  self.layers.push(layer)
}

///|
pub fn Composition::render(self : Composition) -> SvgDocument {
  let document = SvgDocument::new(self.width, self.height)
  let background = Path::rect(self.width, self.height)
  document.add(
    SvgElement::SvgPath(
      background,
      SvgStyle::new(fill=self.background.to_hex(), stroke="none"),
    ),
  )
  for layer in self.layers {
    if layer.visible {
      let children = []
      for motif in layer.motifs {
        let style = {
          ..motif.style,
          opacity: motif.style.opacity * layer.opacity,
        }
        children.push(SvgElement::SvgPath(motif.transformed_path(), style))
      }
      document.add(
        SvgElement::SvgGroup(children, transform=Matrix2D::identity()),
      )
    }
  }
  document
}

///|
/// Places a motif at every generated grid cell, applying cell transforms.
pub fn stamp_on_grid(motif : Motif, grid : Grid) -> Array[Motif] {
  let result = []
  for cell in grid.generate_cells() {
    let (x, y) = cell.transform.transform_point(motif.anchor.x, motif.anchor.y)
    result.push(motif.placed(x, y, motif.scale, motif.rotation))
  }
  result
}

///|
/// Applies a finite symmetry set to a motif around its local anchor.
pub fn stamp_with_symmetry(
  motif : Motif,
  transforms : Array[Matrix2D],
) -> Array[Motif] {
  let result = []
  for transform in transforms {
    let (x, y) = transform.transform_point(motif.anchor.x, motif.anchor.y)
    result.push(motif.placed(x, y, motif.scale, motif.rotation))
  }
  result
}