///|
pub struct Stage {
  index : Int
  line : Int
  base : String
  stage_name : String?
  dependencies : Array[Int]
} derive(Debug, Eq, ToJson)

///|
/// Resolve stage dependencies without fetching images. Unknown COPY names may be external images.
pub fn analyze_stages(
  instructions : Array[Instruction],
) -> Array[Stage] raise ParseError {
  let stages : Array[Stage] = []
  let aliases : Map[String, Int] = Map([])
  for instruction in instructions {
    if instruction.name != "FROM" {
      continue
    }
    if stages.length() == 256 {
      raise Invalid("stage count limit")
    }
    let args = instruction.split_arguments().values
    if args.length() != 1 && !(args.length() == 3 && args[1].to_upper() == "AS") {
      raise Invalid("malformed FROM")
    }
    let stage_name = if args.length() == 3 {
      Some(args[2].to_lower())
    } else {
      None
    }
    if stage_name is Some(name) {
      if aliases.contains(name) {
        raise Invalid("duplicate stage stage_name")
      }
      aliases[name] = stages.length()
    }
    stages.push({
      index: stages.length(),
      line: instruction.line,
      base: args[0],
      stage_name,
      dependencies: [],
    })
  }
  let (edges, _) = build_edges(instructions, stages)
  for edge in edges {
    if !stages[edge.consumer].dependencies.contains(edge.dependency) {
      stages[edge.consumer].dependencies.push(edge.dependency)
    }
  }
  validate_stage_graph(stages)
  stages
}

///|
pub fn Diagnostic::severity(self : Diagnostic) -> String {
  if self.code.has_prefix("ML") ||
    [
      "DL3000", "DL3004", "DL3011", "DL3012", "DL3020", "DL3021", "DL3023", "DL3024",
      "DL3026", "DL3043", "DL3044", "DL4000", "DL4004",
    ].contains(self.code) {
    "error"
  } else if [
      "DL3001", "DL3009", "DL3015", "DL3019", "DL3047", "DL3049", "DL3050", "DL3059",
      "DL3060", "DL3066", "SC2035",
    ].contains(self.code) {
    "info"
  } else if ["DL3048", "SC2005", "SC2006"].contains(self.code) {
    "style"
  } else {
    "warning"
  }
}