///|
/// Render-layer metadata that connects motion sampling to an application
/// renderer without depending on a graphics backend.
pub enum RenderLayerKind2 {
  BackgroundLayer
  GeometryLayer
  SpriteLayer
  TextLayer
  ParticleLayer
  EffectLayer
} derive(Debug)

///|
pub fn render_layer_kinds2() -> Array[RenderLayerKind2] {
  [
    BackgroundLayer,
    GeometryLayer,
    SpriteLayer,
    TextLayer,
    ParticleLayer,
    EffectLayer,
  ]
}

///|
pub struct RenderLayer2 {
  id : Int
  name : String
  kind : RenderLayerKind2
  order : Int
  visible : Bool
  opacity : Double
  blend : BlendMode
  bounds : Bounds2D
} derive(Debug)

///|
pub fn render_layer2(
  id : Int,
  name : String,
  kind : RenderLayerKind2,
  order : Int,
  bounds : Bounds2D,
) -> RenderLayer2 {
  { id, name, kind, order, visible: true, opacity: 1.0, blend: Normal, bounds }
}

///|
pub fn RenderLayer2::id(self : RenderLayer2) -> Int {
  self.id
}

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

///|
pub fn RenderLayer2::kind(self : RenderLayer2) -> RenderLayerKind2 {
  self.kind
}

///|
pub fn RenderLayer2::order(self : RenderLayer2) -> Int {
  self.order
}

///|
pub fn RenderLayer2::visible(self : RenderLayer2) -> Bool {
  self.visible
}

///|
pub fn RenderLayer2::opacity(self : RenderLayer2) -> Double {
  self.opacity
}

///|
pub fn RenderLayer2::blend(self : RenderLayer2) -> BlendMode {
  self.blend
}

///|
pub fn RenderLayer2::bounds(self : RenderLayer2) -> Bounds2D {
  self.bounds
}

///|
pub fn RenderLayer2::with_visibility(
  self : RenderLayer2,
  visible : Bool,
) -> RenderLayer2 {
  {
    id: self.id,
    name: self.name,
    kind: self.kind,
    order: self.order,
    visible,
    opacity: self.opacity,
    blend: self.blend,
    bounds: self.bounds,
  }
}

///|
pub fn RenderLayer2::with_opacity(
  self : RenderLayer2,
  opacity : Double,
) -> RenderLayer2 {
  {
    id: self.id,
    name: self.name,
    kind: self.kind,
    order: self.order,
    visible: self.visible,
    opacity: clamp01(opacity),
    blend: self.blend,
    bounds: self.bounds,
  }
}

///|
pub fn RenderLayer2::with_blend(
  self : RenderLayer2,
  blend : BlendMode,
) -> RenderLayer2 {
  {
    id: self.id,
    name: self.name,
    kind: self.kind,
    order: self.order,
    visible: self.visible,
    opacity: self.opacity,
    blend,
    bounds: self.bounds,
  }
}

///|
pub struct RenderPass2 {
  layer_id : Int
  active : Bool
  opacity : Double
  clip_start : Double
  clip_end : Double
  world_bounds : Bounds2D
} derive(Debug)

///|
pub fn RenderPass2::layer_id(self : RenderPass2) -> Int {
  self.layer_id
}

///|
pub fn RenderPass2::active(self : RenderPass2) -> Bool {
  self.active
}

///|
pub fn RenderPass2::opacity(self : RenderPass2) -> Double {
  self.opacity
}

///|
pub fn RenderPass2::clip_start(self : RenderPass2) -> Double {
  self.clip_start
}

///|
pub fn RenderPass2::clip_end(self : RenderPass2) -> Double {
  self.clip_end
}

///|
pub fn RenderPass2::world_bounds(self : RenderPass2) -> Bounds2D {
  self.world_bounds
}

///|
pub struct RenderPlan2 {
  layers : Array[RenderLayer2]
  start : Double
  end : Double
  clear_color : Rgba
} derive(Debug)

///|
pub fn render_plan2(
  start : Double,
  end : Double,
  clear_color : Rgba,
) -> RenderPlan2 {
  { layers: [], start, end: end.max(start), clear_color }
}

///|
pub fn RenderPlan2::layers(self : RenderPlan2) -> Array[RenderLayer2] {
  self.layers.copy()
}

///|
pub fn RenderPlan2::start(self : RenderPlan2) -> Double {
  self.start
}

///|
pub fn RenderPlan2::end(self : RenderPlan2) -> Double {
  self.end
}

///|
pub fn RenderPlan2::clear_color(self : RenderPlan2) -> Rgba {
  self.clear_color
}

///|
pub fn RenderPlan2::add_layer(self : RenderPlan2, layer : RenderLayer2) -> Bool {
  for existing in self.layers {
    if existing.id == layer.id {
      return false
    }
  }
  self.layers.push(layer)
  self.layers.sort_by(fn(left, right) { left.order.compare(right.order) })
  true
}

///|
pub fn RenderPlan2::remove_layer(self : RenderPlan2, id : Int) -> Bool {
  for index in 0.. RenderLayer2? {
  for layer in self.layers {
    if layer.id == id {
      return Some(layer)
    }
  }
  None
}

///|
pub fn RenderPlan2::active_layers(
  self : RenderPlan2,
  time : Double,
) -> Array[RenderLayer2] {
  let result : Array[RenderLayer2] = []
  if time < self.start || time > self.end {
    return result
  }
  for layer in self.layers {
    if layer.visible && layer.opacity > 0.0 {
      result.push(layer)
    }
  }
  result
}

///|
pub fn RenderPlan2::passes(
  self : RenderPlan2,
  time : Double,
) -> Array[RenderPass2] {
  let result : Array[RenderPass2] = []
  for layer in self.layers {
    let active = time >= self.start &&
      time <= self.end &&
      layer.visible &&
      layer.opacity > 0.0
    result.push({
      layer_id: layer.id,
      active,
      opacity: if active {
        layer.opacity
      } else {
        0.0
      },
      clip_start: self.start,
      clip_end: self.end,
      world_bounds: layer.bounds,
    })
  }
  result
}

///|
pub fn RenderPlan2::bounds(self : RenderPlan2) -> Bounds2D {
  if self.layers.length() == 0 {
    return { minimum: point2(x=0.0, y=0.0), maximum: point2(x=0.0, y=0.0) }
  }
  let mut result = self.layers[0].bounds
  for index in 1.. RenderPlan2 {
  {
    layers: self.layers.copy(),
    start: self.start + delta,
    end: self.end + delta,
    clear_color: self.clear_color,
  }
}

///|
pub struct RenderFrame2 {
  time : Double
  passes : Array[RenderPass2]
  clear_color : Rgba
  visible_count : Int
} derive(Debug)

///|
pub fn RenderFrame2::time(self : RenderFrame2) -> Double {
  self.time
}

///|
pub fn RenderFrame2::passes(self : RenderFrame2) -> Array[RenderPass2] {
  self.passes.copy()
}

///|
pub fn RenderFrame2::clear_color(self : RenderFrame2) -> Rgba {
  self.clear_color
}

///|
pub fn RenderFrame2::visible_count(self : RenderFrame2) -> Int {
  self.visible_count
}

///|
pub fn RenderPlan2::frame(self : RenderPlan2, time : Double) -> RenderFrame2 {
  let passes = self.passes(time)
  let mut visible_count = 0
  for pass in passes {
    if pass.active {
      visible_count = visible_count + 1
    }
  }
  { time, passes, clear_color: self.clear_color, visible_count }
}

///|
pub struct RenderPlanStats2 {
  layer_count : Int
  visible_count : Int
  total_area : Double
  start : Double
  end : Double
} derive(Debug)

///|
pub fn RenderPlanStats2::layer_count(self : RenderPlanStats2) -> Int {
  self.layer_count
}

///|
pub fn RenderPlanStats2::visible_count(self : RenderPlanStats2) -> Int {
  self.visible_count
}

///|
pub fn RenderPlanStats2::total_area(self : RenderPlanStats2) -> Double {
  self.total_area
}

///|
pub fn RenderPlanStats2::start(self : RenderPlanStats2) -> Double {
  self.start
}

///|
pub fn RenderPlanStats2::end(self : RenderPlanStats2) -> Double {
  self.end
}

///|
pub fn RenderPlan2::stats(self : RenderPlan2) -> RenderPlanStats2 {
  let mut area = 0.0
  let mut visible = 0
  for layer in self.layers {
    let width = layer.bounds.maximum().x() - layer.bounds.minimum().x()
    let height = layer.bounds.maximum().y() - layer.bounds.minimum().y()
    area = area + width * height
    if layer.visible && layer.opacity > 0.0 {
      visible = visible + 1
    }
  }
  {
    layer_count: self.layers.length(),
    visible_count: visible,
    total_area: area,
    start: self.start,
    end: self.end,
  }
}

///|
pub enum RenderQuality2 {
  PreviewQuality
  DraftQuality
  FinalQuality
} derive(Debug)

///|
pub fn render_qualities2() -> Array[RenderQuality2] {
  [PreviewQuality, DraftQuality, FinalQuality]
}

///|
pub fn quality_sample_count2(quality : RenderQuality2) -> Int {
  match quality {
    PreviewQuality => 16
    DraftQuality => 64
    FinalQuality => 256
  }
}