///|
/// Backend-neutral export manifest for reproducible media renders.
pub enum ExportFormat2 {
  ImageSequenceExport
  VideoExport
  DataExport
  AudioExport
} derive(Debug)

///|
pub fn export_formats2() -> Array[ExportFormat2] {
  [ImageSequenceExport, VideoExport, DataExport, AudioExport]
}

///|
pub struct ExportSettings2 {
  format : ExportFormat2
  width : Int
  height : Int
  frame_rate : Double
  quality : RenderQuality2
  premultiplied : Bool
} derive(Debug)

///|
pub fn export_settings2(
  format : ExportFormat2,
  width : Int,
  height : Int,
  frame_rate : Double,
) -> ExportSettings2 {
  {
    format,
    width: width.max(1),
    height: height.max(1),
    frame_rate: frame_rate.max(0.001),
    quality: FinalQuality,
    premultiplied: true,
  }
}

///|
pub fn ExportSettings2::format(self : ExportSettings2) -> ExportFormat2 {
  self.format
}

///|
pub fn ExportSettings2::width(self : ExportSettings2) -> Int {
  self.width
}

///|
pub fn ExportSettings2::height(self : ExportSettings2) -> Int {
  self.height
}

///|
pub fn ExportSettings2::frame_rate(self : ExportSettings2) -> Double {
  self.frame_rate
}

///|
pub fn ExportSettings2::quality(self : ExportSettings2) -> RenderQuality2 {
  self.quality
}

///|
pub fn ExportSettings2::with_quality(
  self : ExportSettings2,
  quality : RenderQuality2,
) -> ExportSettings2 {
  {
    format: self.format,
    width: self.width,
    height: self.height,
    frame_rate: self.frame_rate,
    quality,
    premultiplied: self.premultiplied,
  }
}

///|
pub fn ExportSettings2::with_premultiplied(
  self : ExportSettings2,
  value : Bool,
) -> ExportSettings2 {
  {
    format: self.format,
    width: self.width,
    height: self.height,
    frame_rate: self.frame_rate,
    quality: self.quality,
    premultiplied: value,
  }
}

///|
pub fn ExportSettings2::viewport(self : ExportSettings2) -> Viewport {
  viewport(self.width.to_double(), self.height.to_double())
}

///|
pub struct ExportManifestEntry2 {
  id : Int
  name : String
  start : Double
  end : Double
  checksum : Int
  frame_count : Int
} derive(Debug)

///|
pub fn export_manifest_entry2(
  id : Int,
  name : String,
  start : Double,
  end : Double,
  frame_count : Int,
) -> ExportManifestEntry2 {
  { id, name, start, end, checksum: 0, frame_count: frame_count.max(0) }
}

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

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

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

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

///|
pub fn ExportManifestEntry2::checksum(self : ExportManifestEntry2) -> Int {
  self.checksum
}

///|
pub fn ExportManifestEntry2::frame_count(self : ExportManifestEntry2) -> Int {
  self.frame_count
}

///|
pub fn ExportManifestEntry2::with_checksum(
  self : ExportManifestEntry2,
  checksum : Int,
) -> ExportManifestEntry2 {
  {
    id: self.id,
    name: self.name,
    start: self.start,
    end: self.end,
    checksum,
    frame_count: self.frame_count,
  }
}

///|
pub fn export_entry_checksum(values : Array[Double]) -> Int {
  let mut hash = 17
  for value in values {
    let bits = @math.floor(value * 1000000.0).to_int()
    hash = (hash * 31 + bits) % 2147483647
  }
  hash
}

///|
pub struct ExportManifest2 {
  settings : ExportSettings2
  entries : Array[ExportManifestEntry2]
  version : Int
} derive(Debug)

///|
pub fn export_manifest2(settings : ExportSettings2) -> ExportManifest2 {
  { settings, entries: [], version: 1 }
}

///|
pub fn ExportManifest2::settings(self : ExportManifest2) -> ExportSettings2 {
  self.settings
}

///|
pub fn ExportManifest2::entries(
  self : ExportManifest2,
) -> Array[ExportManifestEntry2] {
  self.entries.copy()
}

///|
pub fn ExportManifest2::version(self : ExportManifest2) -> Int {
  self.version
}

///|
pub fn ExportManifest2::add(
  self : ExportManifest2,
  entry : ExportManifestEntry2,
) -> Bool {
  for existing in self.entries {
    if existing.id == entry.id {
      return false
    }
  }
  self.entries.push(entry)
  true
}

///|
pub fn ExportManifest2::find(
  self : ExportManifest2,
  id : Int,
) -> ExportManifestEntry2? {
  for entry in self.entries {
    if entry.id == id {
      return Some(entry)
    }
  }
  None
}

///|
pub fn ExportManifest2::total_frames(self : ExportManifest2) -> Int {
  let mut result = 0
  for entry in self.entries {
    result = result + entry.frame_count
  }
  result
}

///|
pub fn ExportManifest2::time_range(self : ExportManifest2) -> (Double, Double) {
  if self.entries.length() == 0 {
    return (0.0, 0.0)
  }
  let mut start = self.entries[0].start
  let mut end = self.entries[0].end
  for entry in self.entries {
    start = start.min(entry.start)
    end = end.max(entry.end)
  }
  (start, end)
}

///|
pub fn ExportManifest2::validate(self : ExportManifest2) -> DiagnosticReport2 {
  let report = diagnostic_report2("export-manifest").with_measurements(
    self.total_frames(),
    self.time_range().1 - self.time_range().0,
  )
  if self.entries.length() == 0 {
    report.add(
      diagnostic_issue(
        WarningDiagnostic,
        EmptyInputDiagnostic,
        "manifest contains no entries",
        0,
        0.0,
        0.0,
      ),
    )
  }
  for index in 0..