///|
pub(all) struct TypeCount {
  record_type : String
  count : Int
} derive(Eq, Debug)

///|
pub(all) struct ZoneInventory {
  origin : String
  record_count : Int
  owner_count : Int
  address_count : Int
  alias_count : Int
  min_ttl : Int?
  max_ttl : Int?
  type_counts : Array[TypeCount]
} derive(Debug)

///|
fn count_type(type_counts : Array[TypeCount], kind : String) -> Unit {
  for index in 0.. ZoneInventory {
  let owners : Map[String, Bool] = Map([])
  let type_counts : Array[TypeCount] = []
  let mut address_count = 0
  let mut alias_count = 0
  let mut min_ttl : Int? = None
  let mut max_ttl : Int? = None
  for record in zone.records {
    owners.set(record.owner, true)
    count_type(type_counts, record.record_type)
    if record.record_type == "A" || record.record_type == "AAAA" {
      address_count += 1
    }
    if record.record_type == "CNAME" {
      alias_count += 1
    }
    match min_ttl {
      Some(previous) => if record.ttl < previous { min_ttl = Some(record.ttl) }
      None => min_ttl = Some(record.ttl)
    }
    match max_ttl {
      Some(previous) => if record.ttl > previous { max_ttl = Some(record.ttl) }
      None => max_ttl = Some(record.ttl)
    }
  }
  {
    origin: zone.origin,
    record_count: zone.records.length(),
    owner_count: owners.length(),
    address_count,
    alias_count,
    min_ttl,
    max_ttl,
    type_counts,
  }
}

///|
fn optional_int_text(value : Int?) -> String {
  match value {
    Some(number) => number.to_string()
    None => "none"
  }
}

///|
pub fn render_inventory_text(inventory : ZoneInventory) -> String {
  let out = StringBuilder()
  out.write_string("origin: \{inventory.origin}\n")
  out.write_string("records: \{inventory.record_count}\n")
  out.write_string("owners: \{inventory.owner_count}\n")
  out.write_string("addresses: \{inventory.address_count}\n")
  out.write_string("aliases: \{inventory.alias_count}\n")
  out.write_string(
    "ttl range: \{optional_int_text(inventory.min_ttl)}..\{optional_int_text(inventory.max_ttl)}\n",
  )
  out.write_string("types:\n")
  for item in inventory.type_counts {
    out.write_string("  \{item.record_type}: \{item.count}\n")
  }
  out.to_string()
}

///|
pub fn render_inventory_json(inventory : ZoneInventory) -> String {
  let out = StringBuilder()
  out.write_string("{\"origin\":")
  out.write_string(json_escaped(inventory.origin))
  out.write_string(",\"records\":\{inventory.record_count}")
  out.write_string(",\"owners\":\{inventory.owner_count}")
  out.write_string(",\"addresses\":\{inventory.address_count}")
  out.write_string(",\"aliases\":\{inventory.alias_count}")
  out.write_string(",\"min_ttl\":")
  match inventory.min_ttl {
    Some(n) => out.write_string(n.to_string())
    None => out.write_string("null")
  }
  out.write_string(",\"max_ttl\":")
  match inventory.max_ttl {
    Some(n) => out.write_string(n.to_string())
    None => out.write_string("null")
  }
  out.write_string(",\"types\":[")
  for i in 0.. 0 {
      out.write_char(',')
    }
    let item = inventory.type_counts[i]
    out.write_string("{\"type\":")
    out.write_string(json_escaped(item.record_type))
    out.write_string(",\"count\":\{item.count}}")
  }
  out.write_string("]}")
  out.to_string()
}

///|
/// Get all exact owner/type matches in source order. `*` means every type.
pub fn find_records(
  zone : Zone,
  owner : String,
  record_type : String,
) -> Array[ResourceRecord] {
  let result : Array[ResourceRecord] = []
  let name = absolute_name(owner, zone.origin)
  let kind = upper(record_type)
  for record in zone.records {
    if record.owner == name && (kind == "*" || record.record_type == kind) {
      result.push(record)
    }
  }
  result
}

///|
pub(all) struct AliasTrace {
  path : Array[String]
  terminal : String
  cycle : Bool
} derive(Debug)

///|
/// Walk in-zone CNAME links. A repeated name ends the trace with `cycle=true`.
pub fn trace_alias(zone : Zone, owner : String) -> AliasTrace {
  let aliases = collect_aliases(zone)
  let visited : Map[String, Bool] = Map([])
  let path : Array[String] = []
  let mut current = absolute_name(owner, zone.origin)
  for _ in 0..<(zone.records.length() + 1) {
    if visited.get(current) is Some(_) {
      return { path, terminal: current, cycle: true, }
    }
    visited.set(current, true)
    path.push(current)
    match aliases.get(current) {
      Some(next) => current = next
      None => return { path, terminal: current, cycle: false, }
    }
  }
  { path, terminal: current, cycle: true, }
}