///|
/// Batch operations for applying a common export policy to many animations.
pub(all) struct BatchItem {
  name : String
  animation : Animation
} derive(Debug)

///|
pub fn BatchItem::new(name : String, animation : Animation) -> BatchItem {
  { name, animation }
}

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

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

///|
pub(all) struct BatchReport {
  processed : Int
  encoded : Int
  failed : Int
  total_input_bytes : Int
  total_output_bytes : Int
  names : Array[String]
} derive(Debug)

///|
pub fn BatchReport::processed(self : BatchReport) -> Int {
  self.processed
}

///|
pub fn BatchReport::encoded(self : BatchReport) -> Int {
  self.encoded
}

///|
pub fn BatchReport::failed(self : BatchReport) -> Int {
  self.failed
}

///|
pub fn BatchReport::total_input_bytes(self : BatchReport) -> Int {
  self.total_input_bytes
}

///|
pub fn BatchReport::total_output_bytes(self : BatchReport) -> Int {
  self.total_output_bytes
}

///|
pub fn BatchReport::names(self : BatchReport) -> ArrayView[String] {
  self.names[:]
}

///|
/// Encode a batch, retaining successful output and continuing after failures.
pub fn encode_batch(
  items : ArrayView[BatchItem],
  profile : ExportProfile,
) -> (Array[(String, Bytes)], BatchReport) {
  let outputs : Array[(String, Bytes)] = []
  let names : Array[String] = []
  let mut encoded = 0
  let mut failed = 0
  let mut input_bytes = 0
  let mut output_bytes = 0
  for item in items {
    names.push(item.name())
    let result : Bytes? = try {
      let plan = item.animation().plan_export(profile)
      let bytes = item.animation().export_with_plan(plan)
      Some(bytes)
    } catch {
      _ => None
    } noraise {
      _ => None
    }
    match result {
      None => failed += 1
      Some(bytes) => {
        encoded += 1
        output_bytes += bytes.length()
        input_bytes += item.animation().width *
          item.animation().height *
          item.animation().frame_count()
        outputs.push((item.name(), bytes))
      }
    }
  }
  (
    outputs,
    BatchReport::{
      processed: items.length(),
      encoded,
      failed,
      total_input_bytes: input_bytes,
      total_output_bytes: output_bytes,
      names,
    },
  )
}

///|
/// Apply a pipeline to all batch items while preserving their names.
pub fn transform_batch(
  items : ArrayView[BatchItem],
  pipeline : CanvasPipeline,
) -> Array[BatchItem] {
  let result : Array[BatchItem] = []
  for item in items {
    result.push(
      BatchItem::new(item.name(), pipeline.apply_animation(item.animation())),
    )
  }
  result
}

///|
/// Return output compression in per mille, based on measured byte totals.
pub fn BatchReport::saving_per_mille(self : BatchReport) -> Int {
  if self.total_input_bytes <= 0 {
    0
  } else {
    (self.total_input_bytes - self.total_output_bytes) *
    1000 /
    self.total_input_bytes
  }
}