/**
 * Matches an ASCII tab (U+0009) or newline (U+000A / U+000D). The WHATWG URL
 * parser strips these before parsing; we only allocate a cleaned copy (and
 * re-parse) on the rare input that actually contains one.
 */
const CONTROL_CHARS = /[\t\n\r]/g;
// Set by `extractHostname` (a module-scope flag, read synchronously by
// `parseImpl` right after the call — same pattern as the reused RESULT object).
// `true` ONLY when extraction validated the returned host inline (a confirmed-
// valid, "simple" authority) so `parseImpl` can skip the separate
// `isValidHostname` pass. `false` in every other case (validation disabled, a
// complex authority — userinfo/port/brackets/trailing-dot/control — an invalid
// host, or a non-main return path); `parseImpl` then validates as usual. The
// fast path can only ever SKIP a redundant scan for hosts already known valid,
// never accept an invalid one.
export let extractedHostnameValidated = false;
/**
 * True if char `code` is a valid hostname character. This is the per-char half
 * of `is-valid.ts`'s `isValidAscii` (a-z, 0-9, > U+007F) PLUS three additions:
 * A-Z (the host is lowercased before validation, so uppercase ≡ a valid
 * lowercase letter) and '-' / '_' (valid inside a label). KEEP IN SYNC with
 * `is-valid.ts`: these rules are deliberately duplicated to validate during
 * extraction, so any change to the accepted character set there must be
 * mirrored here (and vice-versa).
 */
function isValidHostnameChar(code) {
    return ((code >= 97 && code <= 122) || // a-z
        (code >= 48 && code <= 57) || // 0-9
        code > 127 || // non-ASCII (accepted, not punycode-checked)
        (code >= 65 && code <= 90) || // A-Z (becomes valid once lowercased)
        code === 45 || // '-'
        code === 95 // '_'
    );
}
/**
 * Classify scheme `url.slice(schemeStart, colonIndex)` as a WHATWG special
 * scheme without allocating a substring (case-insensitive via `| 32`).
 * Special schemes: ftp, file, http, https, ws, wss
 * (https://url.spec.whatwg.org/#special-scheme).
 *
 * @returns 0 = not special, 1 = special, 2 = file (its host sits only between
 *          "//" and the next slash).
 */
function getSpecialScheme(url, schemeStart, colonIndex) {
    const length = colonIndex - schemeStart;
    const c0 = url.charCodeAt(schemeStart) | 32;
    if (length === 2) {
        return c0 === 119 && (url.charCodeAt(schemeStart + 1) | 32) === 115 ? 1 : 0; // ws
    }
    else if (length === 3) {
        const c1 = url.charCodeAt(schemeStart + 1) | 32;
        const c2 = url.charCodeAt(schemeStart + 2) | 32;
        if (c0 === 119 && c1 === 115 && c2 === 115)
            return 1; // wss
        if (c0 === 102 && c1 === 116 && c2 === 112)
            return 1; // ftp
        return 0;
    }
    else if (length === 4) {
        const c1 = url.charCodeAt(schemeStart + 1) | 32;
        const c2 = url.charCodeAt(schemeStart + 2) | 32;
        const c3 = url.charCodeAt(schemeStart + 3) | 32;
        if (c0 === 104 && c1 === 116 && c2 === 116 && c3 === 112)
            return 1; // http
        if (c0 === 102 && c1 === 105 && c2 === 108 && c3 === 101)
            return 2; // file
        return 0;
    }
    else if (length === 5) {
        return c0 === 104 &&
            (url.charCodeAt(schemeStart + 1) | 32) === 116 &&
            (url.charCodeAt(schemeStart + 2) | 32) === 116 &&
            (url.charCodeAt(schemeStart + 3) | 32) === 112 &&
            (url.charCodeAt(schemeStart + 4) | 32) === 115
            ? 1
            : 0; // https
    }
    return 0;
}
/**
 * Extract a hostname from `url`, matching a WHATWG URL parser's host-boundary
 * behaviour (https://url.spec.whatwg.org/#concept-basic-url-parser) for tldts'
 * scope. It deliberately does NOT normalise the host (no IDNA/punycode or IPv4
 * canonicalisation; IPv6 brackets are stripped, not compressed), strips trailing
 * dots, and stays lenient where a strict parser rejects (bare host:port,
 * out-of-range port, user@host) — all documented deviations.
 *
 * @param urlIsValidHostname - when true, `url` is already a valid hostname and is
 *   returned by the same reference (factory.ts skips re-validation on that
 *   identity), keeping the common path allocation-free.
 * @param validate - when true, validate the host inline during the authority
 *   scan and publish the verdict via `extractedHostnameValidated` so `parseImpl`
 *   can skip the redundant `isValidHostname` pass for simple authorities.
 */
export default function extractHostname(url, urlIsValidHostname, validate = false) {
    let start = 0;
    let end = url.length;
    let hasUpper = false;
    let isSpecial = false;
    extractedHostnameValidated = false;
    if (!urlIsValidHostname) {
        // Data URLs never carry a host (and may be huge — short-circuit them).
        if (url.startsWith('data:')) {
            return null;
        }
        // WHATWG step 1: trim leading/trailing C0 control or space (<= U+0020).
        // Tab/newline elsewhere are handled lazily below.
        while (start < url.length && url.charCodeAt(start) <= 32) {
            start += 1;
        }
        while (end > start + 1 && url.charCodeAt(end - 1) <= 32) {
            end -= 1;
        }
        if (url.charCodeAt(start) === 47 /* '/' */ &&
            url.charCodeAt(start + 1) === 47 /* '/' */) {
            // Scheme-relative reference ("//host/path").
            start += 2;
        }
        else {
            const indexOfProtocol = url.indexOf(':/', start);
            if (indexOfProtocol !== -1) {
                // "scheme://…". Classify the scheme, then position `start` at the host.
                const special = getSpecialScheme(url, start, indexOfProtocol);
                if (special === 1) {
                    // Special scheme: skip the run of '/' and '\' after it
                    // (special-authority-(ignore-)slashes states; '\' acts as '/').
                    isSpecial = true;
                    start = indexOfProtocol + 2;
                    while (url.charCodeAt(start) === 47 /* '/' */ ||
                        url.charCodeAt(start) === 92 /* '\' */) {
                        start += 1;
                    }
                }
                else if (special === 2) {
                    // file: the host is only what sits between "//" and the next slash, so
                    // "file://h/x" => "h" but "file:///x" / "file:/x" => no host.
                    isSpecial = true;
                    start = indexOfProtocol + 1;
                    let slashes = 0;
                    while ((url.charCodeAt(start) === 47 || url.charCodeAt(start) === 92) &&
                        slashes < 2) {
                        start += 1;
                        slashes += 1;
                    }
                    if (slashes < 2) {
                        return null;
                    }
                }
                else {
                    // Unknown scheme: validate the WHATWG scheme grammar [A-Za-z0-9+.-];
                    // a control char means it was split by a tab/newline (strip + re-parse).
                    for (let i = start; i < indexOfProtocol; i += 1) {
                        const code = url.charCodeAt(i) | 32;
                        if (!(((code >= 97 && code <= 122) || // [a, z]
                            (code >= 48 && code <= 57) || // [0, 9]
                            code === 46 || // '.'
                            code === 45 || // '-'
                            code === 43) // '+'
                        )) {
                            const raw = url.charCodeAt(i);
                            if (raw === 9 || raw === 10 || raw === 13) {
                                return extractHostname(url.replace(CONTROL_CHARS, ''), urlIsValidHostname, validate);
                            }
                            return null;
                        }
                    }
                    // A non-special scheme has an authority only after "//" (else it is an
                    // opaque path with no host). `indexOf(':/')` already gave the first '/'.
                    if (url.charCodeAt(indexOfProtocol + 2) === 47 /* '/' */) {
                        start = indexOfProtocol + 3;
                    }
                    else {
                        return null;
                    }
                }
            }
            else if (url.charCodeAt(start) !== 91 /* '[' */) {
                // Cold path: no scheme "://", and not a bare IPv6 literal (whose first
                // ':' would otherwise look like a scheme separator; "[…]" falls through
                // to the ipv6 handling below). May be a bare host, a host:port, a
                // user@host, a slash-less special scheme ("https:host"), or an opaque
                // URI ("mailto:", "tel:", "urn:…").
                let indexOfColon = -1;
                for (let i = start; i < end; i += 1) {
                    const code = url.charCodeAt(i);
                    if (code === 9 || code === 10 || code === 13) {
                        return extractHostname(url.replace(CONTROL_CHARS, ''), urlIsValidHostname, validate);
                    }
                    if (code === 58 /* ':' */) {
                        indexOfColon = i;
                        break;
                    }
                    if (code === 47 || code === 92 || code === 63 || code === 35) {
                        break;
                    }
                }
                if (indexOfColon !== -1) {
                    // An '@' before the next delimiter => the ':' is userinfo, not a
                    // scheme ("user:pass@host", "mailto:a@b"): keep the whole authority.
                    let hasIdentifier = false;
                    for (let i = indexOfColon + 1; i < end; i += 1) {
                        const code = url.charCodeAt(i);
                        if (code === 47 || code === 92 || code === 63 || code === 35) {
                            break;
                        }
                        if (code === 64 /* '@' */) {
                            hasIdentifier = true;
                            break;
                        }
                    }
                    if (!hasIdentifier) {
                        // All-digits after ':' => a bare "host:port" (tldts accepts
                        // hostnames too); keep `start` and let the port handling trim it.
                        let allDigits = true;
                        let i = indexOfColon + 1;
                        for (; i < end; i += 1) {
                            const code = url.charCodeAt(i);
                            if (code === 47 || code === 92 || code === 63 || code === 35) {
                                break;
                            }
                            if (code < 48 /* '0' */ || code > 57 /* '9' */) {
                                allDigits = false;
                                break;
                            }
                        }
                        if (i === indexOfColon + 1) {
                            allDigits = false; // nothing after ':' => not a port
                        }
                        if (!allDigits) {
                            const special = getSpecialScheme(url, start, indexOfColon);
                            if (special === 0) {
                                // No "://" anywhere on the cold path and not a special scheme.
                                // A second ':' before the host's end marks a bare, unbracketed
                                // IPv6 literal ("2a01:e35::1"): fall through and let the host
                                // loop + isIp classify it. Without one this is an opaque path
                                // with no host ("mailto:x", "foo:bar").
                                let isBareIpv6 = false;
                                for (let j = indexOfColon + 1; j < end; j += 1) {
                                    const code = url.charCodeAt(j);
                                    if (code === 47 ||
                                        code === 92 ||
                                        code === 63 ||
                                        code === 35) {
                                        break;
                                    }
                                    if (code === 58 /* ':' */) {
                                        isBareIpv6 = true;
                                        break;
                                    }
                                }
                                if (!isBareIpv6) {
                                    return null;
                                }
                            }
                            else {
                                isSpecial = true;
                                start = indexOfColon + 1;
                                if (special === 2) {
                                    // file (e.g. "file:\\host"): host only between "//" and next slash.
                                    let slashes = 0;
                                    while ((url.charCodeAt(start) === 47 ||
                                        url.charCodeAt(start) === 92) &&
                                        slashes < 2) {
                                        start += 1;
                                        slashes += 1;
                                    }
                                    if (slashes < 2) {
                                        return null;
                                    }
                                }
                                else {
                                    while (url.charCodeAt(start) === 47 ||
                                        url.charCodeAt(start) === 92) {
                                        start += 1;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        // Find the host's end: first '/', '?' or '#' (and '\' for special URLs,
        // which WHATWG treats like '/'). Track the last '@', ']' and ':' for
        // userinfo, ipv6 and port, plus the first ':' of the host (reset at each
        // '@') to tell a bare IPv6 (>= 2 colons) from a host:port (exactly one);
        // flag uppercase and a stray tab/newline. The loop is split on `code < 64`
        // so common host characters take fewer comparisons.
        //
        // When `validate`, also accumulate `is-valid.ts`'s checks over the scanned
        // run so a simple authority's host can be validated in this single pass.
        // `vValid` only stays meaningful for a "simple" authority (no userinfo, port,
        // brackets, control or trailing dot); those cases clear it / are rejected by
        // the guard below, falling back to `isValidHostname`.
        let indexOfIdentifier = -1;
        let indexOfClosingBracket = -1;
        let indexOfPort = -1;
        let indexOfFirstColon = -1;
        let hasControl = false;
        let vValid = validate; // seeded true when validating; cleared on the first invalid char
        let vLastDot = start - 1; // mirrors is-valid.ts `lastDotIndex = -1` at host start
        let vLastCode = -1;
        if (validate && start < end) {
            // First-char rule: must be a valid host char, '.', or '_' (NOT '-').
            const c0 = url.charCodeAt(start);
            if (!(
            /*@__INLINE__*/ (isValidHostnameChar(c0) ||
                c0 === 46 /* '.' */ ||
                c0 === 95 /* '_' */)) ||
                c0 === 45 /* '-' (isValidHostnameChar allows it mid-label, not first) */) {
                vValid = false;
            }
        }
        for (let i = start; i < end; i += 1) {
            const code = url.charCodeAt(i);
            if (code < 64) {
                if (code === 47 || code === 35 || code === 63) {
                    end = i;
                    break;
                }
                else if (code === 58 /* ':' */) {
                    if (indexOfFirstColon === -1) {
                        indexOfFirstColon = i;
                    }
                    indexOfPort = i;
                }
                else if (code === 9 || code === 10 || code === 13) {
                    hasControl = true;
                }
                else if (validate) {
                    if (code === 46 /* '.' */) {
                        if (i - vLastDot > 64 || vLastCode === 46 || vLastCode === 45) {
                            vValid = false;
                        }
                        vLastDot = i;
                    }
                    else if (code < 48 || code > 57) {
                        // < 64 and not a delimiter/dot/digit => only '-' (45) is a valid
                        // host char here; everything else (space, %, !, etc.) is invalid.
                        // A '-' must also not START a label (the byte right after a '.') —
                        // mirrors is-valid.ts; the first label is covered by the first-char
                        // rule above. (RFC 1034 §3.5 / RFC 1035 §2.3.1 LDH.)
                        if (code !== 45 || vLastCode === 46 /* label-leading '-' */) {
                            vValid = false;
                        }
                    }
                }
            }
            else if (isSpecial && code === 92 /* '\' */) {
                end = i;
                break;
            }
            else if (code === 64 /* '@' */) {
                indexOfIdentifier = i;
                indexOfFirstColon = -1; // colons before '@' are userinfo, not the host
            }
            else if (code === 93 /* ']' */) {
                indexOfClosingBracket = i;
            }
            else if (code >= 65 && code <= 90) {
                hasUpper = true;
            }
            else if (validate && !( /*@__INLINE__*/isValidHostnameChar(code))) {
                // >= 64, not '@'/']'/upper: valid only if a-z, '_', or non-ASCII.
                vValid = false;
            }
            if (validate) {
                vLastCode = code;
            }
        }
        // A tab/newline inside the authority: strip everything and re-parse (rare).
        if (hasControl) {
            return extractHostname(url.replace(CONTROL_CHARS, ''), urlIsValidHostname, validate);
        }
        // Skip userinfo. '>= start' so an empty userinfo ("http://@host") works too.
        if (indexOfIdentifier !== -1 &&
            indexOfIdentifier >= start &&
            indexOfIdentifier < end) {
            start = indexOfIdentifier + 1;
        }
        if (url.charCodeAt(start) === 91 /* '[' */) {
            // ipv6 address: return what is between the brackets, or null if unclosed.
            if (indexOfClosingBracket !== -1) {
                return url.slice(start + 1, indexOfClosingBracket).toLowerCase();
            }
            return null;
        }
        else if (indexOfPort !== -1 &&
            indexOfPort > start &&
            indexOfPort < end &&
            // A host:port has exactly one ':' in the host (so its first ':' is its
            // last); a bare, unbracketed IPv6 literal ("2a01:e35::1") has >= 2, so
            // its first ':' precedes the last. Only the former has a ':port' to trim.
            indexOfFirstColon === indexOfPort) {
            end = indexOfPort; // trim ':port'
        }
        // Empty authority ("http://", "file:///path", "//"); only reachable here via
        // extraction — a bare valid hostname never lands here.
        if (start >= end) {
            return null;
        }
        // Publish the inline-validation verdict — but only for a "simple" authority,
        // where the scanned run equals the final host: no userinfo skip, no port
        // trim, no brackets, no trailing dot (trimmed below), and length within RFC
        // limits. Anything else leaves it `false` so `parseImpl` re-validates.
        //
        // Every clause below is load-bearing for CORRECTNESS, not just speed: the
        // loop accumulates `vValid` over the whole scanned run (it does not stop at
        // ':' or '@', so any port/userinfo bytes are included), so the verdict is
        // only sound when that run equals the final host. Do not drop a clause as
        // "redundant" — e.g. without `indexOfPort === -1`, `host:8080` would be
        // wrongly accepted.
        if (validate &&
            vValid &&
            indexOfIdentifier === -1 &&
            indexOfPort === -1 &&
            indexOfClosingBracket === -1 &&
            url.charCodeAt(end - 1) !== 46 /* no trailing dot */ &&
            end - start <= 255 && // total length
            end - vLastDot - 1 <= 63 && // last label length
            vLastCode !== 45 /* last char not '-' */) {
            extractedHostnameValidated = true;
        }
    }
    // Trim trailing dots
    while (end > start + 1 && url.charCodeAt(end - 1) === 46 /* '.' */) {
        end -= 1;
    }
    const hostname = start !== 0 || end !== url.length ? url.slice(start, end) : url;
    if (hasUpper) {
        return hostname.toLowerCase();
    }
    return hostname;
}
//# sourceMappingURL=extract-hostname.js.map