///|
/// Stages of the reusable waveform analysis workflow.
pub enum WorkflowStage {
  InputValidation
  IndexBuild
  WindowSegmentation
  CoverageAnalysis
  AssertionAnalysis
  ProtocolAnalysis
  ReportAssembly
} derive(Debug, Eq)

///|
/// Status of one workflow stage.
pub enum WorkflowStageStatus {
  StagePending
  StageRunning
  StagePassed
  StageSkipped
  StageFailed
} derive(Debug, Eq)

///|
/// Configuration for a repeatable analysis workflow.
pub struct WorkflowOptions {
  window_width : Int64
  coverage_signal_ids : Array[String]
  assertions : Array[WaveformAssertion]
  protocol_channels : Array[ProtocolChannel]
  stop_on_failure : Bool
} derive(Debug)

///|
/// Result of one workflow stage.
pub struct WorkflowStageResult {
  stage : WorkflowStage
  status : WorkflowStageStatus
  duration_units : Int64
  rows : Int
  message : String
} derive(Debug)

///|
/// Complete workflow output, retaining reusable intermediate products.
pub struct AnalysisWorkflow {
  trace : Trace
  options : WorkflowOptions
  stages : Array[WorkflowStageResult]
  index : TraceIndex?
  windows : Array[TraceWindow]
  coverage : CoverageReport?
  assertions : AssertionSummary?
  protocol_stats : Array[ProtocolStats]
  passed : Bool
} derive(Debug)

///|
/// List workflow stages.
pub fn workflow_stage_catalog() -> Array[WorkflowStage] {
  [
    InputValidation,
    IndexBuild,
    WindowSegmentation,
    CoverageAnalysis,
    AssertionAnalysis,
    ProtocolAnalysis,
    ReportAssembly,
  ]
}

///|
/// List workflow statuses.
pub fn workflow_status_catalog() -> Array[WorkflowStageStatus] {
  [StagePending, StageRunning, StagePassed, StageSkipped, StageFailed]
}

///|
/// Create default options for a production trace review.
pub fn default_workflow_options() -> WorkflowOptions {
  {
    window_width: 100L,
    coverage_signal_ids: [],
    assertions: [],
    protocol_channels: [],
    stop_on_failure: false,
  }
}

///|
/// Set analysis window width.
pub fn WorkflowOptions::with_window_width(
  self : WorkflowOptions,
  width : Int64,
) -> WorkflowOptions {
  { ..self, window_width: Int64::max(1L, width) }
}

///|
/// Add a signal to coverage analysis.
pub fn WorkflowOptions::with_coverage_signal(
  self : WorkflowOptions,
  signal_id : String,
) -> WorkflowOptions {
  let ids = self.coverage_signal_ids.copy()
  if !ids.contains(signal_id) {
    ids.push(signal_id)
  }
  { ..self, coverage_signal_ids: ids }
}

///|
/// Add an assertion to the workflow.
pub fn WorkflowOptions::with_assertion(
  self : WorkflowOptions,
  assertion : WaveformAssertion,
) -> WorkflowOptions {
  let assertions = self.assertions.copy()
  assertions.push(assertion)
  { ..self, assertions, }
}

///|
/// Add a protocol channel to the workflow.
pub fn WorkflowOptions::with_protocol(
  self : WorkflowOptions,
  channel : ProtocolChannel,
) -> WorkflowOptions {
  let channels = self.protocol_channels.copy()
  channels.push(channel)
  { ..self, protocol_channels: channels }
}

///|
/// Run all configured stages and retain their outputs.
pub fn run_analysis_workflow(
  trace : Trace,
  options : WorkflowOptions,
) -> AnalysisWorkflow {
  let stages : Array[WorkflowStageResult] = []
  let mut passed = true
  let mut index : TraceIndex? = None
  let mut windows : Array[TraceWindow] = []
  let mut coverage : CoverageReport? = None
  let mut assertions : AssertionSummary? = None
  let protocol_results : Array[ProtocolStats] = []
  let validation = deep_validate(trace)
  let validation_ok = validation
    .filter(fn(item) { item.severity == "error" })
    .length() ==
    0
  stages.push(
    workflow_stage(
      InputValidation,
      if validation_ok {
        StagePassed
      } else {
        StageFailed
      },
      validation.length(),
      if validation_ok {
        "trace is structurally valid"
      } else {
        "trace has structural errors"
      },
    ),
  )
  passed = passed && validation_ok
  if validation_ok || !options.stop_on_failure {
    let built = build_index(trace)
    index = Some(built)
    let index_ok = built.is_consistent(trace)
    stages.push(
      workflow_stage(
        IndexBuild,
        if index_ok {
          StagePassed
        } else {
          StageFailed
        },
        built.change_positions.length(),
        if index_ok {
          "index is consistent"
        } else {
          "index mismatch"
        },
      ),
    )
    passed = passed && index_ok
  } else {
    stages.push(
      workflow_stage(
        IndexBuild,
        StageSkipped,
        0,
        "stopped after validation failure",
      ),
    )
  }
  if passed || !options.stop_on_failure {
    windows = fixed_windows(trace, options.window_width)
    stages.push(
      workflow_stage(
        WindowSegmentation,
        StagePassed,
        windows.length(),
        "fixed windows generated",
      ),
    )
  } else {
    stages.push(workflow_stage(WindowSegmentation, StageSkipped, 0, "not run"))
  }
  if options.coverage_signal_ids.length() > 0 &&
    (passed || !options.stop_on_failure) {
    let all_bins = value_coverage_bins(trace)
    let bins : Array[CoverageBin] = []
    for id in options.coverage_signal_ids {
      match workflow_signal_path(trace, id) {
        Some(path) =>
          for bin in all_bins {
            if bin.group == path {
              bins.push(bin)
            }
          }
        None => ()
      }
    }
    coverage = Some(coverage_report_from_bins("workflow", bins))
    let score = match coverage {
      Some(item) => item.score
      None => 0
    }
    let coverage_ok = score >= 80
    stages.push(
      workflow_stage(
        CoverageAnalysis,
        if coverage_ok {
          StagePassed
        } else {
          StageFailed
        },
        bins.length(),
        "coverage score=" + score.to_string(),
      ),
    )
    passed = passed && coverage_ok
  } else {
    stages.push(
      workflow_stage(
        CoverageAnalysis,
        StageSkipped,
        0,
        "no coverage signals configured",
      ),
    )
  }
  if options.assertions.length() > 0 && (passed || !options.stop_on_failure) {
    let summary = run_assertion_suite(trace, options.assertions)
    assertions = Some(summary)
    stages.push(
      workflow_stage(
        AssertionAnalysis,
        if summary.is_success() {
          StagePassed
        } else {
          StageFailed
        },
        summary.total,
        "assertions=" + summary.total.to_string(),
      ),
    )
    passed = passed && summary.is_success()
  } else {
    stages.push(
      workflow_stage(
        AssertionAnalysis,
        StageSkipped,
        0,
        "no assertions configured",
      ),
    )
  }
  if options.protocol_channels.length() > 0 &&
    (passed || !options.stop_on_failure) {
    for channel in options.protocol_channels {
      let item = protocol_stats(trace, channel)
      protocol_results.push(item)
      passed = passed && item.incomplete == 0
    }
    stages.push(
      workflow_stage(
        ProtocolAnalysis,
        if protocol_results.all(fn(item) { item.incomplete == 0 }) {
          StagePassed
        } else {
          StageFailed
        },
        protocol_results.length(),
        "protocol channels analyzed",
      ),
    )
  } else {
    stages.push(
      workflow_stage(
        ProtocolAnalysis,
        StageSkipped,
        0,
        "no protocol channels configured",
      ),
    )
  }
  stages.push(
    workflow_stage(
      ReportAssembly,
      StagePassed,
      stages.length(),
      "workflow report assembled",
    ),
  )
  {
    trace,
    options,
    stages,
    index,
    windows,
    coverage,
    assertions,
    protocol_stats: protocol_results,
    passed,
  }
}

///|
/// Resolve a signal identifier to its declared path.
fn workflow_signal_path(trace : Trace, signal_id : String) -> String? {
  for signal in trace.signals {
    if signal.id == signal_id {
      return Some(signal_path(signal))
    }
  }
  None
}

///|
/// Return whether every required workflow stage passed.
pub fn AnalysisWorkflow::is_success(self : AnalysisWorkflow) -> Bool {
  self.passed
}

///|
/// Return a stage result by kind.
pub fn AnalysisWorkflow::stage(
  self : AnalysisWorkflow,
  target : WorkflowStage,
) -> WorkflowStageResult? {
  for stage in self.stages {
    if stage.stage == target {
      return Some(stage)
    }
  }
  None
}

///|
/// Return the number of passed stages.
pub fn AnalysisWorkflow::passed_stage_count(self : AnalysisWorkflow) -> Int {
  self.stages.filter(fn(item) { item.status is StagePassed }).length()
}

///|
/// Render stage timings and statuses as CSV.
pub fn workflow_stages_csv(stages : Array[WorkflowStageResult]) -> String {
  let out = StringBuilder()
  out.write_string("stage,status,duration,rows,message\n")
  for stage in stages {
    out.write_string(
      workflow_stage_name(stage.stage) +
      "," +
      workflow_status_name(stage.status) +
      "," +
      stage.duration_units.to_string() +
      "," +
      stage.rows.to_string() +
      "," +
      escape_text(stage.message) +
      "\n",
    )
  }
  out.to_string()
}

///|
/// Render a workflow summary as JSON.
pub fn AnalysisWorkflow::to_json(self : AnalysisWorkflow) -> String {
  let stages = self.stages.map(workflow_stage_json).join(",")
  "{\"passed\":" +
  self.passed.to_string() +
  ",\"passed_stages\":" +
  self.passed_stage_count().to_string() +
  ",\"stages\":[" +
  stages +
  "]}"
}

///|
/// Render a workflow summary for a terminal.
pub fn AnalysisWorkflow::to_text(self : AnalysisWorkflow) -> String {
  "passed=" +
  self.passed.to_string() +
  " stages=" +
  self.passed_stage_count().to_string() +
  "/" +
  self.stages.length().to_string() +
  " windows=" +
  self.windows.length().to_string() +
  " protocols=" +
  self.protocol_stats.length().to_string()
}

///|
/// Build one stage result; duration is deterministic for reproducible reports.
fn workflow_stage(
  stage : WorkflowStage,
  status : WorkflowStageStatus,
  rows : Int,
  message : String,
) -> WorkflowStageResult {
  { stage, status, duration_units: rows.to_int64(), rows, message }
}

///|
/// Name a workflow stage.
pub fn workflow_stage_name(stage : WorkflowStage) -> String {
  match stage {
    InputValidation => "input-validation"
    IndexBuild => "index-build"
    WindowSegmentation => "window-segmentation"
    CoverageAnalysis => "coverage-analysis"
    AssertionAnalysis => "assertion-analysis"
    ProtocolAnalysis => "protocol-analysis"
    ReportAssembly => "report-assembly"
  }
}

///|
/// Name a workflow status.
pub fn workflow_status_name(status : WorkflowStageStatus) -> String {
  match status {
    StagePending => "pending"
    StageRunning => "running"
    StagePassed => "passed"
    StageSkipped => "skipped"
    StageFailed => "failed"
  }
}

///|
/// Encode a workflow stage as JSON.
fn workflow_stage_json(item : WorkflowStageResult) -> String {
  "{\"stage\":\"" +
  workflow_stage_name(item.stage) +
  "\",\"status\":\"" +
  workflow_status_name(item.status) +
  "\",\"duration\":" +
  item.duration_units.to_string() +
  ",\"rows\":" +
  item.rows.to_string() +
  ",\"message\":\"" +
  escape_text(item.message) +
  "\"}"
}