///|
/// Structural metrics for a parsed nginx configuration.
pub(all) struct ConfigStats {
  mut directives : Int
  mut blocks : Int
  mut simple : Int
  mut comments : Int
  mut max_depth : Int
  mut servers : Int
  mut locations : Int
  mut upstreams : Int
  mut includes : Int
  unknown : Array[String]
} derive(Eq, Debug)

///|
fn empty_stats() -> ConfigStats {
  {
    directives: 0,
    blocks: 0,
    simple: 0,
    comments: 0,
    max_depth: 0,
    servers: 0,
    locations: 0,
    upstreams: 0,
    includes: 0,
    unknown: [],
  }
}

///|
fn push_unique_name(names : Array[String], name : String) -> Unit {
  let mut i = 0
  while i < names.length() {
    if names[i] == name {
      return
    }
    i = i + 1
  }
  names.push(name)
}

///|
fn merge_unknown_names(into : Array[String], from : Array[String]) -> Unit {
  let mut i = 0
  while i < from.length() {
    push_unique_name(into, from[i])
    i = i + 1
  }
}

///|
fn max_int(a : Int, b : Int) -> Int {
  if a > b {
    a
  } else {
    b
  }
}

///|
fn analyze_list(xs : Array[Directive], depth : Int) -> ConfigStats {
  let out = empty_stats()
  let mut i = 0
  while i < xs.length() {
    let d = xs[i]
    if d.name == "#" {
      out.comments = out.comments + 1
    } else {
      out.directives = out.directives + 1
      out.max_depth = max_int(out.max_depth, depth)
      if d.has_block {
        out.blocks = out.blocks + 1
      } else {
        out.simple = out.simple + 1
      }
      if d.name == "server" {
        out.servers = out.servers + 1
      } else if d.name == "location" {
        out.locations = out.locations + 1
      } else if d.name == "upstream" {
        out.upstreams = out.upstreams + 1
      } else if d.name == "include" {
        out.includes = out.includes + 1
      }
      if !known_core_directive(d.name) {
        push_unique_name(out.unknown, d.name)
      }
      if d.has_block {
        let child = analyze_list(d.children, depth + 1)
        out.directives = out.directives + child.directives
        out.blocks = out.blocks + child.blocks
        out.simple = out.simple + child.simple
        out.comments = out.comments + child.comments
        out.max_depth = max_int(out.max_depth, child.max_depth)
        out.servers = out.servers + child.servers
        out.locations = out.locations + child.locations
        out.upstreams = out.upstreams + child.upstreams
        out.includes = out.includes + child.includes
        merge_unknown_names(out.unknown, child.unknown)
      }
    }
    i = i + 1
  }
  out
}

///|
/// Analyze the directive tree without executing nginx or resolving includes.
pub fn analyze_config(cfg : Config) -> ConfigStats {
  analyze_list(cfg.parsed, 1)
}

///|
/// Return unique directive names that are outside the built-in core profile.
pub fn unknown_directives(cfg : Config) -> Array[String] {
  analyze_config(cfg).unknown
}

///|
/// True when every non-comment directive belongs to the built-in core profile.
pub fn uses_known_directives_only(cfg : Config) -> Bool {
  unknown_directives(cfg).length() == 0
}

///|
/// Compact deterministic summary suitable for command-line diagnostics.
pub fn config_summary(cfg : Config) -> String {
  let s = analyze_config(cfg)
  "directives=" +
  s.directives.to_string() +
  ", blocks=" +
  s.blocks.to_string() +
  ", simple=" +
  s.simple.to_string() +
  ", comments=" +
  s.comments.to_string() +
  ", max_depth=" +
  s.max_depth.to_string() +
  ", servers=" +
  s.servers.to_string() +
  ", locations=" +
  s.locations.to_string() +
  ", upstreams=" +
  s.upstreams.to_string() +
  ", includes=" +
  s.includes.to_string() +
  ", unknown=" +
  s.unknown.length().to_string()
}