// Copyright (c) 2026 colmugx
// SPDX-License-Identifier: Apache-2.0

///|
/// `hostname` (RFC 1123 preferred name syntax): ASCII only, and an A-label is
/// judged through its decoded Punycode form by the UTS #46 ToASCII pipeline
/// rather than on its LDH spelling alone. UTS #46 mapping is deliberately not
/// applied here: a non-ASCII spelling is `idn-hostname`'s job, so
/// `example\u{FF0E}com` fails instead of being mapped to `example.com`.
fn format_hostname(value : String) -> Bool {
  if !format_is_ascii(value) {
    return false
  }
  format_idna_domain(value)
}

///|
/// `idn-hostname`: the full UTS #46 ToASCII pipeline — mapping (the U+3002,
/// U+FF0E and U+FF61 label separators included), NFC, A-label decoding,
/// derived-property/CONTEXTJ/CONTEXTO validation, the RFC 5893 Bidi rule and the
/// DNS length bounds. A rejected domain is an assertion failure, never a silent
/// pass.
fn format_idn_hostname(value : String) -> Bool {
  format_idna_domain(value)
}

///|
/// UTS #46 ToASCII followed by IDNA2008 label validity on the decoded U-labels.
/// `@idna` owns mapping, Punycode, the hyphen rules, the Bidi rule, ContextJ and
/// the DNS length bounds; `format_idna2008_label` owns the RFC 5892 derived
/// property and the ContextO rules it does not implement.
fn format_idna_domain(value : String) -> Bool {
  let ascii = @idna.to_ascii(value) catch { _ => return false }
  let unicode = @idna.to_unicode(ascii) catch { _ => return false }
  for label in unicode.split(".") {
    if !format_idna2008_label(label) {
      return false
    }
  }
  true
}

///|
fn format_is_ascii(value : String) -> Bool {
  for ch in value[:] {
    if ch.to_int() >= 0x80 {
      return false
    }
  }
  true
}

///|
/// Dot-separated labels of the domain after `@` in a mailbox. This is the RFC
/// 5321 `Domain` grammar, not UTS #46: `idn-email` has to accept spellings that
/// IDNA processing would reject or normalize away, so the two contracts stay
/// apart.
fn format_domain_labels(
  chars : Array[Char],
  start : Int32,
  end : Int32,
  ascii_only~ : Bool,
) -> Bool {
  if end <= start {
    return false
  }
  let mut label_start = start
  let mut index = start
  while index <= end {
    if index == end || chars[index] == '.' {
      if !format_label(chars, label_start, index, ascii_only~) {
        return false
      }
      label_start = index + 1
    }
    index = index + 1
  }
  format_domain_length(chars, start, end)
}

///|
/// One label, in the character space the format asserts: ASCII LDH for the
/// `email` domain, the structural U-label rules for `idn-email`.
fn format_label(
  chars : Array[Char],
  start : Int32,
  end : Int32,
  ascii_only~ : Bool,
) -> Bool {
  if end <= start || chars[start] == '-' || chars[end - 1] == '-' {
    return false
  }
  if end - start > 63 {
    return false
  }
  for index in start..= 0x80 {
      continue
    }
    if !format_is_alnum(ch) && ch != '-' {
      return false
    }
  }
  true
}

///|
/// Overall length of a domain name: 253 octets, the RFC 1035 limit that leaves
/// room for the root label inside the 255-octet wire form. A U-label domain is
/// measured in octets of its spelling, not of its A-label expansion, because the
/// mailbox grammar performs no mapping.
fn format_domain_length(
  chars : Array[Char],
  start : Int32,
  end : Int32,
) -> Bool {
  format_octets(chars, start, end) <= 253
}