///|
pub(all) enum SceneRenderMode {
  StaticMarkup
  CanvasSurface
  LightweightMarkup
} derive(Debug, Eq)

///|
pub(all) enum SceneQualityLevel {
  FullQuality
  BalancedQuality
  LightweightQuality
} derive(Debug, Eq)

///|
pub struct SceneRenderBudget {
  max_layers : Int
  max_markers : Int
  max_regions : Int
  max_static_assets : Int
} derive(Debug, Eq)

///|
pub struct SceneRenderPlan {
  mode : SceneRenderMode
  quality : SceneQualityLevel
  layer_count : Int
  marker_count : Int
  visible_marker_count : Int
  region_count : Int
  visible_region_count : Int
  asset_count : Int
  diagnostics : Array[String]
  summary : String
} derive(Debug, Eq)

///|
pub fn scene_render_budget(
  max_layers~ : Int,
  max_markers~ : Int,
  max_static_assets~ : Int,
  max_regions? : Int = 128,
) -> SceneRenderBudget {
  { max_layers, max_markers, max_regions, max_static_assets }
}

///|
pub fn default_scene_render_budget() -> SceneRenderBudget {
  { max_layers: 8, max_markers: 256, max_regions: 128, max_static_assets: 32 }
}

///|
pub fn plan_scene_render(input : Scene) -> SceneRenderPlan {
  plan_scene_render_with_budget(input, default_scene_render_budget())
}

///|
pub fn plan_scene_render_with_budget(
  input : Scene,
  budget : SceneRenderBudget,
) -> SceneRenderPlan {
  let marker_count = scene_marker_count(input)
  let region_count = scene_region_count(input)
  plan_scene_render_counts(
    marker_count,
    marker_count,
    region_count,
    region_count,
    input.layers.length(),
    input.assets.length(),
    budget,
    "scene-marker-count-over-budget",
    "scene-region-count-over-budget",
  )
}

///|
pub fn plan_scene_render_viewport(
  input : Scene,
  viewport : SceneViewport,
  budget : SceneRenderBudget,
) -> SceneRenderPlan {
  let marker_count = scene_marker_count(input)
  let visible_marker_count = scene_visible_marker_count(input, viewport)
  let region_count = scene_region_count(input)
  let visible_region_count = scene_visible_region_count(input, viewport)
  plan_scene_render_counts(
    marker_count,
    visible_marker_count,
    region_count,
    visible_region_count,
    input.layers.length(),
    input.assets.length(),
    budget,
    "scene-visible-marker-count-over-budget",
    "scene-visible-region-count-over-budget",
  )
}

///|
fn plan_scene_render_counts(
  marker_count : Int,
  visible_marker_count : Int,
  region_count : Int,
  visible_region_count : Int,
  layer_count : Int,
  asset_count : Int,
  budget : SceneRenderBudget,
  marker_diagnostic : String,
  region_diagnostic : String,
) -> SceneRenderPlan {
  let diagnostics : Array[String] = []
  if layer_count > budget.max_layers {
    diagnostics.push(
      "scene-layer-count-over-budget:\{layer_count}/\{budget.max_layers}",
    )
  }
  if visible_marker_count > budget.max_markers {
    diagnostics.push(
      "\{marker_diagnostic}:\{visible_marker_count}/\{budget.max_markers}",
    )
  }
  if visible_region_count > budget.max_regions {
    diagnostics.push(
      "\{region_diagnostic}:\{visible_region_count}/\{budget.max_regions}",
    )
  }
  if asset_count > budget.max_static_assets {
    diagnostics.push(
      "scene-static-assets-over-budget:\{asset_count}/\{budget.max_static_assets}",
    )
  }
  let quality = if visible_marker_count > budget.max_markers ||
    visible_region_count > budget.max_regions {
    LightweightQuality
  } else if layer_count > budget.max_layers ||
    asset_count > budget.max_static_assets {
    BalancedQuality
  } else {
    FullQuality
  }
  let mode = match quality {
    FullQuality => StaticMarkup
    BalancedQuality => StaticMarkup
    LightweightQuality => LightweightMarkup
  }
  {
    mode,
    quality,
    layer_count,
    marker_count,
    visible_marker_count,
    region_count,
    visible_region_count,
    asset_count,
    diagnostics,
    summary: "Bunnia scene render plan: mode=\{scene_render_mode_id(mode)} quality=\{scene_quality_id(quality)} layers=\{layer_count} markers=\{visible_marker_count}/\{marker_count} regions=\{visible_region_count}/\{region_count} assets=\{asset_count} diagnostics=\{diagnostics.length()}",
  }
}

///|
pub fn scene_marker_count(input : Scene) -> Int {
  let mut total = 0
  for item in input.layers {
    total += item.markers.length()
  }
  total
}

///|
pub fn scene_region_count(input : Scene) -> Int {
  let mut total = 0
  for item in input.layers {
    total += item.regions.length()
  }
  total
}

///|
pub fn scene_visible_marker_count(
  input : Scene,
  viewport : SceneViewport,
) -> Int {
  let mut total = 0
  for item in input.layers {
    for scene_marker in item.markers {
      if marker_in_viewport(scene_marker, viewport) {
        total += 1
      }
    }
  }
  total
}

///|
pub fn scene_visible_region_count(
  input : Scene,
  viewport : SceneViewport,
) -> Int {
  let mut total = 0
  for item in input.layers {
    for scene_region in item.regions {
      if region_in_viewport(scene_region, viewport) {
        total += 1
      }
    }
  }
  total
}

///|
pub fn scene_render_mode_id(mode : SceneRenderMode) -> String {
  match mode {
    StaticMarkup => "static-markup"
    CanvasSurface => "canvas-surface"
    LightweightMarkup => "lightweight-markup"
  }
}

///|
pub fn scene_quality_id(quality : SceneQualityLevel) -> String {
  match quality {
    FullQuality => "full"
    BalancedQuality => "balanced"
    LightweightQuality => "lightweight"
  }
}

///|
fn limit_scene_markers(input : Scene, max_markers : Int) -> Scene {
  let layers : Array[SceneLayer] = []
  let mut remaining = max_markers
  for item in input.layers {
    let markers : Array[SceneMarker] = []
    if remaining > 0 {
      for marker in item.markers {
        if remaining > 0 {
          markers.push(marker)
          remaining -= 1
        }
      }
    }
    layers.push(layer(item.id, item.z_index, markers, regions=item.regions))
  }
  scene(input.id, input.size, layers, assets=input.assets)
}

///|
fn limit_scene_regions(input : Scene, max_regions : Int) -> Scene {
  let layers : Array[SceneLayer] = []
  let mut remaining = max_regions
  for item in input.layers {
    let regions : Array[SceneRegion] = []
    if remaining > 0 {
      for scene_region in item.regions {
        if remaining > 0 {
          regions.push(scene_region)
          remaining -= 1
        }
      }
    }
    layers.push(layer(item.id, item.z_index, item.markers, regions~))
  }
  scene(input.id, input.size, layers, assets=input.assets)
}

///|
fn limit_scene_geometry(input : Scene, budget : SceneRenderBudget) -> Scene {
  limit_scene_regions(
    limit_scene_markers(input, budget.max_markers),
    budget.max_regions,
  )
}