///|
/// Unified production profile for CI, importers, and asset dashboards.
///
/// This is the orchestration layer for the independent analyzers. It keeps
/// every measured domain visible in one stable value, computes a transparent
/// score, and exposes policy failures separately from advisory findings.

///|
pub(all) enum ProductionVerdict {
  Pass
  PassWithWarnings
  Fail
  Empty
} derive(Debug, ToJson, Eq)

///|
pub(all) struct ProductionPolicy {
  semantic : SemanticValidationPolicy
  geometry : GeometryBudget
  materials : MaterialBudget
  resources : ResourcePolicy
  skins : SkinBudget
  animations : AnimationBudget
  optimization : OptimizationPolicy
  minimum_score : Int
  fail_on_warnings : Bool
} derive(Debug, ToJson, Eq)

///|
pub(all) struct ProductionMetrics {
  vertices : Int
  indices : Int
  triangles : Int
  draw_calls : Int
  attribute_bytes : Int
  scene_nodes : Int
  scene_depth : Int
  materials : Int
  textures : Int
  images : Int
  external_resources : Int
  resource_bytes : Int
  animations : Int
  animation_keyframes : Int
  skins : Int
  skin_joints : Int
  diagnostic_errors : Int
  diagnostic_warnings : Int
  semantic_errors : Int
  semantic_warnings : Int
} derive(Debug, ToJson, Eq)

///|
pub(all) struct ProductionGate {
  passed : Bool
  score : Int
  verdict : ProductionVerdict
  violations : Array[String]
  warnings : Array[String]
  metric_failures : Array[String]
} derive(Debug, ToJson, Eq)

///|
pub(all) struct ProductionProfile {
  version : String
  generator : String?
  verdict : ProductionVerdict
  metrics : ProductionMetrics
  asset_report : AssetReport
  semantic : SemanticValidationReport
  geometry : DocumentGeometryReport
  scene : SceneGraphReport
  materials : Array[MaterialReport]
  textures : Array[TextureReport]
  resources : ResourceManifest
  animations : Array[AnimationClipReport]
  skins : Array[SkinReport]
  optimization : OptimizationPlan
  gate : ProductionGate
} derive(Debug, ToJson, Eq)

///|
pub fn default_production_policy() -> ProductionPolicy {
  {
    semantic: default_semantic_policy(),
    geometry: default_geometry_budget(),
    materials: default_material_budget(),
    resources: default_resource_policy(),
    skins: default_skin_budget(),
    animations: default_animation_budget(),
    optimization: default_optimization_policy(),
    minimum_score: 70,
    fail_on_warnings: false,
  }
}

///|
pub fn mobile_production_policy() -> ProductionPolicy {
  {
    semantic: strict_semantic_policy(),
    geometry: mobile_geometry_budget(),
    materials: mobile_material_budget(),
    resources: embedded_resource_policy(),
    skins: mobile_skin_budget(),
    animations: {
      max_clips: 128,
      max_channels: 4000,
      max_duration: 1800.0,
      max_keyframes: 500000,
      require_monotonic_times: true,
    },
    optimization: mobile_optimization_policy(),
    minimum_score: 80,
    fail_on_warnings: true,
  }
}

///|
pub fn production_verdict_name(verdict : ProductionVerdict) -> String {
  match verdict {
    Pass => "pass"
    PassWithWarnings => "pass-with-warnings"
    Fail => "fail"
    Empty => "empty"
  }
}

///|
fn production_metrics(
  asset : AssetReport,
  geometry : DocumentGeometryReport,
  scene : SceneGraphReport,
  materials : Array[MaterialReport],
  textures : Array[TextureReport],
  resources : ResourceManifest,
  animations : Array[AnimationClipReport],
  skins : Array[SkinReport],
  semantic : SemanticValidationReport,
) -> ProductionMetrics {
  {
    vertices: geometry.vertex_count,
    indices: geometry.index_count,
    triangles: geometry.triangle_count,
    draw_calls: geometry.primitive_count,
    attribute_bytes: geometry.attribute_bytes,
    scene_nodes: scene.reachable_count,
    scene_depth: scene.max_depth,
    materials: materials.length(),
    textures: textures.length(),
    images: resource_manifest_image_count(resources),
    external_resources: resources.external_count,
    resource_bytes: resources.declared_bytes,
    animations: animations.length(),
    animation_keyframes: total_animation_keyframes(animations),
    skins: skins.length(),
    skin_joints: skins.fold(init=0, (sum, report) => sum + report.joint_count),
    diagnostic_errors: asset.diagnostics.errors,
    diagnostic_warnings: asset.diagnostics.warnings,
    semantic_errors: semantic.errors,
    semantic_warnings: semantic.warnings,
  }
}

///|
fn production_score_penalty(value : Int, threshold : Int, weight : Int) -> Int {
  if value <= threshold {
    0
  } else {
    let overage = value - threshold
    let denominator = if threshold <= 0 { 1 } else { threshold }
    let penalty = overage * weight / denominator
    if penalty < 1 {
      1
    } else {
      penalty
    }
  }
}

///|
pub fn production_score(
  metrics : ProductionMetrics,
  policy : ProductionPolicy,
) -> Int {
  let mut score = 100
  score -= metrics.diagnostic_errors * 20
  score -= metrics.semantic_errors * 15
  score -= metrics.semantic_warnings * 2
  score -= production_score_penalty(
    metrics.draw_calls,
    policy.geometry.max_draw_calls,
    15,
  )
  score -= production_score_penalty(
    metrics.vertices,
    policy.geometry.max_vertices,
    10,
  )
  score -= production_score_penalty(
    metrics.scene_depth,
    policy.skins.max_depth,
    5,
  )
  score -= production_score_penalty(
    metrics.external_resources,
    policy.resources.max_external_count,
    10,
  )
  if score < 0 {
    0
  } else if score > 100 {
    100
  } else {
    score
  }
}

///|
fn production_gate_with_results(
  metrics : ProductionMetrics,
  semantic : SemanticValidationReport,
  geometry : DocumentGeometryReport,
  material_result : MaterialBudgetResult,
  resource_result : ResourcePolicyResult,
  skin_result : SkinBudgetResult,
  animation_result : AnimationBudgetResult,
  policy : ProductionPolicy,
) -> ProductionGate {
  let violations : Array[String] = []
  let warnings : Array[String] = []
  let metric_failures : Array[String] = []
  let score = production_score(metrics, policy)
  let geometry_result = geometry_budget_result(geometry, policy.geometry)
  for issue in semantic_error_issues(semantic) {
    violations.push(semantic_issue_line(issue))
  }
  for violation in geometry_result.violations {
    metric_failures.push(violation)
  }
  for violation in material_result.violations {
    metric_failures.push(violation)
  }
  for violation in resource_result.violations {
    metric_failures.push(violation)
  }
  for violation in skin_result.violations {
    metric_failures.push(violation)
  }
  for violation in animation_result.violations {
    metric_failures.push(violation)
  }
  if policy.fail_on_warnings {
    for issue in semantic_warning_issues(semantic) {
      violations.push(semantic_issue_line(issue))
    }
  } else {
    for issue in semantic_warning_issues(semantic) {
      warnings.push(semantic_issue_line(issue))
    }
  }
  if score < policy.minimum_score {
    violations.push(
      "production score \{score} is below the minimum \{policy.minimum_score}",
    )
  }
  for failure in metric_failures {
    violations.push(failure)
  }
  let verdict = if metrics.scene_nodes == 0 &&
    metrics.materials == 0 &&
    metrics.vertices == 0 {
    ProductionVerdict::Empty
  } else if violations.length() > 0 {
    Fail
  } else if warnings.length() > 0 {
    PassWithWarnings
  } else {
    Pass
  }
  {
    passed: verdict == Pass || verdict == PassWithWarnings,
    score,
    verdict,
    violations,
    warnings,
    metric_failures,
  }
}

///|
/// Apply a production policy to report data that has already been collected.
/// This compatibility entry point is useful to dashboards and cache readers
/// that do not retain the original document.
pub fn production_gate(
  metrics : ProductionMetrics,
  semantic : SemanticValidationReport,
  geometry : DocumentGeometryReport,
  materials : Array[MaterialReport],
  resources : ResourceManifest,
  animations : Array[AnimationClipReport],
  skins : Array[SkinReport],
  policy : ProductionPolicy,
) -> ProductionGate {
  production_gate_with_results(
    metrics,
    semantic,
    geometry,
    material_budget_result_from_reports(materials, policy.materials),
    resource_policy_result(resources, policy.resources),
    skin_budget_result(skins, policy.skins),
    animation_budget_result_from_reports(animations, policy.animations),
    policy,
  )
}

///|
/// Apply a production policy while retaining document-level resource data.
/// This is the preferred entry point for importers because it counts unused
/// images and non-contiguous texture-coordinate sets as well as referenced
/// material slots.
pub fn production_gate_for_document(
  doc : GltfDocument,
  metrics : ProductionMetrics,
  semantic : SemanticValidationReport,
  geometry : DocumentGeometryReport,
  materials : Array[MaterialReport],
  resources : ResourceManifest,
  animations : Array[AnimationClipReport],
  skins : Array[SkinReport],
  policy : ProductionPolicy,
) -> ProductionGate {
  ignore(materials)
  production_gate_with_results(
    metrics,
    semantic,
    geometry,
    material_budget_result(doc, policy.materials),
    resource_policy_result(resources, policy.resources),
    skin_budget_result(skins, policy.skins),
    animation_budget_result(doc, animations, policy.animations),
    policy,
  )
}

///|
pub fn build_production_profile(
  doc : GltfDocument,
  asset : BinaryAsset,
  policy : ProductionPolicy,
) -> ProductionProfile {
  let asset_report = build_asset_report(doc, default_diagnostic_policy())
  let semantic = semantic_validate(doc, policy.semantic)
  let geometry = document_geometry_report(doc, asset)
  let scene = document_scene_graph_report(doc)
  let materials = all_material_reports(doc)
  let textures = all_texture_reports(doc)
  let resources = resource_manifest(doc, policy.resources)
  let animations = all_animation_clip_reports(doc, asset)
  let skins = all_skin_reports(doc, asset)
  let optimization = optimization_plan(doc, asset, policy.optimization)
  let metrics = production_metrics(
    asset_report, geometry, scene, materials, textures, resources, animations, skins,
    semantic,
  )
  let gate = production_gate_for_document(
    doc, metrics, semantic, geometry, materials, resources, animations, skins, policy,
  )
  {
    version: doc.version,
    generator: doc.generator,
    verdict: gate.verdict,
    metrics,
    asset_report,
    semantic,
    geometry,
    scene,
    materials,
    textures,
    resources,
    animations,
    skins,
    optimization,
    gate,
  }
}

///|
pub fn build_default_production_profile(
  doc : GltfDocument,
  asset : BinaryAsset,
) -> ProductionProfile {
  build_production_profile(doc, asset, default_production_policy())
}

///|
pub fn build_mobile_production_profile(
  doc : GltfDocument,
  asset : BinaryAsset,
) -> ProductionProfile {
  build_production_profile(doc, asset, mobile_production_policy())
}

///|
pub fn production_profile_is_acceptable(profile : ProductionProfile) -> Bool {
  profile.gate.passed
}

///|
pub fn production_profile_score(profile : ProductionProfile) -> Int {
  profile.gate.score
}

///|
pub fn production_profile_has_blockers(profile : ProductionProfile) -> Bool {
  profile.gate.violations.length() > 0
}

///|
pub fn production_profile_summary_lines(
  profile : ProductionProfile,
) -> Array[String] {
  let m = profile.metrics
  [
    "verdict=\{production_verdict_name(profile.verdict)}, score=\{profile.gate.score}",
    "vertices=\{m.vertices}, triangles=\{m.triangles}, draw_calls=\{m.draw_calls}",
    "nodes=\{m.scene_nodes}, materials=\{m.materials}, textures=\{m.textures}",
    "resources=\{m.external_resources} external / \{m.resource_bytes} bytes",
    "animations=\{m.animations} / \{m.animation_keyframes} keys, skins=\{m.skins} / \{m.skin_joints} joints",
    "errors=\{m.diagnostic_errors + m.semantic_errors}, warnings=\{m.diagnostic_warnings + m.semantic_warnings}",
  ]
}

///|
pub fn production_profile_failure_lines(
  profile : ProductionProfile,
) -> Array[String] {
  profile.gate.violations
}

///|
pub fn production_profile_warning_lines(
  profile : ProductionProfile,
) -> Array[String] {
  profile.gate.warnings
}

///|
pub fn production_profile_to_json(profile : ProductionProfile) -> String {
  Repr(profile).to_string()
}