///|
/// Synthetic pipeline: all durations are minutes; each runner has one slot.
fn stage(
  id : String,
  duration : Int,
  runner : String,
  priority : Int,
  prerequisites : Array[String],
) -> @pipeline.PipelineStage {
  { id, duration, runner, priority, prerequisites, }
}

///|
fn prerequisites_text(prerequisites : Array[String]) -> String {
  if prerequisites.is_empty() {
    return "-"
  }
  let mut result = ""
  for index, prerequisite in prerequisites {
    if index > 0 {
      result = result + ","
    }
    result = result + prerequisite
  }
  result
}

///|
fn main raise {
  let stages = [
    stage("lint", 5, "linux-a", 1, []),
    stage("compile-api", 12, "linux-a", 3, []),
    stage("compile-web", 9, "linux-b", 2, []),
    stage("unit-api", 8, "linux-a", 2, ["compile-api"]),
    stage("unit-web", 7, "linux-b", 2, ["compile-web"]),
    stage("integration", 11, "linux-c", 3, ["unit-api", "unit-web"]),
    stage("package", 4, "linux-a", 1, ["integration", "lint"]),
  ]
  let analysis = @pipeline.analyze_pipeline(
    stages~,
    runners=["linux-a", "linux-b", "linux-c"],
    target_makespan=Some(40),
    search_budget=100,
  )
  println("Synthetic CI pipeline; duration unit: minutes; runner capacity: 1")
  println("stage | runner | duration | prerequisites")
  for stage in stages {
    println(
      "\{stage.id} | \{stage.runner} | \{stage.duration} | \{prerequisites_text(stage.prerequisites)}",
    )
  }
  println("algorithm | makespan")
  for baseline in analysis.baselines {
    println("\{baseline.explanation.algorithm} | \{baseline.makespan}")
  }
  println("budgeted-search | \{analysis.optimization.result.makespan}")
  println("optimized stage | runner | start | finish")
  for entry in analysis.optimization.result.schedule {
    println(
      "\{entry.task_id} | \{entry.resource_id} | \{entry.start} | \{entry.finish}",
    )
  }
  match analysis.bottleneck_runner {
    Some(runner) =>
      println(
        "highest runner load: \{runner} = \{analysis.bottleneck_load} minutes",
      )
    None => println("highest runner load: none")
  }
  println("lower bound: \{analysis.optimization.lower_bound} minutes")
  match analysis.target_met {
    Some(true) => println("target <= 40 minutes: met")
    Some(false) => println("target <= 40 minutes: missed")
    None => println("target <= 40 minutes: not set")
  }
}