///|
/// Lightweight asset descriptors for animation tools. The catalog deliberately
/// stores no file handles; it can be serialized by a host application and
/// reused for preview or final export.
pub enum MediaKind2 {
  VectorAsset
  RasterAsset
  AudioAsset
  CameraAsset
  CompositeAsset
} derive(Debug)

///|
pub fn media_kinds2() -> Array[MediaKind2] {
  [VectorAsset, RasterAsset, AudioAsset, CameraAsset, CompositeAsset]
}

///|
pub struct MediaAsset2 {
  id : Int
  name : String
  kind : MediaKind2
  start : Double
  duration : Double
  bounds : Bounds2D
  tags : Array[String]
  enabled : Bool
} derive(Debug)

///|
pub fn media_asset2(
  id : Int,
  name : String,
  kind : MediaKind2,
  start : Double,
  duration : Double,
  bounds : Bounds2D,
) -> MediaAsset2 {
  {
    id,
    name,
    kind,
    start,
    duration: duration.max(0.0),
    bounds,
    tags: [],
    enabled: true,
  }
}

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

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

///|
pub fn MediaAsset2::kind(self : MediaAsset2) -> MediaKind2 {
  self.kind
}

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

///|
pub fn MediaAsset2::duration(self : MediaAsset2) -> Double {
  self.duration
}

///|
pub fn MediaAsset2::end(self : MediaAsset2) -> Double {
  self.start + self.duration
}

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

///|
pub fn MediaAsset2::tags(self : MediaAsset2) -> Array[String] {
  self.tags.copy()
}

///|
pub fn MediaAsset2::enabled(self : MediaAsset2) -> Bool {
  self.enabled
}

///|
pub fn MediaAsset2::with_tag(self : MediaAsset2, tag : String) -> MediaAsset2 {
  let tags = self.tags.copy()
  if !tags.contains(tag) {
    tags.push(tag)
  }
  {
    id: self.id,
    name: self.name,
    kind: self.kind,
    start: self.start,
    duration: self.duration,
    bounds: self.bounds,
    tags,
    enabled: self.enabled,
  }
}

///|
pub fn MediaAsset2::with_enabled(
  self : MediaAsset2,
  enabled : Bool,
) -> MediaAsset2 {
  {
    id: self.id,
    name: self.name,
    kind: self.kind,
    start: self.start,
    duration: self.duration,
    bounds: self.bounds,
    tags: self.tags,
    enabled,
  }
}

///|
pub fn MediaAsset2::active_at(self : MediaAsset2, time : Double) -> Bool {
  self.enabled && time >= self.start && time <= self.end()
}

///|
pub struct MediaCatalog2 {
  assets : Array[MediaAsset2]
  mut revision : Int
} derive(Debug)

///|
pub fn media_catalog2() -> MediaCatalog2 {
  { assets: [], revision: 0 }
}

///|
pub fn MediaCatalog2::assets(self : MediaCatalog2) -> Array[MediaAsset2] {
  self.assets.copy()
}

///|
pub fn MediaCatalog2::revision(self : MediaCatalog2) -> Int {
  self.revision
}

///|
pub fn MediaCatalog2::length(self : MediaCatalog2) -> Int {
  self.assets.length()
}

///|
pub fn MediaCatalog2::add(self : MediaCatalog2, asset : MediaAsset2) -> Bool {
  for existing in self.assets {
    if existing.id == asset.id {
      return false
    }
  }
  self.assets.push(asset)
  self.revision = self.revision + 1
  true
}

///|
pub fn MediaCatalog2::replace(
  self : MediaCatalog2,
  asset : MediaAsset2,
) -> Bool {
  for index in 0.. Bool {
  for index in 0.. MediaAsset2? {
  for asset in self.assets {
    if asset.id == id {
      return Some(asset)
    }
  }
  None
}

///|
pub fn MediaCatalog2::find_name(
  self : MediaCatalog2,
  name : String,
) -> Array[MediaAsset2] {
  let result : Array[MediaAsset2] = []
  for asset in self.assets {
    if asset.name == name {
      result.push(asset)
    }
  }
  result
}

///|
pub fn MediaCatalog2::find_tag(
  self : MediaCatalog2,
  tag : String,
) -> Array[MediaAsset2] {
  let result : Array[MediaAsset2] = []
  for asset in self.assets {
    if asset.tags.contains(tag) {
      result.push(asset)
    }
  }
  result
}

///|
pub fn MediaCatalog2::active_at(
  self : MediaCatalog2,
  time : Double,
) -> Array[MediaAsset2] {
  let result : Array[MediaAsset2] = []
  for asset in self.assets {
    if asset.active_at(time) {
      result.push(asset)
    }
  }
  result.sort_by(fn(left, right) { left.start.compare(right.start) })
  result
}

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

///|
pub fn MediaCatalog2::bounds(self : MediaCatalog2) -> Bounds2D {
  if self.assets.length() == 0 {
    return { minimum: point2(x=0.0, y=0.0), maximum: point2(x=0.0, y=0.0) }
  }
  let mut result = self.assets[0].bounds
  for index in 1.. Array[MediaAsset2] {
  let result : Array[MediaAsset2] = []
  for asset in self.assets {
    let same = match (asset.kind, kind) {
      (VectorAsset, VectorAsset)
      | (RasterAsset, RasterAsset)
      | (AudioAsset, AudioAsset)
      | (CameraAsset, CameraAsset)
      | (CompositeAsset, CompositeAsset) => true
      (_, _) => false
    }
    if same {
      result.push(asset)
    }
  }
  result
}

///|
pub fn MediaCatalog2::shift(
  self : MediaCatalog2,
  delta : Double,
) -> MediaCatalog2 {
  let result = media_catalog2()
  for asset in self.assets {
    ignore(
      result.add({
        id: asset.id,
        name: asset.name,
        kind: asset.kind,
        start: asset.start + delta,
        duration: asset.duration,
        bounds: asset.bounds,
        tags: asset.tags,
        enabled: asset.enabled,
      }),
    )
  }
  result
}