///|
/// A small composition graph for reusable animation scenes.
pub(all) struct AnimationNode {
  name : String
  animation : Animation
  opacity : Byte
  enabled : Bool
} derive(Debug)

///|
pub fn AnimationNode::new(
  name : String,
  animation : Animation,
) -> AnimationNode {
  { name, animation, opacity: 255, enabled: true }
}

///|
pub fn AnimationNode::name(self : AnimationNode) -> String {
  self.name
}

///|
pub fn AnimationNode::animation(self : AnimationNode) -> Animation {
  self.animation
}

///|
pub fn AnimationNode::opacity(
  self : AnimationNode,
  value : Byte,
) -> AnimationNode {
  { ..self, opacity: value }
}

///|
pub fn AnimationNode::enabled(
  self : AnimationNode,
  value : Bool,
) -> AnimationNode {
  { ..self, enabled: value }
}

///|
pub(all) struct AnimationGraph {
  width : Int
  height : Int
  nodes : Array[AnimationNode]
  background : Rgba
} derive(Debug)

///|
pub fn AnimationGraph::new(width : Int, height : Int) -> AnimationGraph {
  { width, height, nodes: [], background: rgba(0, 0, 0, 0) }
}

///|
pub fn AnimationGraph::background(
  self : AnimationGraph,
  color : Rgba,
) -> AnimationGraph {
  { ..self, background: color }
}

///|
pub fn AnimationGraph::add(
  self : AnimationGraph,
  node : AnimationNode,
) -> AnimationGraph {
  self.nodes.push(node)
  self
}

///|
pub fn AnimationGraph::nodes(self : AnimationGraph) -> ArrayView[AnimationNode] {
  self.nodes[:]
}

///|
pub fn AnimationGraph::frame_count(self : AnimationGraph) -> Int {
  let mut count = 0
  for node in self.nodes {
    if node.enabled && node.animation.frame_count() > count {
      count = node.animation.frame_count()
    }
  }
  count
}

///|
/// Render the graph by compositing each enabled node at a cyclic frame index.
pub fn AnimationGraph::render(self : AnimationGraph) -> Animation {
  let result = Animation::new(width=self.width, height=self.height).with_background(
    background=self.background,
  )
  let count = self.frame_count()
  for index in 0.. 0 {
        let frame = node.animation.frames[index % node.animation.frame_count()]
        let positioned = frame.canvas().scale_alpha(node.opacity)
        current = current.overlay(positioned, left=0, top=0)
        if frame.delay_cs() > delay {
          delay = frame.delay_cs()
        }
      }
    }
    ignore(result.add_frame(Frame::new(canvas=current, delay_cs=delay)))
  }
  result
}

///|
/// Replace one named node while preserving graph order.
pub fn AnimationGraph::replace(
  self : AnimationGraph,
  name : String,
  node : AnimationNode,
) -> AnimationGraph {
  let result : Array[AnimationNode] = []
  let mut replaced = false
  for existing in self.nodes {
    if existing.name() == name {
      result.push(node)
      replaced = true
    } else {
      result.push(existing)
    }
  }
  if !replaced {
    result.push(node)
  }
  { ..self, nodes: result }
}

///|
/// Remove disabled nodes and empty animations before rendering.
pub fn AnimationGraph::compact(self : AnimationGraph) -> AnimationGraph {
  let result : Array[AnimationNode] = []
  for node in self.nodes {
    if node.enabled && node.animation.frame_count() > 0 {
      result.push(node)
    }
  }
  { ..self, nodes: result }
}

///|
/// Return a stable list of node names for diagnostics.
pub fn AnimationGraph::node_names(self : AnimationGraph) -> Array[String] {
  let result : Array[String] = []
  for node in self.nodes {
    result.push(node.name())
  }
  result
}