///|
/// Import pipeline planning and stage accounting.
///
/// A production importer benefits from explicit stage boundaries. This module
/// describes the dependency order between parsing, binary decoding, semantic
/// validation, optimization, and packaging, so a CLI or build service can
/// report a failed stage without hiding the successful work before it.

///|
pub(all) enum ImportStageKind {
  ParseDocument
  LoadBinary
  ValidateStructure
  ValidateSemantics
  AuditLayout
  AnalyzeGeometry
  AnalyzeMaterials
  AuditResources
  AnalyzeAnimations
  AnalyzeSkins
  BuildOptimizationPlan
  BuildPackagePlan
  EmitReport
} derive(Debug, ToJson, Eq)

///|
pub(all) enum ImportStageStatus {
  Pending
  Running
  Passed
  PassedWithWarnings
  Failed
  Skipped
} derive(Debug, ToJson, Eq)

///|
pub(all) struct ImportStage {
  index : Int
  kind : ImportStageKind
  name : String
  dependencies : Array[Int]
  status : ImportStageStatus
  duration_hint_ms : Int
  blocking : Bool
  messages : Array[String]
} derive(Debug, ToJson, Eq)

///|
pub(all) struct ImportPipeline {
  stages : Array[ImportStage]
  valid : Bool
  completed_count : Int
  failed_count : Int
  warning_count : Int
} derive(Debug, ToJson, Eq)

///|
pub(all) struct ImportPipelinePolicy {
  fail_on_semantic_error : Bool
  fail_on_layout_error : Bool
  fail_on_unsafe_resource : Bool
  run_optimization : Bool
  run_packaging : Bool
  emit_report : Bool
} derive(Debug, ToJson, Eq)

///|
pub fn default_import_pipeline_policy() -> ImportPipelinePolicy {
  {
    fail_on_semantic_error: true,
    fail_on_layout_error: true,
    fail_on_unsafe_resource: true,
    run_optimization: true,
    run_packaging: true,
    emit_report: true,
  }
}

///|
pub fn import_stage_name(kind : ImportStageKind) -> String {
  match kind {
    ParseDocument => "parse-document"
    LoadBinary => "load-binary"
    ValidateStructure => "validate-structure"
    ValidateSemantics => "validate-semantics"
    AuditLayout => "audit-layout"
    AnalyzeGeometry => "analyze-geometry"
    AnalyzeMaterials => "analyze-materials"
    AuditResources => "audit-resources"
    AnalyzeAnimations => "analyze-animations"
    AnalyzeSkins => "analyze-skins"
    BuildOptimizationPlan => "build-optimization-plan"
    BuildPackagePlan => "build-package-plan"
    EmitReport => "emit-report"
  }
}

///|
pub fn import_stage_is_terminal(status : ImportStageStatus) -> Bool {
  status == Passed ||
  status == PassedWithWarnings ||
  status == Failed ||
  status == Skipped
}

///|
pub fn import_stage_is_success(status : ImportStageStatus) -> Bool {
  status == Passed || status == PassedWithWarnings
}

///|
pub fn import_stage_status_name(status : ImportStageStatus) -> String {
  match status {
    Pending => "pending"
    Running => "running"
    Passed => "passed"
    PassedWithWarnings => "passed-with-warnings"
    Failed => "failed"
    Skipped => "skipped"
  }
}

///|
fn stage(
  index : Int,
  kind : ImportStageKind,
  dependencies : Array[Int],
  blocking : Bool,
) -> ImportStage {
  {
    index,
    kind,
    name: import_stage_name(kind),
    dependencies,
    status: Pending,
    duration_hint_ms: 0,
    blocking,
    messages: [],
  }
}

///|
pub fn default_import_stages() -> Array[ImportStage] {
  [
    stage(0, ParseDocument, [], true),
    stage(1, LoadBinary, [0], true),
    stage(2, ValidateStructure, [0], true),
    stage(3, ValidateSemantics, [2], true),
    stage(4, AuditLayout, [1, 2], true),
    stage(5, AnalyzeGeometry, [1, 3, 4], false),
    stage(6, AnalyzeMaterials, [3], false),
    stage(7, AuditResources, [2, 3], true),
    stage(8, AnalyzeAnimations, [1, 3, 4], false),
    stage(9, AnalyzeSkins, [1, 3, 4], false),
    stage(10, BuildOptimizationPlan, [5, 6, 7, 8, 9], false),
    stage(11, BuildPackagePlan, [7], false),
    stage(12, EmitReport, [5, 6, 7, 8, 9, 10, 11], false),
  ]
}

///|
pub fn import_pipeline_stage(
  pipeline : ImportPipeline,
  kind : ImportStageKind,
) -> ImportStage? {
  for stage in pipeline.stages {
    if stage.kind == kind {
      return Some(stage)
    }
  }
  None
}

///|
pub fn import_pipeline_pending_stages(
  pipeline : ImportPipeline,
) -> Array[ImportStage] {
  pipeline.stages.filter(stage => stage.status == Pending)
}

///|
pub fn import_pipeline_failed_stages(
  pipeline : ImportPipeline,
) -> Array[ImportStage] {
  pipeline.stages.filter(stage => stage.status == Failed)
}

///|
pub fn import_pipeline_ready_stages(
  pipeline : ImportPipeline,
) -> Array[ImportStage] {
  let ready : Array[ImportStage] = []
  for stage in pipeline.stages {
    if stage.status == Pending {
      let dependencies_ready = stage.dependencies.fold(init=true, (
        passed,
        dependency,
      ) => {
        match pipeline.stages.get(dependency) {
          Some(value) => passed && import_stage_is_success(value.status)
          None => false
        }
      })
      if dependencies_ready {
        ready.push(stage)
      }
    }
  }
  ready
}

///|
pub fn import_pipeline_mark_stage(
  pipeline : ImportPipeline,
  kind : ImportStageKind,
  status : ImportStageStatus,
  messages : Array[String],
) -> ImportPipeline {
  let stages : Array[ImportStage] = []
  for stage in pipeline.stages {
    if stage.kind == kind {
      stages.push({ ..stage, status, messages })
    } else {
      stages.push(stage)
    }
  }
  let completed = stages.fold(init=0, (count, stage) => {
    if import_stage_is_terminal(stage.status) {
      count + 1
    } else {
      count
    }
  })
  let failed = stages.fold(init=0, (count, stage) => {
    if stage.status == Failed {
      count + 1
    } else {
      count
    }
  })
  let warnings = stages.fold(init=0, (count, stage) => {
    if stage.status == PassedWithWarnings {
      count + 1
    } else {
      count
    }
  })
  {
    stages,
    valid: failed == 0,
    completed_count: completed,
    failed_count: failed,
    warning_count: warnings,
  }
}

///|
pub fn import_pipeline_from_profile(
  doc : GltfDocument,
  profile : ProductionProfile,
  policy : ImportPipelinePolicy,
) -> ImportPipeline {
  let base = {
    stages: default_import_stages(),
    valid: true,
    completed_count: 0,
    failed_count: 0,
    warning_count: 0,
  }
  let mut pipeline = base
  pipeline = import_pipeline_mark_stage(pipeline, ParseDocument, Passed, [])
  pipeline = import_pipeline_mark_stage(pipeline, LoadBinary, Passed, [])
  pipeline = import_pipeline_mark_stage(
    pipeline,
    ValidateStructure,
    if profile.asset_report.diagnostics.errors == 0 {
      Passed
    } else {
      Failed
    },
    diagnostics_to_lines(profile.asset_report.diagnostics),
  )
  pipeline = import_pipeline_mark_stage(
    pipeline,
    ValidateSemantics,
    if profile.semantic.errors == 0 || !policy.fail_on_semantic_error {
      if profile.semantic.warnings > 0 {
        PassedWithWarnings
      } else {
        Passed
      }
    } else {
      Failed
    },
    semantic_issue_lines(profile.semantic),
  )
  let layout_policy = {
    ..default_buffer_layout_policy(),
    reject_overlaps: policy.fail_on_layout_error,
  }
  let layout = buffer_layout_report(doc, layout_policy)
  pipeline = import_pipeline_mark_stage(
    pipeline,
    AuditLayout,
    if layout.valid || !policy.fail_on_layout_error {
      Passed
    } else {
      Failed
    },
    buffer_layout_summary_lines(layout),
  )
  pipeline = import_pipeline_mark_stage(pipeline, AnalyzeGeometry, Passed, [])
  pipeline = import_pipeline_mark_stage(pipeline, AnalyzeMaterials, Passed, [])
  pipeline = import_pipeline_mark_stage(
    pipeline,
    AuditResources,
    if profile.resources.unsafe_count == 0 || !policy.fail_on_unsafe_resource {
      Passed
    } else {
      Failed
    },
    resource_manifest_summary_lines(profile.resources),
  )
  pipeline = import_pipeline_mark_stage(pipeline, AnalyzeAnimations, Passed, [])
  pipeline = import_pipeline_mark_stage(pipeline, AnalyzeSkins, Passed, [])
  pipeline = import_pipeline_mark_stage(
    pipeline,
    BuildOptimizationPlan,
    if policy.run_optimization {
      Passed
    } else {
      Skipped
    },
    optimization_plan_summary_lines(profile.optimization),
  )
  pipeline = import_pipeline_mark_stage(
    pipeline,
    BuildPackagePlan,
    if policy.run_packaging {
      Passed
    } else {
      Skipped
    },
    [],
  )
  pipeline = import_pipeline_mark_stage(
    pipeline,
    EmitReport,
    if policy.emit_report {
      Passed
    } else {
      Skipped
    },
    production_profile_summary_lines(profile),
  )
  pipeline
}

///|
pub fn import_pipeline_is_complete(pipeline : ImportPipeline) -> Bool {
  pipeline.completed_count == pipeline.stages.length()
}

///|
pub fn import_pipeline_has_blocker(pipeline : ImportPipeline) -> Bool {
  for stage in pipeline.stages {
    if stage.status == Failed && stage.blocking {
      return true
    }
  }
  false
}

///|
pub fn import_pipeline_progress(pipeline : ImportPipeline) -> Double {
  if pipeline.stages.length() == 0 {
    1.0
  } else {
    pipeline.completed_count.to_double() / pipeline.stages.length().to_double()
  }
}

///|
pub fn import_pipeline_stage_lines(pipeline : ImportPipeline) -> Array[String] {
  pipeline.stages.map(stage => {
    let message = if stage.messages.length() == 0 {
      ""
    } else {
      ": \{stage.messages[0]}"
    }
    "\{stage.index}. \{stage.name}=\{import_stage_status_name(stage.status)}\{message}"
  })
}

///|
pub fn import_pipeline_summary_lines(
  pipeline : ImportPipeline,
) -> Array[String] {
  [
    "complete=\{import_pipeline_is_complete(pipeline)}, progress=\{import_pipeline_progress(pipeline)}",
    "passed=\{pipeline.completed_count - pipeline.failed_count}, failed=\{pipeline.failed_count}, warnings=\{pipeline.warning_count}",
    "blocker=\{import_pipeline_has_blocker(pipeline)}",
  ]
}