///|
pub(all) enum SceneAssetKind {
  Image
  Sprite
  Tile
  RemoteImage
} derive(Debug, Eq)

///|
pub struct SceneAsset {
  id : String
  kind : SceneAssetKind
  src : String
  estimated_bytes : Int
  remote : Bool
} derive(Debug, Eq)

///|
pub struct SceneAssetBudget {
  max_assets : Int
  max_package_bytes : Int
  max_remote_assets : Int
} derive(Debug, Eq)

///|
pub struct SceneAssetPolicy {
  approved_remote_domains : Array[String]
  require_https : Bool
} derive(Debug, Eq)

///|
pub struct SceneAssetPlan {
  assets : Array[SceneAsset]
  package_bytes : Int
  remote_assets : Int
  insecure_remote_asset_count : Int
  unapproved_remote_asset_count : Int
  unapproved_remote_asset_ids : Array[String]
  deferred_asset_count : Int
  deferred_asset_ids : Array[String]
  diagnostics : Array[String]
  summary : String
} derive(Debug, Eq)

///|
pub fn scene_asset(
  id~ : String,
  kind~ : SceneAssetKind,
  src~ : String,
  estimated_bytes? : Int = 0,
  remote? : Bool = false,
) -> SceneAsset {
  { id, kind, src, estimated_bytes, remote }
}

///|
pub fn image_asset(
  id : String,
  src : String,
  estimated_bytes : Int,
) -> SceneAsset {
  scene_asset(id~, kind=Image, src~, estimated_bytes~)
}

///|
pub fn sprite_asset(
  id : String,
  src : String,
  estimated_bytes : Int,
) -> SceneAsset {
  scene_asset(id~, kind=Sprite, src~, estimated_bytes~)
}

///|
pub fn tile_asset(
  id : String,
  src : String,
  estimated_bytes : Int,
) -> SceneAsset {
  scene_asset(id~, kind=Tile, src~, estimated_bytes~)
}

///|
pub fn remote_image_asset(id : String, src : String) -> SceneAsset {
  scene_asset(id~, kind=RemoteImage, src~, remote=true)
}

///|
pub fn scene_asset_budget(
  max_assets~ : Int,
  max_package_bytes~ : Int,
  max_remote_assets~ : Int,
) -> SceneAssetBudget {
  { max_assets, max_package_bytes, max_remote_assets }
}

///|
pub fn default_scene_asset_budget() -> SceneAssetBudget {
  { max_assets: 64, max_package_bytes: 1024 * 1024, max_remote_assets: 16 }
}

///|
pub fn scene_asset_policy(
  approved_remote_domains~ : Array[String],
  require_https? : Bool = true,
) -> SceneAssetPolicy {
  { approved_remote_domains, require_https }
}

///|
pub fn default_scene_asset_policy() -> SceneAssetPolicy {
  { approved_remote_domains: [], require_https: true }
}

///|
pub fn plan_scene_assets(assets : Array[SceneAsset]) -> SceneAssetPlan {
  plan_scene_assets_with_budget(assets, default_scene_asset_budget())
}

///|
pub fn plan_scene_assets_with_budget(
  assets : Array[SceneAsset],
  budget : SceneAssetBudget,
) -> SceneAssetPlan {
  plan_scene_assets_with_budget_and_policy(
    assets,
    budget,
    default_scene_asset_policy(),
  )
}

///|
pub fn plan_scene_assets_with_budget_and_policy(
  assets : Array[SceneAsset],
  budget : SceneAssetBudget,
  policy : SceneAssetPolicy,
) -> SceneAssetPlan {
  let diagnostics : Array[String] = []
  let deferred_asset_ids : Array[String] = []
  let unapproved_remote_asset_ids : Array[String] = []
  let mut package_bytes = 0
  let mut remote_assets = 0
  let mut insecure_remote_asset_count = 0
  let mut planned_package_bytes = 0
  for item in assets {
    if item.remote {
      remote_assets += 1
      if policy.require_https && !item.src.has_prefix("https://") {
        insecure_remote_asset_count += 1
        diagnostics.push("scene-remote-asset-not-https:\{item.id}")
      }
      let domain = remote_asset_domain(item.src)
      if !remote_domain_approved(domain, policy.approved_remote_domains) {
        unapproved_remote_asset_ids.push(item.id)
        diagnostics.push(
          "scene-remote-asset-domain-unapproved:\{item.id}:\{domain}",
        )
      }
    } else {
      package_bytes += item.estimated_bytes
      if planned_package_bytes + item.estimated_bytes <=
        budget.max_package_bytes {
        planned_package_bytes += item.estimated_bytes
      } else {
        deferred_asset_ids.push(item.id)
      }
    }
  }
  if assets.length() > budget.max_assets {
    diagnostics.push(
      "scene-asset-count-over-budget:\{assets.length()}/\{budget.max_assets}",
    )
  }
  if package_bytes > budget.max_package_bytes {
    diagnostics.push(
      "scene-package-bytes-over-budget:\{package_bytes}/\{budget.max_package_bytes}",
    )
  }
  if remote_assets > budget.max_remote_assets {
    diagnostics.push(
      "scene-remote-assets-over-budget:\{remote_assets}/\{budget.max_remote_assets}",
    )
  }
  if insecure_remote_asset_count > 0 {
    diagnostics.push(
      "scene-insecure-remote-assets:\{insecure_remote_asset_count}",
    )
  }
  if unapproved_remote_asset_ids.length() > 0 {
    diagnostics.push(
      "scene-unapproved-remote-assets:\{unapproved_remote_asset_ids.length()}",
    )
  }
  if deferred_asset_ids.length() > 0 {
    diagnostics.push("scene-deferred-assets:\{deferred_asset_ids.length()}")
  }
  {
    assets,
    package_bytes,
    remote_assets,
    insecure_remote_asset_count,
    unapproved_remote_asset_count: unapproved_remote_asset_ids.length(),
    unapproved_remote_asset_ids,
    deferred_asset_count: deferred_asset_ids.length(),
    deferred_asset_ids,
    diagnostics,
    summary: "Bunnia scene asset plan: assets=\{assets.length()} package_bytes=\{package_bytes} remote=\{remote_assets} insecure_remote=\{insecure_remote_asset_count} unapproved_remote=\{unapproved_remote_asset_ids.length()} deferred=\{deferred_asset_ids.length()} diagnostics=\{diagnostics.length()}",
  }
}

///|
fn remote_domain_approved(
  domain : String,
  approved_domains : Array[String],
) -> Bool {
  if approved_domains.length() == 0 {
    true
  } else {
    approved_domains.contains(domain)
  }
}

///|
fn remote_asset_domain(src : String) -> String {
  let start = if src.has_prefix("https://") {
    8
  } else if src.has_prefix("http://") {
    7
  } else {
    0
  }
  let mut end = src.length()
  for i in start.. String {
  match kind {
    Image => "image"
    Sprite => "sprite"
    Tile => "tile"
    RemoteImage => "remote-image"
  }
}