/**
 * Implements fast shallow verification of hostnames. This does not perform a
 * struct check on the content of labels (classes of Unicode characters, etc.)
 * but instead check that the structure is valid (number of labels, length of
 * labels, etc.).
 *
 * If you need stricter validation, consider using an external library.
 */

// KEEP IN SYNC with `extract-hostname.ts` `isValidHostnameChar` + its inline
// scan/verdict, which duplicate these structural rules to validate during
// extraction (a perf fusion). That copy additionally accepts A-Z (the host is
// not yet lowercased there) and folds in '-' / '_'. Any change to the accepted
// character set or the label/length rules here must be mirrored there.
function isValidAscii(code: number): boolean {
  return (
    (code >= 97 && code <= 122) || (code >= 48 && code <= 57) || code > 127
  );
}

/**
 * Check if a hostname string is valid. It's usually a preliminary check before
 * trying to use getDomain or anything else.
 *
 * Beware: it does not check if the TLD exists.
 */
export default function (hostname: string): boolean {
  if (hostname.length > 255) {
    return false;
  }

  if (hostname.length === 0) {
    return false;
  }

  if (
    /*@__INLINE__*/ !isValidAscii(hostname.charCodeAt(0)) &&
    hostname.charCodeAt(0) !== 46 && // '.' (dot)
    hostname.charCodeAt(0) !== 95 // '_' (underscore)
  ) {
    return false;
  }

  // Validate hostname according to RFC
  let lastDotIndex = -1;
  let lastCharCode = -1;
  const len = hostname.length;

  for (let i = 0; i < len; i += 1) {
    const code = hostname.charCodeAt(i);
    if (code === 46 /* '.' */) {
      if (
        // Check that previous label is < 63 bytes long (64 = 63 + '.')
        i - lastDotIndex > 64 ||
        // Check that previous character was not already a '.'
        lastCharCode === 46 ||
        // Check that the previous label does not end with '-' (RFC 1035 §2.3.1 LDH).
        // '_' is intentionally NOT restricted: DNS allows any octet (RFC 2181 §11) and
        // WHATWG URL does not treat '_' as a forbidden host code point.
        lastCharCode === 45
      ) {
        return false;
      }

      lastDotIndex = i;
    } else if (
      // A forbidden character in the label...
      !(/*@__INLINE__*/ (isValidAscii(code) || code === 45 || code === 95)) ||
      // ...or a '-' starting a label (the byte right after a '.'). A label must
      // not begin with a hyphen (RFC 1034 §3.5 / RFC 1035 §2.3.1 LDH, as amended
      // by RFC 1123 §2.1; cf. UTS #46 CheckHyphens). The first label is covered by
      // the leading-character guard above; mirrors the trailing-'-' rule below.
      (code === 45 && lastCharCode === 46)
    ) {
      return false;
    }

    lastCharCode = code;
  }

  return (
    // Check that last label is shorter than 63 chars
    len - lastDotIndex - 1 <= 63 &&
    // Check that the last character is an allowed trailing label character.
    // Since we already checked that the char is a valid hostname character,
    // we only need to check that it's different from '-'.
    lastCharCode !== 45
  );
}
