///|
/// Return a fixed stage order for dashboards.
pub fn pipeline_stage_order() -> Array[PipelineStage] {
  [SourceInventory, Parse, Validation, Index, Analysis, Export]
}

///|
/// Return the number of recorded stages.
pub fn pipeline_stage_count(pipeline : TracePipeline) -> Int {
  pipeline.results.length()
}

///|
/// Return the number of recorded items across successful stages.
pub fn pipeline_item_count(pipeline : TracePipeline) -> Int {
  pipeline.results.fold(init=0, fn(total, result) { total + result.items })
}

///|
/// Return a stage detail, if that stage was recorded.
pub fn pipeline_detail(
  pipeline : TracePipeline,
  stage : PipelineStage,
) -> String? {
  for result in pipeline.results {
    if result.stage == stage {
      return Some(result.detail)
    }
  }
  None
}

///|
/// Run the same pipeline over multiple independent inputs.
pub fn run_pipeline_batch(inputs : Array[String]) -> Array[TracePipeline] {
  inputs.map(run_pipeline)
}

///|
/// Count successful pipelines in a batch.
pub fn successful_pipeline_count(pipelines : Array[TracePipeline]) -> Int {
  pipelines.count_if(fn(pipeline) { pipeline.is_success() })
}

///|
/// Return a batch verdict with success and failure counts.
pub fn pipeline_batch_verdict(pipelines : Array[TracePipeline]) -> String {
  let passed = successful_pipeline_count(pipelines)
  "passed=" +
  passed.to_string() +
  " failed=" +
  (pipelines.length() - passed).to_string() +
  " total=" +
  pipelines.length().to_string()
}

///|
/// Render every pipeline verdict as newline-delimited text.
pub fn pipeline_batch_report(pipelines : Array[TracePipeline]) -> String {
  let out : Array[String] = []
  for index in 0.. Array[String] {
  pipelines.map(fn(pipeline) { source_statistics_line(pipeline.source) })
}

///|
/// Return whether every pipeline has the expected stage order.
pub fn pipeline_shapes_valid(pipelines : Array[TracePipeline]) -> Bool {
  for pipeline in pipelines {
    if pipeline.results.length() > pipeline_stage_order().length() {
      return false
    }
  }
  true
}

///|
/// Render a batch as a compact JSON object.
pub fn pipeline_batch_json(pipelines : Array[TracePipeline]) -> String {
  "{\"total\":" +
  pipelines.length().to_string() +
  ",\"passed\":" +
  successful_pipeline_count(pipelines).to_string() +
  ",\"pipelines\":[" +
  pipelines.map(fn(pipeline) { pipeline.to_json() }).join(",") +
  "]}"
}

///|
/// Return a stable source-size total for a batch.
pub fn pipeline_source_bytes(pipelines : Array[TracePipeline]) -> Int {
  pipelines.fold(init=0, fn(total, pipeline) { total + pipeline.source.bytes })
}