///|
pub(all) struct PlanAnalysis {
  target : String
  edge_count : Int
  wave_count : Int
  critical_path : Int
  max_parallelism : Int
  leaf_input_count : Int
  produced_output_count : Int
  leaf_inputs : Array[String]
} derive(Debug, Eq)

///|
pub fn PlanAnalysis::to_text(self : PlanAnalysis) -> String {
  "target=" +
  self.target +
  " edges=" +
  self.edge_count.to_string() +
  " waves=" +
  self.wave_count.to_string() +
  " critical_path=" +
  self.critical_path.to_string() +
  " max_parallelism=" +
  self.max_parallelism.to_string() +
  " leaves=" +
  self.leaf_input_count.to_string() +
  " outputs=" +
  self.produced_output_count.to_string()
}

///|
/// Analyze the executable shape of one target before choosing a host runner.
pub fn DepGraph::analyze(
  self : DepGraph,
  target : String,
) -> Result[PlanAnalysis, String] {
  let edges = match self.traverse(target) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let waves = match self.parallel_waves(target) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let produced : Map[String, Bool] = Map([])
  for edge in edges {
    for output in edge.outputs {
      produced[output] = true
    }
  }
  let leaves : Map[String, Bool] = Map([])
  for edge in edges {
    for input in edge.inputs {
      if !produced.contains(input) {
        leaves[input] = true
      }
    }
  }
  let leaf_inputs : Array[String] = []
  for input, _ in leaves {
    leaf_inputs.push(input)
  }
  let mut max_parallelism = 0
  for wave in waves {
    if wave.length() > max_parallelism {
      max_parallelism = wave.length()
    }
  }
  Ok({
    target,
    edge_count: edges.length(),
    wave_count: waves.length(),
    critical_path: waves.length(),
    max_parallelism,
    leaf_input_count: leaf_inputs.length(),
    produced_output_count: produced.length(),
    leaf_inputs,
  })
}

///|
pub fn Manifest::analyze(
  self : Manifest,
  target : String,
) -> Result[PlanAnalysis, String] {
  DepGraph::build(self).analyze(target)
}