///|
/// Stages reported by a reusable parse-and-analyze pipeline.
pub enum PipelineStage {
  SourceInventory
  Parse
  Validation
  Index
  Analysis
  Export
} derive(Debug, Eq)

///|
/// One stage result with deterministic counters and a status flag.
pub struct PipelineResult {
  stage : PipelineStage
  ok : Bool
  items : Int
  detail : String
} derive(Debug, Eq)

///|
/// Complete result of the high-level processing pipeline.
pub struct TracePipeline {
  source : SourceStatistics
  trace : Trace?
  results : Array[PipelineResult]
} derive(Debug)

///|
/// Run inventory, parse, validation, indexing, analysis, and export checks.
pub fn run_pipeline(source : String) -> TracePipeline {
  let source_info = source_statistics(source)
  let results : Array[PipelineResult] = [
    {
      stage: SourceInventory,
      ok: source_info.nonempty_lines > 0,
      items: source_info.lines,
      detail: source_statistics_line(source_info),
    },
  ]
  match parse_normalized(source) {
    Err(error) => {
      results.push({
        stage: Parse,
        ok: false,
        items: 0,
        detail: parse_error_message(error),
      })
      { source: source_info, trace: None, results }
    }
    Ok(trace) => {
      results.push({
        stage: Parse,
        ok: true,
        items: trace.changes.length(),
        detail: "ok",
      })
      let summary = validation_summary(trace)
      results.push({
        stage: Validation,
        ok: structural_gate(trace),
        items: summary.diagnostics,
        detail: validation_summary_line(summary),
      })
      let index = build_index(trace)
      results.push({
        stage: Index,
        ok: index.is_consistent(trace),
        items: index.change_count(""),
        detail: "signals=" + index.signal_count().to_string(),
      })
      let metrics = trace_analysis_metrics(trace)
      results.push({
        stage: Analysis,
        ok: analysis_safe(trace),
        items: metrics.length(),
        detail: trace_quality_report(trace),
      })
      results.push({
        stage: Export,
        ok: export_parseable(trace),
        items: json_size(trace),
        detail: export_summary(trace),
      })
      { source: source_info, trace: Some(trace), results }
    }
  }
}

///|
/// Return whether all completed pipeline stages passed.
pub fn TracePipeline::is_success(self : TracePipeline) -> Bool {
  self.results.all(fn(result) { result.ok })
}

///|
/// Return the number of pipeline stages that passed.
pub fn TracePipeline::passed_stages(self : TracePipeline) -> Int {
  self.results.count_if(fn(result) { result.ok })
}

///|
/// Return the number of pipeline stages that failed.
pub fn TracePipeline::failed_stages(self : TracePipeline) -> Int {
  self.results.count_if(fn(result) { !result.ok })
}

///|
/// Render a pipeline result as readable text.
pub fn TracePipeline::to_text(self : TracePipeline) -> String {
  let out : Array[String] = [
    "MoonBit VCD pipeline",
    source_statistics_line(self.source),
  ]
  for result in self.results {
    out.push(
      pipeline_stage_name(result.stage) +
      ": " +
      result.ok.to_string() +
      " " +
      result.detail,
    )
  }
  out.join("\n")
}

///|
/// Render pipeline stages as CSV.
pub fn TracePipeline::to_csv(self : TracePipeline) -> String {
  let out : Array[String] = ["stage,ok,items,detail"]
  for result in self.results {
    out.push(
      pipeline_stage_name(result.stage) +
      "," +
      result.ok.to_string() +
      "," +
      result.items.to_string() +
      "," +
      escape_text(result.detail),
    )
  }
  out.join("\n")
}

///|
/// Render a pipeline result as JSON.
pub fn TracePipeline::to_json(self : TracePipeline) -> String {
  let body = self.results
    .map(fn(result) {
      "{\"stage\":\"" +
      pipeline_stage_name(result.stage) +
      "\",\"ok\":" +
      result.ok.to_string() +
      ",\"items\":" +
      result.items.to_string() +
      ",\"detail\":\"" +
      escape_text(result.detail) +
      "\"}"
    })
    .join(",")
  "{\"success\":" +
  self.is_success().to_string() +
  ",\"results\":[" +
  body +
  "]}"
}

///|
fn pipeline_stage_name(stage : PipelineStage) -> String {
  match stage {
    SourceInventory => "source"
    Parse => "parse"
    Validation => "validation"
    Index => "index"
    Analysis => "analysis"
    Export => "export"
  }
}

///|
/// Return the parsed trace when every required stage passed.
pub fn TracePipeline::successful_trace(self : TracePipeline) -> Trace? {
  if self.is_success() {
    self.trace
  } else {
    None
  }
}

///|
/// Return a one-line pipeline verdict for CI.
pub fn pipeline_verdict(pipeline : TracePipeline) -> String {
  (if pipeline.is_success() { "pass" } else { "fail" }) +
  " stages=" +
  pipeline.passed_stages().to_string() +
  "/" +
  pipeline.results.length().to_string()
}