///|
pub struct SceneVisualQualityBudget {
  min_width : Int
  min_height : Int
  max_markers_without_status : Int
  max_markers_without_tap : Int
  max_markers_without_asset_ref : Int
  max_unresolved_asset_refs : Int
  max_regions_without_status : Int
  max_regions_without_tap : Int
  min_hit_width : Int
  min_hit_height : Int
  max_small_hit_targets : Int
} derive(Debug, Eq)

///|
pub struct SceneVisualQualityPlan {
  render_plan : SceneRenderPlan
  asset_plan : SceneAssetPlan
  marker_count : Int
  region_count : Int
  markers_without_status_count : Int
  markers_without_tap_count : Int
  markers_without_asset_ref_count : Int
  unresolved_asset_ref_count : Int
  regions_without_status_count : Int
  regions_without_tap_count : Int
  small_hit_target_count : Int
  degraded : Bool
  diagnostics : Array[String]
  summary : String
} derive(Debug, Eq)

///|
pub fn scene_visual_quality_budget(
  min_width~ : Int,
  min_height~ : Int,
  max_markers_without_status~ : Int,
  max_markers_without_tap~ : Int,
  max_markers_without_asset_ref~ : Int,
  max_unresolved_asset_refs~ : Int,
  max_regions_without_status? : Int = 0,
  max_regions_without_tap? : Int = 0,
  min_hit_width? : Int = 44,
  min_hit_height? : Int = 44,
  max_small_hit_targets? : Int = 0,
) -> SceneVisualQualityBudget {
  {
    min_width,
    min_height,
    max_markers_without_status,
    max_markers_without_tap,
    max_markers_without_asset_ref,
    max_unresolved_asset_refs,
    max_regions_without_status,
    max_regions_without_tap,
    min_hit_width,
    min_hit_height,
    max_small_hit_targets,
  }
}

///|
pub fn default_scene_visual_quality_budget() -> SceneVisualQualityBudget {
  {
    min_width: 320,
    min_height: 240,
    max_markers_without_status: 0,
    max_markers_without_tap: 0,
    max_markers_without_asset_ref: 0,
    max_unresolved_asset_refs: 0,
    max_regions_without_status: 0,
    max_regions_without_tap: 0,
    min_hit_width: 44,
    min_hit_height: 44,
    max_small_hit_targets: 0,
  }
}

///|
pub fn plan_scene_visual_quality(input : Scene) -> SceneVisualQualityPlan {
  plan_scene_visual_quality_with_budget(
    input,
    default_scene_render_budget(),
    default_scene_asset_budget(),
    default_scene_visual_quality_budget(),
  )
}

///|
pub fn plan_scene_visual_quality_with_budget(
  input : Scene,
  render_budget : SceneRenderBudget,
  asset_budget : SceneAssetBudget,
  quality_budget : SceneVisualQualityBudget,
) -> SceneVisualQualityPlan {
  plan_scene_visual_quality_from_plans(
    input,
    plan_scene_render_with_budget(input, render_budget),
    plan_scene_assets_with_budget(input.assets, asset_budget),
    quality_budget,
  )
}

///|
pub fn plan_scene_visual_quality_viewport(
  input : Scene,
  viewport : SceneViewport,
  render_budget : SceneRenderBudget,
  asset_budget : SceneAssetBudget,
  quality_budget : SceneVisualQualityBudget,
) -> SceneVisualQualityPlan {
  let visible = scene_visual_quality_viewport_scene(input, viewport)
  plan_scene_visual_quality_from_plans(
    visible,
    plan_scene_render_viewport(input, viewport, render_budget),
    plan_scene_assets_with_budget(visible.assets, asset_budget),
    quality_budget,
  )
}

///|
fn scene_visual_quality_viewport_scene(
  input : Scene,
  viewport : SceneViewport,
) -> Scene {
  let visible = viewport_scene(input, viewport)
  scene(
    visible.id,
    scene_size(viewport.width, viewport.height),
    visible.layers,
    assets=visible.assets,
  )
}

///|
fn plan_scene_visual_quality_from_plans(
  input : Scene,
  render_plan : SceneRenderPlan,
  asset_plan : SceneAssetPlan,
  quality_budget : SceneVisualQualityBudget,
) -> SceneVisualQualityPlan {
  let diagnostics : Array[String] = []
  let (
    markers_without_status_count,
    markers_without_tap_count,
    markers_without_asset_ref_count,
    unresolved_asset_ref_count,
    regions_without_status_count,
    regions_without_tap_count,
    small_hit_target_count,
  ) = scene_quality_counts(input, quality_budget)
  push_scene_size_diagnostics(input, quality_budget, diagnostics)
  push_marker_quality_diagnostics(
    markers_without_status_count, markers_without_tap_count, markers_without_asset_ref_count,
    unresolved_asset_ref_count, small_hit_target_count, quality_budget, diagnostics,
  )
  push_region_quality_diagnostics(
    regions_without_status_count, regions_without_tap_count, quality_budget, diagnostics,
  )
  push_prefixed_quality_diagnostics(
    "render",
    render_plan.diagnostics,
    diagnostics,
  )
  push_prefixed_quality_diagnostics(
    "assets",
    asset_plan.diagnostics,
    diagnostics,
  )
  let degraded = diagnostics.length() > 0 ||
    !(render_plan.quality is FullQuality)
  {
    render_plan,
    asset_plan,
    marker_count: scene_marker_count(input),
    region_count: scene_region_count(input),
    markers_without_status_count,
    markers_without_tap_count,
    markers_without_asset_ref_count,
    unresolved_asset_ref_count,
    regions_without_status_count,
    regions_without_tap_count,
    small_hit_target_count,
    degraded,
    diagnostics,
    summary: "Bunnia scene visual quality plan: markers=\{scene_marker_count(input)} regions=\{scene_region_count(input)} missing_status=\{markers_without_status_count} missing_tap=\{markers_without_tap_count} missing_assets=\{markers_without_asset_ref_count} unresolved_assets=\{unresolved_asset_ref_count} region_missing_status=\{regions_without_status_count} region_missing_tap=\{regions_without_tap_count} small_hit_targets=\{small_hit_target_count} degraded=\{degraded} diagnostics=\{diagnostics.length()}",
  }
}

///|
fn scene_quality_counts(
  input : Scene,
  budget : SceneVisualQualityBudget,
) -> (Int, Int, Int, Int, Int, Int, Int) {
  let mut markers_without_status_count = 0
  let mut markers_without_tap_count = 0
  let mut markers_without_asset_ref_count = 0
  let mut unresolved_asset_ref_count = 0
  let mut regions_without_status_count = 0
  let mut regions_without_tap_count = 0
  let mut small_hit_target_count = 0
  for scene_layer in input.layers {
    for scene_marker in scene_layer.markers {
      if scene_marker.status == "" {
        markers_without_status_count += 1
      }
      if scene_marker.tap_message == "" {
        markers_without_tap_count += 1
      }
      if scene_marker.asset_ref == "" {
        markers_without_asset_ref_count += 1
      } else if !has_asset(input.assets, scene_marker.asset_ref) {
        unresolved_asset_ref_count += 1
      }
      if scene_marker.tap_message != "" &&
        (
          scene_marker.hit_width < budget.min_hit_width ||
          scene_marker.hit_height < budget.min_hit_height
        ) {
        small_hit_target_count += 1
      }
    }
    for scene_region in scene_layer.regions {
      if scene_region.status == "" {
        regions_without_status_count += 1
      }
      if scene_region.tap_message == "" {
        regions_without_tap_count += 1
      }
    }
  }
  (
    markers_without_status_count, markers_without_tap_count, markers_without_asset_ref_count,
    unresolved_asset_ref_count, regions_without_status_count, regions_without_tap_count,
    small_hit_target_count,
  )
}

///|
fn push_scene_size_diagnostics(
  input : Scene,
  budget : SceneVisualQualityBudget,
  diagnostics : Array[String],
) -> Unit {
  if input.size.width < budget.min_width {
    diagnostics.push(
      "scene-width-under-min:\{input.size.width}/\{budget.min_width}",
    )
  }
  if input.size.height < budget.min_height {
    diagnostics.push(
      "scene-height-under-min:\{input.size.height}/\{budget.min_height}",
    )
  }
}

///|
fn push_marker_quality_diagnostics(
  markers_without_status_count : Int,
  markers_without_tap_count : Int,
  markers_without_asset_ref_count : Int,
  unresolved_asset_ref_count : Int,
  small_hit_target_count : Int,
  budget : SceneVisualQualityBudget,
  diagnostics : Array[String],
) -> Unit {
  if markers_without_status_count > budget.max_markers_without_status {
    diagnostics.push(
      "scene-markers-without-status:\{markers_without_status_count}/\{budget.max_markers_without_status}",
    )
  }
  if markers_without_tap_count > budget.max_markers_without_tap {
    diagnostics.push(
      "scene-markers-without-tap:\{markers_without_tap_count}/\{budget.max_markers_without_tap}",
    )
  }
  if markers_without_asset_ref_count > budget.max_markers_without_asset_ref {
    diagnostics.push(
      "scene-markers-without-asset-ref:\{markers_without_asset_ref_count}/\{budget.max_markers_without_asset_ref}",
    )
  }
  if unresolved_asset_ref_count > budget.max_unresolved_asset_refs {
    diagnostics.push(
      "scene-unresolved-asset-refs:\{unresolved_asset_ref_count}/\{budget.max_unresolved_asset_refs}",
    )
  }
  if small_hit_target_count > budget.max_small_hit_targets {
    diagnostics.push(
      "scene-small-hit-targets:\{small_hit_target_count}/\{budget.max_small_hit_targets}",
    )
  }
}

///|
fn push_region_quality_diagnostics(
  regions_without_status_count : Int,
  regions_without_tap_count : Int,
  budget : SceneVisualQualityBudget,
  diagnostics : Array[String],
) -> Unit {
  if regions_without_status_count > budget.max_regions_without_status {
    diagnostics.push(
      "scene-regions-without-status:\{regions_without_status_count}/\{budget.max_regions_without_status}",
    )
  }
  if regions_without_tap_count > budget.max_regions_without_tap {
    diagnostics.push(
      "scene-regions-without-tap:\{regions_without_tap_count}/\{budget.max_regions_without_tap}",
    )
  }
}

///|
fn push_prefixed_quality_diagnostics(
  prefix : String,
  input : Array[String],
  output : Array[String],
) -> Unit {
  for item in input {
    output.push("\{prefix}:\{item}")
  }
}

///|
fn has_asset(assets : Array[SceneAsset], asset_ref : String) -> Bool {
  for item in assets {
    if item.id == asset_ref {
      return true
    }
  }
  false
}