///|
/// A read-only explanation of the dependency closure of one target.
pub(all) struct TargetInspection {
  target : String
  producer_key : String?
  direct_inputs : Array[String]
  transitive_inputs : Array[String]
  outputs : Array[String]
  leaf_inputs : Array[String]
  edge_count : Int
  depth : Int
} derive(Debug, Eq)

///|
pub fn TargetInspection::to_text(self : TargetInspection) -> String {
  let producer = match self.producer_key {
    Some(value) => value
    None => "source"
  }
  "target=" +
  self.target +
  " producer=" +
  producer +
  " edges=" +
  self.edge_count.to_string() +
  " depth=" +
  self.depth.to_string() +
  " direct_inputs=" +
  self.direct_inputs.length().to_string() +
  " transitive_inputs=" +
  self.transitive_inputs.length().to_string() +
  " leaves=" +
  self.leaf_inputs.length().to_string()
}

///|
fn inspection_unique_inputs(edges : Array[BuildEdge]) -> Array[String] {
  let inputs : Array[String] = []
  for edge in edges {
    for input in edge.inputs {
      if !inputs.contains(input) {
        inputs.push(input)
      }
    }
  }
  inputs
}

///|
fn inspection_unique_outputs(edges : Array[BuildEdge]) -> Array[String] {
  let outputs : Array[String] = []
  for edge in edges {
    for output in edge.outputs {
      if !outputs.contains(output) {
        outputs.push(output)
      }
    }
  }
  outputs
}

///|
fn inspection_leaves(edges : Array[BuildEdge]) -> Array[String] {
  let produced : Map[String, Bool] = Map([])
  for edge in edges {
    for output in edge.outputs {
      produced[output] = true
    }
  }
  let leaves : Array[String] = []
  for input in inspection_unique_inputs(edges) {
    if !produced.contains(input) {
      leaves.push(input)
    }
  }
  leaves
}

///|
/// Inspect a target without executing it. This is useful for CLI explain
/// commands, acceptance reports, and host-side scheduling decisions.
pub fn Manifest::inspect_target(
  self : Manifest,
  target : String,
) -> Result[TargetInspection, String] {
  let graph = DepGraph::build(self)
  let edges = match graph.traverse(target) {
    Err(error) => return Err(error)
    Ok(value) => value
  }
  if edges.is_empty() && !graph.producer.contains(target) {
    return Err("target is not produced: " + target)
  }
  let direct_inputs : Array[String] = match graph.producer.get(target) {
    Some(edge) => edge.inputs.copy()
    None => []
  }
  let producer_key : String? = match graph.producer.get(target) {
    Some(edge) => Some(edge.key())
    None => None
  }
  let outputs = inspection_unique_outputs(edges)
  let inputs = inspection_unique_inputs(edges)
  let leaves = inspection_leaves(edges)
  let depth = match graph.parallel_waves(target) {
    Ok(waves) => waves.length()
    Err(_) => 0
  }
  Ok({
    target,
    producer_key,
    direct_inputs,
    transitive_inputs: inputs,
    outputs,
    leaf_inputs: leaves,
    edge_count: edges.length(),
    depth,
  })
}