///|
fn is_dnssec_companion(record_type : String) -> Bool {
  record_type == "RRSIG" || record_type == "NSEC" || record_type == "NSEC3"
}

///|
fn data_key(record : ResourceRecord) -> String {
  let builder = StringBuilder()
  builder.write_string(record.owner)
  builder.write_char('|')
  builder.write_string(record.class_name)
  builder.write_char('|')
  builder.write_string(record.record_type)
  for index in 0.. String {
  record.owner + "|" + record.class_name + "|" + record.record_type
}

///|
fn add_record_note(
  diagnostics : Array[Diagnostic],
  code : String,
  severity : String,
  message : String,
  record : ResourceRecord,
) -> Unit {
  diagnostics.push(
    diagnostic(
      code,
      severity,
      message,
      record.line,
      record.column,
      owner=record.owner,
    ),
  )
}

///|
fn check_zone_structure(zone : Zone, diagnostics : Array[Diagnostic]) -> Unit {
  let mut soa_count = 0
  let mut ns_count = 0
  for record in zone.records {
    if !valid_domain_name(record.owner) {
      add_record_note(
        diagnostics, "Z200", "error", "invalid owner name", record,
      )
    }
    if !is_within_zone(record.owner, zone.origin) {
      add_record_note(
        diagnostics, "Z201", "error", "owner lies outside the declared zone", record,
      )
    }
    if record.class_name != "IN" {
      add_record_note(
        diagnostics, "Z202", "warning", "class is outside the Internet zone profile",
        record,
      )
    }
    if record.record_type == "SOA" {
      if record.owner != zone.origin {
        add_record_note(
          diagnostics, "Z203", "error", "SOA must be at the zone apex", record,
        )
      } else {
        soa_count += 1
      }
    }
    if record.record_type == "NS" && record.owner == zone.origin {
      ns_count += 1
    }
  }
  if soa_count == 0 {
    diagnostics.push(
      diagnostic(
        "Z204",
        "error",
        "zone apex has no SOA record",
        1,
        1,
        owner=zone.origin,
      ),
    )
  } else if soa_count > 1 {
    diagnostics.push(
      diagnostic(
        "Z205",
        "error",
        "zone apex has more than one SOA record",
        1,
        1,
        owner=zone.origin,
      ),
    )
  }
  if ns_count == 0 {
    diagnostics.push(
      diagnostic(
        "Z206",
        "error",
        "zone apex has no NS record",
        1,
        1,
        owner=zone.origin,
      ),
    )
  }
}

///|
fn check_duplicate_and_ttl(
  zone : Zone,
  diagnostics : Array[Diagnostic],
) -> Unit {
  let seen : Map[String, Bool] = Map([])
  let rrset_ttl : Map[String, Int] = Map([])
  for record in zone.records {
    let key = data_key(record)
    if seen.get(key) is Some(_) {
      add_record_note(
        diagnostics, "Z210", "warning", "duplicate resource record", record,
      )
    } else {
      seen.set(key, true)
    }
    let group_key = rrset_key(record)
    match rrset_ttl.get(group_key) {
      Some(previous) =>
        if previous != record.ttl {
          add_record_note(
            diagnostics, "Z211", "error", "records in one RRset have different TTL values",
            record,
          )
        }
      None => rrset_ttl.set(group_key, record.ttl)
    }
  }
}

///|
fn collect_aliases(zone : Zone) -> Map[String, String] {
  let aliases : Map[String, String] = Map([])
  for record in zone.records {
    if record.record_type == "CNAME" && record.rdata.length() == 1 {
      aliases.set(record.owner, absolute_rdata_name(record, 0, zone.origin))
    }
  }
  aliases
}

///|
fn check_alias_coexistence(
  zone : Zone,
  diagnostics : Array[Diagnostic],
) -> Unit {
  let aliases = collect_aliases(zone)
  for record in zone.records {
    if record.record_type != "CNAME" && !is_dnssec_companion(record.record_type) {
      if aliases.get(record.owner) is Some(_) {
        add_record_note(
          diagnostics, "Z220", "error", "CNAME owner also has other record data",
          record,
        )
      }
    }
    if record.record_type == "NS" && record.rdata.length() == 1 {
      let target = absolute_rdata_name(record, 0, zone.origin)
      if aliases.get(target) is Some(_) {
        add_record_note(
          diagnostics, "Z221", "warning", "NS target is a CNAME alias", record,
        )
      }
    }
    if record.record_type == "MX" && record.rdata.length() == 2 {
      let target = absolute_rdata_name(record, 1, zone.origin)
      if aliases.get(target) is Some(_) {
        add_record_note(
          diagnostics, "Z222", "warning", "MX target is a CNAME alias", record,
        )
      }
    }
  }
}

///|
fn check_alias_cycles(zone : Zone, diagnostics : Array[Diagnostic]) -> Unit {
  let aliases = collect_aliases(zone)
  let reported : Map[String, Bool] = Map([])
  for record in zone.records {
    if record.record_type != "CNAME" || record.rdata.length() != 1 {
      continue
    }
    let start = record.owner
    if reported.get(start) is Some(_) {
      continue
    }
    let mut current = start
    let visited : Map[String, Bool] = Map([])
    let mut steps = 0
    while steps <= zone.records.length() {
      if visited.get(current) is Some(_) {
        if current == start {
          add_record_note(
            diagnostics, "Z223", "error", "CNAME cycle returns to this owner", record,
          )
          reported.set(start, true)
        }
        break
      }
      visited.set(current, true)
      match aliases.get(current) {
        Some(next) => current = next
        None => break
      }
      steps += 1
    }
  }
}

///|
fn check_record_shapes(zone : Zone, diagnostics : Array[Diagnostic]) -> Unit {
  for record in zone.records {
    check_record_data(record, zone.origin, diagnostics)
  }
}

///|
/// Analyze syntax, record content, and cross-record DNS invariants.
pub fn validate_zone(zone : Zone) -> Array[Diagnostic] {
  let diagnostics : Array[Diagnostic] = []
  for item in zone.diagnostics {
    diagnostics.push(item)
  }
  check_zone_structure(zone, diagnostics)
  check_record_shapes(zone, diagnostics)
  check_duplicate_and_ttl(zone, diagnostics)
  check_alias_coexistence(zone, diagnostics)
  check_alias_cycles(zone, diagnostics)
  check_zone_integrity(zone, diagnostics)
  check_mail_security(zone, diagnostics)
  diagnostics
}

///|
/// Parse and validate a master file in one call.
pub fn inspect_zone(input : String, origin : String) -> Array[Diagnostic] {
  validate_zone(parse_zone(input, origin))
}