import process from 'node:process';
import { promisify, inspect, isDeepStrictEqual, } from 'node:util';
import { checkServerIdentity } from 'node:tls';
// DO NOT use destructuring for `https.request` and `http.request` as it's not compatible with `nock`.
import https from 'node:https';
import http from 'node:http';
import is, { assert } from '@sindresorhus/is';
import lowercaseKeys from 'lowercase-keys';
import CacheableLookup from 'cacheable-lookup';
import http2wrapper from 'http2-wrapper';
import parseLinkHeader from './parse-link-header.js';
import { getUnixSocketPath } from './utils/is-unix-socket-url.js';
const [major, minor] = process.versions.node.split('.').map(Number);
/**
Generic helper that wraps any assertion function to add context to error messages.
*/
function wrapAssertionWithContext(optionName, assertionFn) {
    try {
        assertionFn();
    }
    catch (error) {
        if (error instanceof Error) {
            error.message = `Option '${optionName}': ${error.message}`;
        }
        throw error;
    }
}
/**
Helper function that wraps assert.any() to provide better error messages.
When assertion fails, it includes the option name in the error message.
*/
function assertAny(optionName, validators, value) {
    wrapAssertionWithContext(optionName, () => {
        assert.any(validators, value);
    });
}
/**
Helper function that wraps assert.plainObject() to provide better error messages.
When assertion fails, it includes the option name in the error message.
*/
function assertPlainObject(optionName, value) {
    wrapAssertionWithContext(optionName, () => {
        assert.plainObject(value);
    });
}
export function isSameOrigin(previousUrl, nextUrl) {
    return previousUrl.origin === nextUrl.origin
        && getUnixSocketPath(previousUrl) === getUnixSocketPath(nextUrl);
}
export const crossOriginStripHeaders = ['host', 'cookie', 'cookie2', 'authorization', 'proxy-authorization'];
const bodyHeaderNames = ['content-length', 'content-encoding', 'content-language', 'content-location', 'content-type', 'transfer-encoding'];
function usesUnixSocket(url) {
    return url.protocol === 'unix:' || getUnixSocketPath(url) !== undefined;
}
function hasCredentialInUrl(url, credential) {
    if (url instanceof URL) {
        return url[credential] !== '';
    }
    if (!is.string(url)) {
        return false;
    }
    try {
        return new URL(url)[credential] !== '';
    }
    catch {
        return false;
    }
}
export const hasExplicitCredentialInUrlChange = (changedState, url, credential) => (changedState.has(credential)
    || (changedState.has('url') && url?.[credential] !== ''));
const hasProtocolSlashes = (value) => /^[a-z][\d+\-.a-z]*:\/\//iv.test(value);
const hasHttpProtocolWithoutSlashes = (value) => /^https?:(?!\/\/)/iv.test(value);
const hasUnixProtocolWithoutSlashes = (value) => /^unix:/iv.test(value);
const isAbsoluteUrl = (url) => is.urlInstance(url) || (is.string(url) && (hasProtocolSlashes(url) || url.startsWith('//')));
const isSlashOrBackslash = (character) => character === '/' || character === '\\';
const startsWithSchemeRelativeSeparators = (value) => value.length > 1 && isSlashOrBackslash(value[0]) && isSlashOrBackslash(value[1]);
const stripLeadingC0ControlOrSpace = (value) => {
    let index = 0;
    while (index < value.length && value.codePointAt(index) <= 0x20) {
        index++;
    }
    return value.slice(index);
};
const removeAsciiTabOrNewline = (value) => {
    let result = '';
    for (const character of value) {
        const codePoint = character.codePointAt(0);
        if (codePoint !== 0x09 && codePoint !== 0x0A && codePoint !== 0x0D) {
            result += character;
        }
    }
    return result;
};
const assertRelativeUrlIfNeeded = (options, url) => {
    if (!options.prefixUrl || options.allowAbsoluteUrls) {
        return;
    }
    const normalizedUrl = is.string(url) ? stripLeadingC0ControlOrSpace(removeAsciiTabOrNewline(url)) : url;
    const isDisallowed = isAbsoluteUrl(normalizedUrl)
        || (is.string(normalizedUrl) && (hasHttpProtocolWithoutSlashes(normalizedUrl)
            || startsWithSchemeRelativeSeparators(normalizedUrl)
            || (options.enableUnixSockets && hasUnixProtocolWithoutSlashes(normalizedUrl))));
    if (isDisallowed) {
        throw new Error('The `url` option must be relative when `allowAbsoluteUrls` is false and `prefixUrl` is set');
    }
};
export const assertUrlHasSameOriginAsPrefixUrlIfNeeded = (options, url) => {
    if (!options.prefixUrl || options.allowAbsoluteUrls) {
        return;
    }
    let prefixUrl;
    try {
        prefixUrl = new URL(options.prefixUrl);
    }
    catch {
        return;
    }
    if (isSameOrigin(prefixUrl, url)) {
        return;
    }
    throw new Error('The `url` option must stay on the same origin as `prefixUrl` when `allowAbsoluteUrls` is false');
};
export const getUrlPrefixBoundary = (options) => ({
    url: options.url instanceof URL ? new URL(options.url) : undefined,
    prefixUrl: options.prefixUrl.toString(),
    allowAbsoluteUrls: options.allowAbsoluteUrls,
});
export const hasUrlOrPrefixUrlBoundaryChanged = (options, currentUrl, previous) => (currentUrl.href !== previous.url?.href
    || options.prefixUrl.toString() !== previous.prefixUrl
    || options.allowAbsoluteUrls !== previous.allowAbsoluteUrls);
export function applyUrlOverride(options, url, { username, password, baseUrl } = {}) {
    assertRelativeUrlIfNeeded(options, url);
    if (is.string(url) && options.url) {
        const resolvedUrl = new URL(url, baseUrl ?? options.url);
        url = resolvedUrl.toString();
    }
    if (options.allowAbsoluteUrls) {
        options.prefixUrl = '';
        options.url = url;
    }
    else {
        const { allowAbsoluteUrls } = options;
        try {
            options.allowAbsoluteUrls = true;
            options.url = url;
        }
        finally {
            options.allowAbsoluteUrls = allowAbsoluteUrls;
        }
    }
    if (username !== undefined) {
        options.username = username;
    }
    if (password !== undefined) {
        options.password = password;
    }
    return options.url;
}
function assertValidHeaderName(name) {
    if (name.startsWith(':')) {
        throw new TypeError(`HTTP/2 pseudo-headers are not supported in \`options.headers\`: ${name}`);
    }
}
/**
Safely assign own properties from source to target, skipping `__proto__` to prevent prototype pollution from JSON.parse'd input.
*/
function safeObjectAssign(target, source) {
    for (const [key, value] of Object.entries(source)) {
        if (key === '__proto__') {
            continue;
        }
        Reflect.set(target, key, value);
    }
}
const isToughCookieJar = (cookieJar) => cookieJar.setCookie.length === 4 && cookieJar.getCookieString.length === 0;
function validateSearchParameters(searchParameters) {
    for (const key of Object.keys(searchParameters)) {
        if (key === '__proto__') {
            continue;
        }
        const value = searchParameters[key];
        assertAny(`searchParams.${key}`, [is.string, is.number, is.boolean, is.null, is.undefined], value);
    }
}
const globalCache = new Map();
let globalDnsCache;
const getGlobalDnsCache = () => {
    if (globalDnsCache) {
        return globalDnsCache;
    }
    globalDnsCache = new CacheableLookup();
    return globalDnsCache;
};
// Detects and wraps QuickLRU v7+ instances to make them compatible with the StorageAdapter interface
const wrapQuickLruIfNeeded = (value) => {
    // Check if this is QuickLRU v7+ using Symbol.toStringTag and the evict method (added in v7)
    if (value?.[Symbol.toStringTag] === 'QuickLRU' && typeof value.evict === 'function') {
        // QuickLRU v7+ uses set(key, value, {maxAge: number}) but StorageAdapter expects set(key, value, ttl)
        // Wrap it to translate the interface
        return {
            get(key) {
                return value.get(key);
            },
            set(key, cacheValue, ttl) {
                if (ttl === undefined) {
                    value.set(key, cacheValue);
                }
                else {
                    value.set(key, cacheValue, { maxAge: ttl });
                }
                return true;
            },
            delete(key) {
                return value.delete(key);
            },
            clear() {
                return value.clear();
            },
            has(key) {
                return value.has(key);
            },
        };
    }
    // QuickLRU v5 and other caches work as-is
    return value;
};
const defaultInternals = {
    request: undefined,
    agent: {
        http: undefined,
        https: undefined,
        http2: undefined,
    },
    h2session: undefined,
    decompress: true,
    timeout: {
        connect: undefined,
        lookup: undefined,
        read: undefined,
        request: undefined,
        response: undefined,
        secureConnect: undefined,
        send: undefined,
        socket: undefined,
    },
    prefixUrl: '',
    body: undefined,
    form: undefined,
    json: undefined,
    cookieJar: undefined,
    ignoreInvalidCookies: false,
    searchParams: undefined,
    dnsLookup: undefined,
    dnsCache: undefined,
    context: {},
    hooks: {
        init: [],
        beforeRequest: [],
        beforeError: [],
        beforeRedirect: [],
        beforeRetry: [],
        beforeCache: [],
        afterResponse: [],
    },
    followRedirect: true,
    maxRedirects: 10,
    cache: undefined,
    throwHttpErrors: true,
    username: '',
    password: '',
    http2: false,
    allowGetBody: false,
    allowAbsoluteUrls: true,
    copyPipedHeaders: false,
    headers: {
        'user-agent': 'got (https://github.com/sindresorhus/got)',
    },
    methodRewriting: false,
    dnsLookupIpVersion: undefined,
    parseJson: JSON.parse,
    stringifyJson: JSON.stringify,
    retry: {
        limit: 2,
        methods: [
            'GET',
            'PUT',
            'HEAD',
            'DELETE',
            'OPTIONS',
            'TRACE',
        ],
        statusCodes: [
            408,
            413,
            429,
            500,
            502,
            503,
            504,
            521,
            522,
            524,
        ],
        errorCodes: [
            'ETIMEDOUT',
            'ECONNRESET',
            'EADDRINUSE',
            'ECONNREFUSED',
            'EPIPE',
            'ENOTFOUND',
            'ENETUNREACH',
            'EAI_AGAIN',
        ],
        maxRetryAfter: undefined,
        calculateDelay: ({ computedValue }) => computedValue,
        backoffLimit: Number.POSITIVE_INFINITY,
        noise: 100,
        enforceRetryRules: true,
    },
    localAddress: undefined,
    method: 'GET',
    createConnection: undefined,
    cacheOptions: {
        shared: undefined,
        cacheHeuristic: undefined,
        immutableMinTimeToLive: undefined,
        ignoreCargoCult: undefined,
    },
    https: {
        alpnProtocols: undefined,
        rejectUnauthorized: undefined,
        checkServerIdentity: undefined,
        serverName: undefined,
        certificateAuthority: undefined,
        key: undefined,
        certificate: undefined,
        passphrase: undefined,
        pfx: undefined,
        ciphers: undefined,
        honorCipherOrder: undefined,
        minVersion: undefined,
        maxVersion: undefined,
        signatureAlgorithms: undefined,
        tlsSessionLifetime: undefined,
        dhparam: undefined,
        ecdhCurve: undefined,
        certificateRevocationLists: undefined,
        secureOptions: undefined,
    },
    encoding: undefined,
    resolveBodyOnly: false,
    isStream: false,
    responseType: 'text',
    url: undefined,
    pagination: {
        transform(response) {
            if (response.request.options.responseType === 'json') {
                return response.body;
            }
            return JSON.parse(response.body);
        },
        paginate({ response }) {
            const rawLinkHeader = response.headers.link;
            if (typeof rawLinkHeader !== 'string' || rawLinkHeader.trim() === '') {
                return false;
            }
            const parsed = parseLinkHeader(rawLinkHeader);
            const next = parsed.find(entry => entry.parameters.rel === 'next' || entry.parameters.rel === '"next"');
            if (next) {
                return {
                    url: next.reference,
                };
            }
            return false;
        },
        filter: () => true,
        shouldContinue: () => true,
        countLimit: Number.POSITIVE_INFINITY,
        backoff: 0,
        requestLimit: 10_000,
        stackAllItems: false,
    },
    setHost: true,
    maxHeaderSize: undefined,
    signal: undefined,
    enableUnixSockets: false,
    strictContentLength: true,
};
const cloneInternals = (internals) => {
    const { hooks, retry } = internals;
    const result = {
        ...internals,
        context: { ...internals.context },
        cacheOptions: { ...internals.cacheOptions },
        https: { ...internals.https },
        agent: { ...internals.agent },
        headers: { ...internals.headers },
        retry: {
            ...retry,
            errorCodes: [...retry.errorCodes],
            methods: [...retry.methods],
            statusCodes: [...retry.statusCodes],
        },
        timeout: { ...internals.timeout },
        hooks: {
            init: [...hooks.init],
            beforeRequest: [...hooks.beforeRequest],
            beforeError: [...hooks.beforeError],
            beforeRedirect: [...hooks.beforeRedirect],
            beforeRetry: [...hooks.beforeRetry],
            beforeCache: [...hooks.beforeCache],
            afterResponse: [...hooks.afterResponse],
        },
        searchParams: internals.searchParams ? new URLSearchParams(internals.searchParams) : undefined,
        pagination: { ...internals.pagination },
    };
    return result;
};
const cloneRaw = (raw) => {
    const result = { ...raw };
    if (Object.hasOwn(raw, 'context') && is.object(raw.context)) {
        result.context = { ...raw.context };
    }
    if (Object.hasOwn(raw, 'cacheOptions') && is.object(raw.cacheOptions)) {
        result.cacheOptions = { ...raw.cacheOptions };
    }
    if (Object.hasOwn(raw, 'https') && is.object(raw.https)) {
        result.https = { ...raw.https };
    }
    if (Object.hasOwn(raw, 'agent') && is.object(raw.agent)) {
        result.agent = { ...raw.agent };
    }
    if (Object.hasOwn(raw, 'headers') && is.object(raw.headers)) {
        result.headers = { ...raw.headers };
    }
    if (Object.hasOwn(raw, 'retry') && is.object(raw.retry)) {
        const { retry } = raw;
        result.retry = { ...retry };
        if (is.array(retry.errorCodes)) {
            result.retry.errorCodes = [...retry.errorCodes];
        }
        if (is.array(retry.methods)) {
            result.retry.methods = [...retry.methods];
        }
        if (is.array(retry.statusCodes)) {
            result.retry.statusCodes = [...retry.statusCodes];
        }
    }
    if (Object.hasOwn(raw, 'timeout') && is.object(raw.timeout)) {
        result.timeout = { ...raw.timeout };
    }
    if (Object.hasOwn(raw, 'hooks') && is.object(raw.hooks)) {
        const { hooks } = raw;
        result.hooks = {
            ...hooks,
        };
        if (is.array(hooks.init)) {
            result.hooks.init = [...hooks.init];
        }
        if (is.array(hooks.beforeRequest)) {
            result.hooks.beforeRequest = [...hooks.beforeRequest];
        }
        if (is.array(hooks.beforeError)) {
            result.hooks.beforeError = [...hooks.beforeError];
        }
        if (is.array(hooks.beforeRedirect)) {
            result.hooks.beforeRedirect = [...hooks.beforeRedirect];
        }
        if (is.array(hooks.beforeRetry)) {
            result.hooks.beforeRetry = [...hooks.beforeRetry];
        }
        if (is.array(hooks.beforeCache)) {
            result.hooks.beforeCache = [...hooks.beforeCache];
        }
        if (is.array(hooks.afterResponse)) {
            result.hooks.afterResponse = [...hooks.afterResponse];
        }
    }
    if (Object.hasOwn(raw, 'searchParams') && raw.searchParams) {
        if (is.string(raw.searchParams)) {
            result.searchParams = raw.searchParams;
        }
        else if (raw.searchParams instanceof URLSearchParams) {
            result.searchParams = new URLSearchParams(raw.searchParams);
        }
        else if (is.object(raw.searchParams)) {
            result.searchParams = { ...raw.searchParams };
        }
    }
    if (Object.hasOwn(raw, 'pagination') && is.object(raw.pagination)) {
        result.pagination = { ...raw.pagination };
    }
    return result;
};
const getHttp2TimeoutOption = (internals) => {
    const delays = [internals.timeout.socket, internals.timeout.connect, internals.timeout.lookup, internals.timeout.request, internals.timeout.secureConnect].filter(delay => typeof delay === 'number');
    return delays.length > 0 ? Math.min(...delays) : undefined;
};
const trackStateMutation = (trackedStateMutations, name) => {
    trackedStateMutations?.add(name);
};
const addExplicitHeader = (explicitHeaders, name) => {
    explicitHeaders.add(name);
};
const markHeaderAsExplicit = (explicitHeaders, trackedStateMutations, name) => {
    addExplicitHeader(explicitHeaders, name);
    trackStateMutation(trackedStateMutations, name);
};
const trackReplacedHeaderMutations = (trackedStateMutations, previousHeaders, nextHeaders) => {
    if (!trackedStateMutations) {
        return;
    }
    for (const header of new Set([...Object.keys(previousHeaders), ...Object.keys(nextHeaders)])) {
        if (previousHeaders[header] !== nextHeaders[header]) {
            trackStateMutation(trackedStateMutations, header);
        }
    }
};
const init = (options, withOptions, self) => {
    const initHooks = options.hooks?.init;
    if (initHooks) {
        for (const hook of initHooks) {
            hook(withOptions, self);
        }
    }
};
// Keys never merged: got.extend() internals, url (passed as first arg), control flags, security
const nonMergeableKeys = new Set(['mutableDefaults', 'handlers', 'url', 'preserveHooks', 'isStream', '__proto__']);
export default class Options {
    #internals;
    #headersProxy;
    #merging = false;
    #init;
    #explicitHeaders;
    #trackedStateMutations;
    constructor(input, options, defaults) {
        assertAny('input', [is.string, is.urlInstance, is.object, is.undefined], input);
        assertAny('options', [is.object, is.undefined], options);
        assertAny('defaults', [is.object, is.undefined], defaults);
        if (input instanceof Options || options instanceof Options) {
            throw new TypeError('The defaults must be passed as the third argument');
        }
        if (defaults) {
            this.#internals = cloneInternals(defaults.#internals);
            this.#init = [...defaults.#init];
            this.#explicitHeaders = new Set(defaults.#explicitHeaders);
        }
        else {
            this.#internals = cloneInternals(defaultInternals);
            this.#init = [];
            this.#explicitHeaders = new Set();
        }
        this.#headersProxy = this.#createHeadersProxy();
        // This rule allows `finally` to be considered more important.
        // Meaning no matter the error thrown in the `try` block,
        // if `finally` throws then the `finally` error will be thrown.
        //
        // Yes, we want this. If we set `url` first, then the `url.searchParams`
        // would get merged. Instead we set the `searchParams` first, then
        // `url.searchParams` is overwritten as expected.
        //
        /* eslint-disable no-unsafe-finally -- `finally` is used intentionally here to ensure `url` is always set last, overwriting any merged searchParams */
        try {
            if (is.plainObject(input)) {
                try {
                    this.merge(input);
                    this.merge(options);
                }
                finally {
                    this.url = input.url;
                }
            }
            else {
                try {
                    this.merge(options);
                }
                finally {
                    if (options?.url !== undefined) {
                        if (input === undefined) {
                            this.url = options.url;
                        }
                        else {
                            throw new TypeError('The `url` option is mutually exclusive with the `input` argument');
                        }
                    }
                    else if (input !== undefined) {
                        this.url = input;
                    }
                }
            }
        }
        catch (error) {
            error.options = this;
            throw error;
        }
        /* eslint-enable no-unsafe-finally */
    }
    merge(options) {
        if (!options) {
            return;
        }
        if (options instanceof Options) {
            // Create a copy of the #init array to avoid infinite loop
            // when merging an Options instance with itself
            const initArray = [...options.#init];
            for (const init of initArray) {
                this.merge(init);
            }
            return;
        }
        options = cloneRaw(options);
        init(this, options, this);
        init(options, options, this);
        this.#merging = true;
        try {
            let push = false;
            for (const key of Object.keys(options)) {
                if (nonMergeableKeys.has(key)) {
                    continue;
                }
                if (!(key in this)) {
                    throw new Error(`Unexpected option: ${key}`);
                }
                // @ts-expect-error Type 'unknown' is not assignable to type 'never'.
                const value = options[key];
                if (value === undefined) {
                    continue;
                }
                // @ts-expect-error Type 'unknown' is not assignable to type 'never'.
                this[key] = value;
                push = true;
            }
            if (push) {
                this.#init.push(options);
            }
        }
        finally {
            this.#merging = false;
        }
    }
    /**
    Custom request function.
    The main purpose of this is to [support HTTP2 using a wrapper](https://github.com/szmarczak/http2-wrapper).

    @default http.request | https.request
    */
    get request() {
        return this.#internals.request;
    }
    set request(value) {
        assertAny('request', [is.function, is.undefined], value);
        this.#internals.request = value;
    }
    /**
    An object representing `http`, `https` and `http2` keys for [`http.Agent`](https://nodejs.org/api/http.html#http_class_http_agent), [`https.Agent`](https://nodejs.org/api/https.html#https_class_https_agent) and [`http2wrapper.Agent`](https://github.com/szmarczak/http2-wrapper#new-http2agentoptions) instance.
    This is necessary because a request to one protocol might redirect to another.
    In such a scenario, Got will switch over to the right protocol agent for you.

    If a key is not present, it will default to a global agent.

    @example
    ```
    import got from 'got';
    import HttpAgent from 'agentkeepalive';

    const {HttpsAgent} = HttpAgent;

    await got('https://sindresorhus.com', {
        agent: {
            http: new HttpAgent(),
            https: new HttpsAgent()
        }
    });
    ```
    */
    get agent() {
        return this.#internals.agent;
    }
    set agent(value) {
        assertPlainObject('agent', value);
        for (const key of Object.keys(value)) {
            if (key === '__proto__') {
                continue;
            }
            if (!(key in this.#internals.agent)) {
                throw new TypeError(`Unexpected agent option: ${key}`);
            }
            // @ts-expect-error - No idea why `value[key]` doesn't work here.
            assertAny(`agent.${key}`, [is.object, is.undefined, (v) => v === false], value[key]);
        }
        if (this.#merging) {
            safeObjectAssign(this.#internals.agent, value);
        }
        else {
            this.#internals.agent = { ...value };
        }
    }
    get h2session() {
        return this.#internals.h2session;
    }
    set h2session(value) {
        this.#internals.h2session = value;
    }
    /**
    Decompress the response automatically.

    This will set the `accept-encoding` header to `gzip, deflate, br` unless you set it yourself.

    If this is disabled, a compressed response is returned as a `Uint8Array`.
    This may be useful if you want to handle decompression yourself or stream the raw compressed data.

    @default true
    */
    get decompress() {
        return this.#internals.decompress;
    }
    set decompress(value) {
        assert.boolean(value);
        this.#internals.decompress = value;
    }
    /**
    Milliseconds to wait for the server to end the response before aborting the request with `got.TimeoutError` error (a.k.a. `request` property).
    By default, there's no timeout.

    This also accepts an `object` with the following fields to constrain the duration of each phase of the request lifecycle:

    - `lookup` starts when a socket is assigned and ends when the hostname has been resolved.
        Does not apply when using a Unix domain socket.
    - `connect` starts when `lookup` completes (or when the socket is assigned if lookup does not apply to the request) and ends when the socket is connected.
    - `secureConnect` starts when `connect` completes and ends when the handshaking process completes (HTTPS only).
    - `socket` starts when the socket is connected. See [request.setTimeout](https://nodejs.org/api/http.html#http_request_settimeout_timeout_callback).
    - `response` starts when the request has been written to the socket and ends when the response headers are received.
    - `send` starts when the socket is connected and ends with the request has been written to the socket.
    - `request` starts when the request is initiated and ends when the response's end event fires.
    */
    get timeout() {
        // We always return `Delays` here.
        // It has to be `Delays | number`, otherwise TypeScript will error because the getter and the setter have incompatible types.
        return this.#internals.timeout;
    }
    set timeout(value) {
        assertPlainObject('timeout', value);
        for (const key of Object.keys(value)) {
            if (key === '__proto__') {
                continue;
            }
            if (!(key in this.#internals.timeout)) {
                throw new Error(`Unexpected timeout option: ${key}`);
            }
            // @ts-expect-error - No idea why `value[key]` doesn't work here.
            assertAny(`timeout.${key}`, [is.number, is.undefined], value[key]);
        }
        if (this.#merging) {
            safeObjectAssign(this.#internals.timeout, value);
        }
        else {
            this.#internals.timeout = { ...value };
        }
    }
    /**
    When specified, `prefixUrl` will be prepended to relative string `url` input.
    The prefix can be any valid URL, either relative or absolute.
    A trailing slash `/` is optional - one will be added automatically.

    __Note__: Absolute string URLs and URL instances bypass `prefixUrl` by default. Other instance defaults, including headers, still apply. For untrusted URLs, set `allowAbsoluteUrls` to `false`.

    __Note__: Got cannot know which custom headers are sensitive. If you use headers like `x-api-key`, only pass trusted URLs or use `allowAbsoluteUrls: false`.

    __Note__: Leading slashes in `input` are disallowed when using this option to enforce consistency and avoid confusion.
    For example, when the prefix URL is `https://example.com/foo` and the input is `/bar`, there's ambiguity whether the resulting URL would become `https://example.com/foo/bar` or `https://example.com/bar`.
    The latter is used by browsers.

    __Tip__: Useful when used with `got.extend()` to create niche-specific Got instances.

    __Tip__: You can change `prefixUrl` using hooks as long as the URL still includes the `prefixUrl`.
    If the URL doesn't include it anymore, it will throw.

    @example
    ```
    import got from 'got';

    await got('unicorn', {prefixUrl: 'https://cats.com'});
    //=> 'https://cats.com/unicorn'

    const instance = got.extend({
        prefixUrl: 'https://google.com'
    });

    await instance('unicorn', {
        hooks: {
            beforeRequest: [
                options => {
                    options.prefixUrl = 'https://cats.com';
                }
            ]
        }
    });
    //=> 'https://cats.com/unicorn'
    ```
    */
    get prefixUrl() {
        // We always return `string` here.
        // It has to be `string | URL`, otherwise TypeScript will error because the getter and the setter have incompatible types.
        return this.#internals.prefixUrl;
    }
    set prefixUrl(value) {
        assertAny('prefixUrl', [is.string, is.urlInstance], value);
        if (value === '') {
            this.#internals.prefixUrl = '';
            return;
        }
        value = value.toString();
        if (!value.endsWith('/')) {
            value += '/';
        }
        if (this.#internals.prefixUrl && this.#internals.url) {
            const { href } = this.#internals.url;
            this.#internals.url.href = value + href.slice(this.#internals.prefixUrl.length);
        }
        this.#internals.prefixUrl = value;
    }
    /**
    __Note #1__: The `body` option cannot be used with the `json` or `form` option.

    __Note #2__: If you provide this option, `got.stream()` will be read-only.

    __Note #3__: If you provide a payload with the `GET` or `HEAD` method, it will throw a `TypeError` unless the method is `GET` and the `allowGetBody` option is set to `true`.

    __Note #4__: This option is not enumerable and will not be merged with the instance defaults.

    The `content-length` header will be automatically set if `body` is a `string` / `Uint8Array` / typed array, and `content-length` and `transfer-encoding` are not manually set in `options.headers`.

    Since Got 12, the `content-length` is not automatically set when `body` is a `fs.createReadStream`.

    You can use `Iterable` and `AsyncIterable` objects as request body, including Web [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream):

    @example
    ```
    import got from 'got';

    // Using an async generator
    async function* generateData() {
        yield 'Hello, ';
        yield 'world!';
    }

    await got.post('https://httpbin.org/anything', {
        body: generateData()
    });
    ```
    */
    get body() {
        return this.#internals.body;
    }
    set body(value) {
        assertAny('body', [is.string, is.buffer, is.nodeStream, is.generator, is.asyncGenerator, is.iterable, is.asyncIterable, is.typedArray, is.undefined], value);
        if (is.nodeStream(value)) {
            assert.truthy(value.readable);
        }
        if (value !== undefined) {
            assert.undefined(this.#internals.form);
            assert.undefined(this.#internals.json);
        }
        this.#internals.body = value;
        trackStateMutation(this.#trackedStateMutations, 'body');
    }
    /**
    The form body is converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj).

    If the `Content-Type` header is not present, it will be set to `application/x-www-form-urlencoded`.

    __Note #1__: If you provide this option, `got.stream()` will be read-only.

    __Note #2__: This option is not enumerable and will not be merged with the instance defaults.
    */
    get form() {
        return this.#internals.form;
    }
    set form(value) {
        assertAny('form', [is.plainObject, is.undefined], value);
        if (value !== undefined) {
            assert.undefined(this.#internals.body);
            assert.undefined(this.#internals.json);
        }
        this.#internals.form = value;
        trackStateMutation(this.#trackedStateMutations, 'form');
    }
    /**
    JSON request body. If the `content-type` header is not set, it will be set to `application/json`.

    __Important__: This option only affects the request body you send to the server. To parse the response as JSON, you must either call `.json()` on the promise or set `responseType: 'json'` in the options.

    __Note #1__: If you provide this option, `got.stream()` will be read-only.

    __Note #2__: This option is not enumerable and will not be merged with the instance defaults.
    */
    get json() {
        return this.#internals.json;
    }
    set json(value) {
        if (value !== undefined) {
            assert.undefined(this.#internals.body);
            assert.undefined(this.#internals.form);
        }
        this.#internals.json = value;
        trackStateMutation(this.#trackedStateMutations, 'json');
    }
    /**
    The URL to request, as a string, a [`https.request` options object](https://nodejs.org/api/https.html#https_https_request_options_callback), or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url).

    Properties from `options` will override properties in the parsed `url`.

    If no protocol is specified, it will throw a `TypeError`.

    __Note__: The query string is **not** parsed as search params.

    @example
    ```
    await got('https://example.com/?query=a b'); //=> https://example.com/?query=a%20b
    await got('https://example.com/', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b

    // The query string is overridden by `searchParams`
    await got('https://example.com/?query=a b', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b
    ```
    */
    get url() {
        return this.#internals.url;
    }
    set url(value) {
        assertAny('url', [is.string, is.urlInstance, is.undefined], value);
        if (value === undefined) {
            this.#internals.url = undefined;
            trackStateMutation(this.#trackedStateMutations, 'url');
            return;
        }
        if (is.string(value) && value.startsWith('/')) {
            throw new Error('`url` must not start with a slash');
        }
        const valueString = value.toString();
        if (is.string(value)
            && !this.prefixUrl
            && hasHttpProtocolWithoutSlashes(valueString)) {
            throw new Error('`url` protocol must be followed by `//`');
        }
        // Detect if URL is already absolute.
        const isAbsolute = isAbsoluteUrl(value);
        assertRelativeUrlIfNeeded(this, value);
        // Only concatenate prefixUrl if the URL is relative
        const urlString = isAbsolute ? valueString : `${this.prefixUrl}${valueString}`;
        const url = new URL(urlString);
        this.#internals.url = url;
        trackStateMutation(this.#trackedStateMutations, 'url');
        if (usesUnixSocket(url) && !this.#internals.enableUnixSockets) {
            throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled');
        }
        if (url.protocol === 'unix:') {
            url.href = `http://unix${url.pathname}${url.search}`;
        }
        if (url.protocol !== 'http:' && url.protocol !== 'https:') {
            const error = new Error(`Unsupported protocol: ${url.protocol}`);
            error.code = 'ERR_UNSUPPORTED_PROTOCOL';
            throw error;
        }
        if (this.#internals.username) {
            url.username = this.#internals.username;
            this.#internals.username = '';
        }
        if (this.#internals.password) {
            url.password = this.#internals.password;
            this.#internals.password = '';
        }
        if (this.#internals.searchParams) {
            url.search = this.#internals.searchParams.toString();
            this.#internals.searchParams = undefined;
        }
    }
    /**
    Cookie support. You don't have to care about parsing or how to store them.

    __Note__: If you provide this option, `options.headers.cookie` will be overridden.
    */
    get cookieJar() {
        return this.#internals.cookieJar;
    }
    set cookieJar(value) {
        assertAny('cookieJar', [is.object, is.undefined], value);
        if (value === undefined) {
            this.#internals.cookieJar = undefined;
            return;
        }
        const { setCookie, getCookieString } = value;
        assert.function(setCookie);
        assert.function(getCookieString);
        /* istanbul ignore next: Horrible `tough-cookie` v3 check */
        if (isToughCookieJar(value)) {
            this.#internals.cookieJar = {
                setCookie: promisify(value.setCookie.bind(value)),
                getCookieString: promisify(value.getCookieString.bind(value)),
            };
        }
        else {
            this.#internals.cookieJar = value;
        }
    }
    /**
    You can abort the `request` using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).

    @example
    ```
    import got from 'got';

    const abortController = new AbortController();

    const request = got('https://httpbin.org/anything', {
        signal: abortController.signal
    });

    setTimeout(() => {
        abortController.abort();
    }, 100);
    ```
    */
    get signal() {
        return this.#internals.signal;
    }
    set signal(value) {
        assertAny('signal', [is.object, is.undefined], value);
        this.#internals.signal = value;
    }
    /**
    Ignore invalid cookies instead of throwing an error.
    Only useful when the `cookieJar` option has been set. Not recommended.

    @default false
    */
    get ignoreInvalidCookies() {
        return this.#internals.ignoreInvalidCookies;
    }
    set ignoreInvalidCookies(value) {
        assert.boolean(value);
        this.#internals.ignoreInvalidCookies = value;
    }
    /**
    Query string that will be added to the request URL.
    This will override the query string in `url`.

    If you need to pass in an array, you can do it using a `URLSearchParams` instance.

    @example
    ```
    import got from 'got';

    const searchParams = new URLSearchParams([['key', 'a'], ['key', 'b']]);

    await got('https://example.com', {searchParams});

    console.log(searchParams.toString());
    //=> 'key=a&key=b'
    ```
    */
    get searchParams() {
        if (this.#internals.url) {
            return this.#internals.url.searchParams;
        }
        this.#internals.searchParams ??= new URLSearchParams();
        return this.#internals.searchParams;
    }
    set searchParams(value) {
        assertAny('searchParams', [is.string, is.object, is.undefined], value);
        const url = this.#internals.url;
        if (value === undefined) {
            this.#internals.searchParams = undefined;
            if (url) {
                url.search = '';
            }
            return;
        }
        const searchParameters = this.searchParams;
        let updated;
        if (is.string(value)) {
            updated = new URLSearchParams(value);
        }
        else if (value instanceof URLSearchParams) {
            // Clone so the caller-owned object is not stored by reference.
            updated = new URLSearchParams(value);
        }
        else {
            validateSearchParameters(value);
            updated = new URLSearchParams();
            for (const key of Object.keys(value)) {
                if (key === '__proto__') {
                    continue;
                }
                const entry = value[key];
                if (entry === null) {
                    updated.append(key, '');
                }
                else if (entry === undefined) {
                    searchParameters.delete(key);
                }
                else {
                    updated.append(key, entry);
                }
            }
        }
        if (this.#merging) {
            // These keys will be replaced
            for (const key of updated.keys()) {
                searchParameters.delete(key);
            }
            for (const [key, value] of updated) {
                searchParameters.append(key, value);
            }
        }
        else if (url) {
            // Overrides the query string in the URL.
            url.search = updated.toString();
        }
        else {
            this.#internals.searchParams = updated;
        }
    }
    get searchParameters() {
        throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.');
    }
    set searchParameters(_value) {
        throw new Error('The `searchParameters` option does not exist. Use `searchParams` instead.');
    }
    get dnsLookup() {
        return this.#internals.dnsLookup;
    }
    set dnsLookup(value) {
        assertAny('dnsLookup', [is.function, is.undefined], value);
        this.#internals.dnsLookup = value;
    }
    /**
    An instance of [`CacheableLookup`](https://github.com/szmarczak/cacheable-lookup) used for making DNS lookups.
    Useful when making lots of requests to different *public* hostnames.

    `CacheableLookup` uses `dns.resolver4(..)` and `dns.resolver6(...)` under the hood and fall backs to `dns.lookup(...)` when the first two fail, which may lead to additional delay.

    __Note__: This should stay disabled when making requests to internal hostnames such as `localhost`, `database.local` etc.

    @default false
    */
    get dnsCache() {
        return this.#internals.dnsCache;
    }
    set dnsCache(value) {
        assertAny('dnsCache', [is.object, is.boolean, is.undefined], value);
        if (value === true) {
            this.#internals.dnsCache = getGlobalDnsCache();
        }
        else if (value === false) {
            this.#internals.dnsCache = undefined;
        }
        else {
            this.#internals.dnsCache = value;
        }
    }
    /**
    User data. `context` is shallow merged and enumerable. If it contains non-enumerable properties they will NOT be merged.

    @example
    ```
    import got from 'got';

    const instance = got.extend({
        hooks: {
            beforeRequest: [
                options => {
                    if (!options.context || !options.context.token) {
                        throw new Error('Token required');
                    }

                    options.headers.token = options.context.token;
                }
            ]
        }
    });

    const context = {
        token: 'secret'
    };

    const response = await instance('https://httpbin.org/headers', {context});

    // Let's see the headers
    console.log(response.body);
    ```
    */
    get context() {
        return this.#internals.context;
    }
    set context(value) {
        assert.object(value);
        if (this.#merging) {
            safeObjectAssign(this.#internals.context, value);
        }
        else {
            this.#internals.context = { ...value };
        }
    }
    /**
    Hooks allow modifications during the request lifecycle.
    Hook functions may be async and are run serially.
    */
    get hooks() {
        return this.#internals.hooks;
    }
    set hooks(value) {
        assert.object(value);
        for (const knownHookEvent of Object.keys(value)) {
            if (knownHookEvent === '__proto__') {
                continue;
            }
            if (!(knownHookEvent in this.#internals.hooks)) {
                throw new Error(`Unexpected hook event: ${knownHookEvent}`);
            }
            const typedKnownHookEvent = knownHookEvent;
            const hooks = value[typedKnownHookEvent];
            assertAny(`hooks.${knownHookEvent}`, [is.array, is.undefined], hooks);
            if (hooks) {
                for (const hook of hooks) {
                    assert.function(hook);
                }
            }
            if (this.#merging) {
                if (hooks) {
                    // @ts-expect-error FIXME
                    this.#internals.hooks[typedKnownHookEvent].push(...hooks);
                }
            }
            else {
                if (!hooks) {
                    throw new Error(`Missing hook event: ${knownHookEvent}`);
                }
                // @ts-expect-error FIXME
                this.#internals.hooks[knownHookEvent] = [...hooks];
            }
        }
    }
    /**
    Whether redirect responses should be followed automatically.

    Optionally, pass a function to dynamically decide based on the response object.

    Note that if a `303` is sent by the server in response to any request type (`POST`, `DELETE`, etc.), Got will automatically request the resource pointed to in the location header via `GET`.
    This is in accordance with [the spec](https://tools.ietf.org/html/rfc7231#section-6.4.4). You can optionally turn on this behavior also for other redirect codes - see `methodRewriting`.
    On cross-origin redirects, Got strips `host`, `cookie`, `cookie2`, `authorization`, and `proxy-authorization`. When a redirect rewrites the request to `GET`, Got also strips request body headers. Use `hooks.beforeRedirect` for app-specific sensitive headers.

    @default true
    */
    get followRedirect() {
        return this.#internals.followRedirect;
    }
    set followRedirect(value) {
        assertAny('followRedirect', [is.boolean, is.function], value);
        this.#internals.followRedirect = value;
    }
    get followRedirects() {
        throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
    }
    set followRedirects(_value) {
        throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
    }
    /**
    If exceeded, the request will be aborted and a `MaxRedirectsError` will be thrown.

    @default 10
    */
    get maxRedirects() {
        return this.#internals.maxRedirects;
    }
    set maxRedirects(value) {
        assert.number(value);
        this.#internals.maxRedirects = value;
    }
    /**
    A cache adapter instance for storing cached response data.

    @default false
    */
    get cache() {
        return this.#internals.cache;
    }
    set cache(value) {
        assertAny('cache', [is.object, is.string, is.boolean, is.undefined], value);
        if (value === true) {
            this.#internals.cache = globalCache;
        }
        else if (value === false) {
            this.#internals.cache = undefined;
        }
        else {
            this.#internals.cache = wrapQuickLruIfNeeded(value);
        }
    }
    /**
    Determines if a `got.HTTPError` is thrown for unsuccessful responses.

    If this is disabled, requests that encounter an error status code will be resolved with the `response` instead of throwing.
    This may be useful if you are checking for resource availability and are expecting error responses.

    @default true
    */
    get throwHttpErrors() {
        return this.#internals.throwHttpErrors;
    }
    set throwHttpErrors(value) {
        assert.boolean(value);
        this.#internals.throwHttpErrors = value;
    }
    get username() {
        const url = this.#internals.url;
        const value = url ? url.username : this.#internals.username;
        return decodeURIComponent(value);
    }
    set username(value) {
        assert.string(value);
        const url = this.#internals.url;
        const fixedValue = encodeURIComponent(value);
        if (url) {
            url.username = fixedValue;
        }
        else {
            this.#internals.username = fixedValue;
        }
        trackStateMutation(this.#trackedStateMutations, 'username');
    }
    get password() {
        const url = this.#internals.url;
        const value = url ? url.password : this.#internals.password;
        return decodeURIComponent(value);
    }
    set password(value) {
        assert.string(value);
        const url = this.#internals.url;
        const fixedValue = encodeURIComponent(value);
        if (url) {
            url.password = fixedValue;
        }
        else {
            this.#internals.password = fixedValue;
        }
        trackStateMutation(this.#trackedStateMutations, 'password');
    }
    /**
    If set to `true`, Got will additionally accept HTTP2 requests.

    It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol.

    __Note__: This option requires Node.js 15.10.0 or newer as HTTP/2 support on older Node.js versions is very buggy.

    __Note__: Overriding `options.request` will disable HTTP2 support.

    @default false

    @example
    ```
    import got from 'got';

    const {headers} = await got('https://nghttp2.org/httpbin/anything', {http2: true});

    console.log(headers.via);
    //=> '2 nghttpx'
    ```
    */
    get http2() {
        return this.#internals.http2;
    }
    set http2(value) {
        assert.boolean(value);
        this.#internals.http2 = value;
    }
    /**
    Set this to `true` to allow sending body for the `GET` method.
    However, the [HTTP/2 specification](https://tools.ietf.org/html/rfc7540#section-8.1.3) says that `An HTTP GET request includes request header fields and no payload body`, therefore when using the HTTP/2 protocol this option will have no effect.
    This option is only meant to interact with non-compliant servers when you have no other choice.

    __Note__: The [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) doesn't specify any particular behavior for the GET method having a payload, therefore __it's considered an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern)__.

    @default false
    */
    get allowGetBody() {
        return this.#internals.allowGetBody;
    }
    set allowGetBody(value) {
        assert.boolean(value);
        this.#internals.allowGetBody = value;
    }
    /**
    Allow absolute URLs to bypass `prefixUrl`.

    When set to `false` with `prefixUrl`, passing an absolute `url` will throw. This also rejects scheme-relative URL strings like `//example.com/path` in retry and pagination URL overrides. Use this when untrusted URL input must stay on the same origin as the configured `prefixUrl`. This is not a path sandbox: relative paths like `../other` still follow standard URL resolution on the same origin. Set `prefixUrl` to an empty string for a request that intentionally needs an absolute URL.

    __Note__: This guards the `url` you pass. It does not block cross-origin redirects issued by the server, though inherited sensitive headers are still stripped when a redirect changes origin.

    __Note__: The check is defeated if the same hook or `pagination.paginate(…)` return also sets `prefixUrl` or `allowAbsoluteUrls`. Do not populate those options from untrusted data.

    @default true
    */
    get allowAbsoluteUrls() {
        return this.#internals.allowAbsoluteUrls;
    }
    set allowAbsoluteUrls(value) {
        assert.boolean(value);
        this.#internals.allowAbsoluteUrls = value;
    }
    /**
    Automatically copy headers from piped streams.

    When piping a request into a Got stream (e.g., `request.pipe(got.stream(url))`), this controls whether headers from the source stream are automatically merged into the Got request headers.

    Note: Explicitly set headers take precedence over piped headers. Piped headers are only copied when a header is not already explicitly set.

    Useful for proxy scenarios when explicitly enabled, but you may still want to filter out headers like `Host`, `Connection`, `Authorization`, etc.

    @default false

    @example
    ```
    import got from 'got';
    import {pipeline} from 'node:stream/promises';

    // Opt in to automatic header copying for proxy scenarios
    server.get('/proxy', async (request, response) => {
        const gotStream = got.stream('https://example.com', {
            copyPipedHeaders: true,
            // Explicit headers win over piped headers
            headers: {
                host: 'example.com',
            }
        });

        await pipeline(request, gotStream, response);
    });
    ```

    @example
    ```
    import got from 'got';
    import {pipeline} from 'node:stream/promises';

    // Keep it disabled and manually copy only safe headers
    server.get('/proxy', async (request, response) => {
        const gotStream = got.stream('https://example.com', {
            headers: {
                'user-agent': request.headers['user-agent'],
                'accept': request.headers['accept'],
                // Explicitly NOT copying host, connection, authorization, etc.
            }
        });

        await pipeline(request, gotStream, response);
    });
    ```
    */
    get copyPipedHeaders() {
        return this.#internals.copyPipedHeaders;
    }
    set copyPipedHeaders(value) {
        assert.boolean(value);
        this.#internals.copyPipedHeaders = value;
    }
    isHeaderExplicitlySet(name) {
        return this.#explicitHeaders.has(name.toLowerCase());
    }
    shouldCopyPipedHeader(name) {
        return !this.isHeaderExplicitlySet(name);
    }
    setPipedHeader(name, value) {
        assertValidHeaderName(name);
        this.#internals.headers[name.toLowerCase()] = value;
    }
    getInternalHeaders() {
        return this.#internals.headers;
    }
    setInternalHeader(name, value) {
        assertValidHeaderName(name);
        this.#internals.headers[name.toLowerCase()] = value;
    }
    deleteInternalHeader(name) {
        // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
        delete this.#internals.headers[name.toLowerCase()];
    }
    async trackStateMutations(operation) {
        const changedState = new Set();
        this.#trackedStateMutations = changedState;
        try {
            return await operation(changedState);
        }
        finally {
            this.#trackedStateMutations = undefined;
        }
    }
    clearBody() {
        this.body = undefined;
        this.json = undefined;
        this.form = undefined;
        for (const header of bodyHeaderNames) {
            this.deleteInternalHeader(header);
        }
    }
    clearUnchangedCookieHeader(previousState, changedState) {
        if (previousState?.hadCookieJar
            && this.cookieJar === undefined
            && !this.isHeaderExplicitlySet('cookie')
            && !changedState?.has('cookie')
            && this.headers.cookie === previousState.headers.cookie) {
            this.deleteInternalHeader('cookie');
        }
    }
    restoreCookieHeader(previousState, headers) {
        if (!previousState) {
            return;
        }
        if (Object.hasOwn(headers ?? {}, 'cookie')) {
            return;
        }
        if (previousState.cookieWasExplicitlySet) {
            this.headers.cookie = previousState.headers.cookie;
            return;
        }
        delete this.headers.cookie;
        if (previousState.headers.cookie !== undefined) {
            this.setInternalHeader('cookie', previousState.headers.cookie);
        }
    }
    syncCookieHeaderAfterMerge(previousState, headers) {
        this.restoreCookieHeader(previousState, headers);
        this.clearUnchangedCookieHeader(previousState);
    }
    stripUnchangedCrossOriginState(previousState, changedState, { clearBody = true } = {}) {
        const headers = this.getInternalHeaders();
        const url = this.#internals.url;
        for (const header of crossOriginStripHeaders) {
            if (!changedState.has(header) && headers[header] === previousState.headers[header]) {
                this.deleteInternalHeader(header);
            }
        }
        if (!hasExplicitCredentialInUrlChange(changedState, url, 'username')) {
            this.username = '';
        }
        if (!hasExplicitCredentialInUrlChange(changedState, url, 'password')) {
            this.password = '';
        }
        if (clearBody && !changedState.has('body') && !changedState.has('json') && !changedState.has('form') && isBodyUnchanged(this, previousState)) {
            this.clearBody();
        }
    }
    /**
    Strip sensitive headers and credentials when navigating to a different origin.
    Headers and credentials explicitly provided in `userOptions` are preserved.
    */
    stripSensitiveHeaders(previousUrl, nextUrl, userOptions) {
        if (isSameOrigin(previousUrl, nextUrl)) {
            return;
        }
        const headers = lowercaseKeys(userOptions.headers ?? {});
        for (const header of crossOriginStripHeaders) {
            if (headers[header] === undefined) {
                this.deleteInternalHeader(header);
            }
        }
        const explicitUsername = Object.hasOwn(userOptions, 'username') ? userOptions.username : undefined;
        const explicitPassword = Object.hasOwn(userOptions, 'password') ? userOptions.password : undefined;
        const hasExplicitUsername = explicitUsername !== undefined
            || hasCredentialInUrl(userOptions.url, 'username')
            || isCrossOriginCredentialChanged(previousUrl, nextUrl, 'username');
        const hasExplicitPassword = explicitPassword !== undefined
            || hasCredentialInUrl(userOptions.url, 'password')
            || isCrossOriginCredentialChanged(previousUrl, nextUrl, 'password');
        if (!hasExplicitUsername && this.username) {
            this.username = '';
        }
        if (!hasExplicitPassword && this.password) {
            this.password = '';
        }
    }
    /**
    Request headers.

    Existing headers will be overwritten. Headers set to `undefined` will be omitted.

    @default {}
    */
    get headers() {
        return this.#headersProxy;
    }
    set headers(value) {
        assertPlainObject('headers', value);
        const normalizedHeaders = lowercaseKeys(value);
        for (const header of Object.keys(normalizedHeaders)) {
            assertValidHeaderName(header);
        }
        if (this.#merging) {
            safeObjectAssign(this.#internals.headers, normalizedHeaders);
        }
        else {
            const previousHeaders = this.#internals.headers;
            this.#internals.headers = normalizedHeaders;
            this.#headersProxy = this.#createHeadersProxy();
            this.#explicitHeaders.clear();
            trackReplacedHeaderMutations(this.#trackedStateMutations, previousHeaders, normalizedHeaders);
        }
        for (const header of Object.keys(normalizedHeaders)) {
            if (this.#merging) {
                markHeaderAsExplicit(this.#explicitHeaders, this.#trackedStateMutations, header);
            }
            else {
                addExplicitHeader(this.#explicitHeaders, header);
            }
        }
    }
    /**
    Specifies if the HTTP request method should be [rewritten as `GET`](https://tools.ietf.org/html/rfc7231#section-6.4) on redirects.

    As the [specification](https://tools.ietf.org/html/rfc7231#section-6.4) prefers to rewrite the HTTP method only on `303` responses, this is Got's default behavior. Cross-origin `301` and `302` redirects also rewrite `POST` requests to `GET` by default to avoid forwarding request bodies to another origin.
    Setting `methodRewriting` to `true` will also rewrite same-origin `301` and `302` responses, as allowed by the spec. This is the behavior followed by `curl` and browsers.

    __Note__: Got never performs method rewriting on `307` and `308` responses, as this is [explicitly prohibited by the specification](https://www.rfc-editor.org/rfc/rfc7231#section-6.4.7).

    @default false
    */
    get methodRewriting() {
        return this.#internals.methodRewriting;
    }
    set methodRewriting(value) {
        assert.boolean(value);
        this.#internals.methodRewriting = value;
    }
    /**
    Indicates which DNS record family to use.

    Values:
    - `undefined`: IPv4 (if present) or IPv6
    - `4`: Only IPv4
    - `6`: Only IPv6

    @default undefined
    */
    get dnsLookupIpVersion() {
        return this.#internals.dnsLookupIpVersion;
    }
    set dnsLookupIpVersion(value) {
        if (value !== undefined && value !== 4 && value !== 6) {
            throw new TypeError(`Invalid DNS lookup IP version: ${value}`);
        }
        this.#internals.dnsLookupIpVersion = value;
    }
    /**
    A function used to parse JSON responses.

    @example
    ```
    import got from 'got';
    import Bourne from '@hapi/bourne';

    const parsed = await got('https://example.com', {
        parseJson: text => Bourne.parse(text)
    }).json();

    console.log(parsed);
    ```
    */
    get parseJson() {
        return this.#internals.parseJson;
    }
    set parseJson(value) {
        assert.function(value);
        this.#internals.parseJson = value;
    }
    /**
    A function used to stringify the body of JSON requests.

    @example
    ```
    import got from 'got';

    await got.post('https://example.com', {
        stringifyJson: object => JSON.stringify(object, (key, value) => {
            if (key.startsWith('_')) {
                return;
            }

            return value;
        }),
        json: {
            some: 'payload',
            _ignoreMe: 1234
        }
    });
    ```

    @example
    ```
    import got from 'got';

    await got.post('https://example.com', {
        stringifyJson: object => JSON.stringify(object, (key, value) => {
            if (typeof value === 'number') {
                return value.toString();
            }

            return value;
        }),
        json: {
            some: 'payload',
            number: 1
        }
    });
    ```
    */
    get stringifyJson() {
        return this.#internals.stringifyJson;
    }
    set stringifyJson(value) {
        assert.function(value);
        this.#internals.stringifyJson = value;
    }
    /**
    An object representing `limit`, `calculateDelay`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes.

    Delays between retries counts with function `1000 * Math.pow(2, retry) + Math.random() * 100`, where `retry` is attempt number (starts from 1).

    The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value.
    The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry).

    The `enforceRetryRules` property is a `boolean` that, when set to `true` (default), enforces the `limit`, `methods`, `statusCodes`, and `errorCodes` options before calling `calculateDelay`. Your `calculateDelay` function is only invoked when a retry is allowed based on these criteria. When `false`, `calculateDelay` receives the computed value but can override all retry logic.

    __Note:__ When `enforceRetryRules` is `false`, you must check `computedValue` in your `calculateDelay` function to respect retry rules. When `true` (default), the retry rules are enforced automatically.

    By default, it retries *only* on the specified methods, status codes, and on these network errors:

    - `ETIMEDOUT`: One of the [timeout](#timeout) limits were reached.
    - `ECONNRESET`: Connection was forcibly closed by a peer.
    - `EADDRINUSE`: Could not bind to any free port.
    - `ECONNREFUSED`: Connection was refused by the server.
    - `EPIPE`: The remote side of the stream being written has been closed.
    - `ENOTFOUND`: Couldn't resolve the hostname to an IP address.
    - `ENETUNREACH`: No internet connection.
    - `EAI_AGAIN`: DNS lookup timed out.

    __Note__: If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`.
    __Note__: If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request.
    */
    get retry() {
        return this.#internals.retry;
    }
    set retry(value) {
        assertPlainObject('retry', value);
        assertAny('retry.calculateDelay', [is.function, is.undefined], value.calculateDelay);
        assertAny('retry.maxRetryAfter', [is.number, is.undefined], value.maxRetryAfter);
        assertAny('retry.limit', [is.number, is.undefined], value.limit);
        assertAny('retry.methods', [is.array, is.undefined], value.methods);
        assertAny('retry.statusCodes', [is.array, is.undefined], value.statusCodes);
        assertAny('retry.errorCodes', [is.array, is.undefined], value.errorCodes);
        assertAny('retry.noise', [is.number, is.undefined], value.noise);
        assertAny('retry.enforceRetryRules', [is.boolean, is.undefined], value.enforceRetryRules);
        if (value.noise && Math.abs(value.noise) > 100) {
            throw new Error(`The maximum acceptable retry noise is +/- 100ms, got ${value.noise}`);
        }
        for (const key of Object.keys(value)) {
            if (key === '__proto__') {
                continue;
            }
            if (!(key in this.#internals.retry)) {
                throw new Error(`Unexpected retry option: ${key}`);
            }
        }
        if (this.#merging) {
            safeObjectAssign(this.#internals.retry, value);
        }
        else {
            this.#internals.retry = { ...value };
        }
        const { retry } = this.#internals;
        retry.methods = [...new Set(retry.methods.map(method => method.toUpperCase()))];
        retry.statusCodes = [...new Set(retry.statusCodes)];
        retry.errorCodes = [...new Set(retry.errorCodes)];
    }
    /**
    From `http.RequestOptions`.

    The IP address used to send the request from.
    */
    get localAddress() {
        return this.#internals.localAddress;
    }
    set localAddress(value) {
        assertAny('localAddress', [is.string, is.undefined], value);
        this.#internals.localAddress = value;
    }
    /**
    The HTTP method used to make the request.

    @default 'GET'
    */
    get method() {
        return this.#internals.method;
    }
    set method(value) {
        assert.string(value);
        this.#internals.method = value.toUpperCase();
    }
    get createConnection() {
        return this.#internals.createConnection;
    }
    set createConnection(value) {
        assertAny('createConnection', [is.function, is.undefined], value);
        this.#internals.createConnection = value;
    }
    /**
    From `http-cache-semantics`

    @default {}
    */
    get cacheOptions() {
        return this.#internals.cacheOptions;
    }
    set cacheOptions(value) {
        assertPlainObject('cacheOptions', value);
        assertAny('cacheOptions.shared', [is.boolean, is.undefined], value.shared);
        assertAny('cacheOptions.cacheHeuristic', [is.number, is.undefined], value.cacheHeuristic);
        assertAny('cacheOptions.immutableMinTimeToLive', [is.number, is.undefined], value.immutableMinTimeToLive);
        assertAny('cacheOptions.ignoreCargoCult', [is.boolean, is.undefined], value.ignoreCargoCult);
        for (const key of Object.keys(value)) {
            if (key === '__proto__') {
                continue;
            }
            if (!(key in this.#internals.cacheOptions)) {
                throw new Error(`Cache option \`${key}\` does not exist`);
            }
        }
        if (this.#merging) {
            safeObjectAssign(this.#internals.cacheOptions, value);
        }
        else {
            this.#internals.cacheOptions = { ...value };
        }
    }
    /**
    Options for the advanced HTTPS API.
    */
    get https() {
        return this.#internals.https;
    }
    set https(value) {
        assertPlainObject('https', value);
        assertAny('https.rejectUnauthorized', [is.boolean, is.undefined], value.rejectUnauthorized);
        assertAny('https.checkServerIdentity', [is.function, is.undefined], value.checkServerIdentity);
        assertAny('https.serverName', [is.string, is.undefined], value.serverName);
        assertAny('https.certificateAuthority', [is.string, is.object, is.array, is.undefined], value.certificateAuthority);
        assertAny('https.key', [is.string, is.object, is.array, is.undefined], value.key);
        assertAny('https.certificate', [is.string, is.object, is.array, is.undefined], value.certificate);
        assertAny('https.passphrase', [is.string, is.undefined], value.passphrase);
        assertAny('https.pfx', [is.string, is.buffer, is.array, is.undefined], value.pfx);
        assertAny('https.alpnProtocols', [is.array, is.undefined], value.alpnProtocols);
        assertAny('https.ciphers', [is.string, is.undefined], value.ciphers);
        assertAny('https.dhparam', [is.string, is.buffer, is.undefined], value.dhparam);
        assertAny('https.signatureAlgorithms', [is.string, is.undefined], value.signatureAlgorithms);
        assertAny('https.minVersion', [is.string, is.undefined], value.minVersion);
        assertAny('https.maxVersion', [is.string, is.undefined], value.maxVersion);
        assertAny('https.honorCipherOrder', [is.boolean, is.undefined], value.honorCipherOrder);
        assertAny('https.tlsSessionLifetime', [is.number, is.undefined], value.tlsSessionLifetime);
        assertAny('https.ecdhCurve', [is.string, is.undefined], value.ecdhCurve);
        assertAny('https.certificateRevocationLists', [is.string, is.buffer, is.array, is.undefined], value.certificateRevocationLists);
        assertAny('https.secureOptions', [is.number, is.undefined], value.secureOptions);
        for (const key of Object.keys(value)) {
            if (key === '__proto__') {
                continue;
            }
            if (!(key in this.#internals.https)) {
                throw new Error(`HTTPS option \`${key}\` does not exist`);
            }
        }
        if (this.#merging) {
            safeObjectAssign(this.#internals.https, value);
        }
        else {
            this.#internals.https = { ...value };
        }
    }
    /**
    [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data.

    To get a [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), you need to set `responseType` to `buffer` instead.
    Don't set this option to `null`.

    __Note__: This doesn't affect streams! Instead, you need to do `got.stream(...).setEncoding(encoding)`.

    @default 'utf-8'
    */
    get encoding() {
        return this.#internals.encoding;
    }
    set encoding(value) {
        if (value === null) {
            throw new TypeError('To get a Uint8Array, set `options.responseType` to `buffer` instead');
        }
        assertAny('encoding', [is.string, is.undefined], value);
        this.#internals.encoding = value;
    }
    /**
    When set to `true` the promise will return the Response body instead of the Response object.

    @default false
    */
    get resolveBodyOnly() {
        return this.#internals.resolveBodyOnly;
    }
    set resolveBodyOnly(value) {
        assert.boolean(value);
        this.#internals.resolveBodyOnly = value;
    }
    /**
    Returns a `Stream` instead of a `Promise`.
    Set internally by `got.stream()`.

    @default false
    @internal
    */
    get isStream() {
        return this.#internals.isStream;
    }
    set isStream(value) {
        assert.boolean(value);
        this.#internals.isStream = value;
    }
    /**
    The parsing method.

    The promise also has `.text()`, `.json()` and `.buffer()` methods which return another Got promise for the parsed body.

    It's like setting the options to `{responseType: 'json', resolveBodyOnly: true}` but without affecting the main Got promise.

    __Note__: When using streams, this option is ignored.

    @example
    ```
    const responsePromise = got(url);
    const bufferPromise = responsePromise.buffer();
    const jsonPromise = responsePromise.json();

    const [response, buffer, json] = Promise.all([responsePromise, bufferPromise, jsonPromise]);
    // `response` is an instance of Got Response
    // `buffer` is an instance of Uint8Array
    // `json` is an object
    ```

    @example
    ```
    // This
    const body = await got(url).json();

    // is semantically the same as this
    const body = await got(url, {responseType: 'json', resolveBodyOnly: true});
    ```
    */
    get responseType() {
        return this.#internals.responseType;
    }
    set responseType(value) {
        if (value === undefined) {
            this.#internals.responseType = 'text';
            return;
        }
        if (value !== 'text' && value !== 'buffer' && value !== 'json') {
            throw new Error(`Invalid \`responseType\` option: ${value}`);
        }
        this.#internals.responseType = value;
    }
    get pagination() {
        return this.#internals.pagination;
    }
    set pagination(value) {
        assert.object(value);
        if (this.#merging) {
            safeObjectAssign(this.#internals.pagination, value);
        }
        else {
            this.#internals.pagination = value;
        }
    }
    get auth() {
        throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.');
    }
    set auth(_value) {
        throw new Error('Parameter `auth` is deprecated. Use `username` / `password` instead.');
    }
    get setHost() {
        return this.#internals.setHost;
    }
    set setHost(value) {
        assert.boolean(value);
        this.#internals.setHost = value;
    }
    get maxHeaderSize() {
        return this.#internals.maxHeaderSize;
    }
    set maxHeaderSize(value) {
        assertAny('maxHeaderSize', [is.number, is.undefined], value);
        this.#internals.maxHeaderSize = value;
    }
    get enableUnixSockets() {
        return this.#internals.enableUnixSockets;
    }
    set enableUnixSockets(value) {
        assert.boolean(value);
        this.#internals.enableUnixSockets = value;
    }
    /**
    Throw an error if the server response's `content-length` header value doesn't match the number of bytes received.

    This is useful for detecting truncated responses and follows RFC 9112 requirements for message completeness.

    __Note__: Responses without a `content-length` header are not validated.
    __Note__: When enabled and validation fails, a `ReadError` with code `ERR_HTTP_CONTENT_LENGTH_MISMATCH` will be thrown.

    @default true
    */
    get strictContentLength() {
        return this.#internals.strictContentLength;
    }
    set strictContentLength(value) {
        assert.boolean(value);
        this.#internals.strictContentLength = value;
    }
    // eslint-disable-next-line @typescript-eslint/naming-convention
    toJSON() {
        return { ...this.#internals };
    }
    [Symbol.for('nodejs.util.inspect.custom')](_depth, options) {
        return inspect(this.#internals, options);
    }
    createNativeRequestOptions() {
        const internals = this.#internals;
        const url = internals.url;
        let agent;
        if (url.protocol === 'https:') {
            if (internals.http2) {
                // Ensure HTTP/2 agent is configured for connection reuse
                // If no custom agent.http2 is provided, use the global agent for connection pooling
                agent = {
                    ...internals.agent,
                    http2: internals.agent.http2 ?? http2wrapper.globalAgent,
                };
            }
            else {
                agent = internals.agent.https;
            }
        }
        else {
            agent = internals.agent.http;
        }
        const { https } = internals;
        let { pfx } = https;
        if (is.array(pfx) && is.plainObject(pfx[0])) {
            pfx = pfx.map(object => ({
                buf: object.buffer,
                passphrase: object.passphrase,
            }));
        }
        const unixSocketPath = getUnixSocketPath(url);
        if (usesUnixSocket(url) && !internals.enableUnixSockets) {
            throw new Error('Using UNIX domain sockets but option `enableUnixSockets` is not enabled');
        }
        let unixSocketGroups;
        if (unixSocketPath !== undefined) {
            unixSocketGroups = /^(?<socketPath>[^:]+):(?<path>.+)$/v.exec(`${url.pathname}${url.search}`)?.groups;
        }
        const unixOptions = unixSocketGroups
            ? { socketPath: unixSocketGroups.socketPath, path: unixSocketGroups.path, host: '' }
            : undefined;
        return {
            ...internals.cacheOptions,
            ...unixOptions,
            // HTTPS options
            // eslint-disable-next-line @typescript-eslint/naming-convention
            ALPNProtocols: https.alpnProtocols,
            ca: https.certificateAuthority,
            cert: https.certificate,
            key: https.key,
            passphrase: https.passphrase,
            pfx,
            rejectUnauthorized: https.rejectUnauthorized,
            checkServerIdentity: https.checkServerIdentity ?? checkServerIdentity,
            servername: https.serverName,
            ciphers: https.ciphers,
            honorCipherOrder: https.honorCipherOrder,
            minVersion: https.minVersion,
            maxVersion: https.maxVersion,
            sigalgs: https.signatureAlgorithms,
            sessionTimeout: https.tlsSessionLifetime,
            dhparam: https.dhparam,
            ecdhCurve: https.ecdhCurve,
            crl: https.certificateRevocationLists,
            secureOptions: https.secureOptions,
            // HTTP options
            lookup: internals.dnsLookup ?? internals.dnsCache?.lookup,
            family: internals.dnsLookupIpVersion,
            agent,
            setHost: internals.setHost,
            method: internals.method,
            maxHeaderSize: internals.maxHeaderSize,
            localAddress: internals.localAddress,
            headers: internals.headers,
            createConnection: internals.createConnection,
            timeout: internals.http2 ? getHttp2TimeoutOption(internals) : undefined,
            // HTTP/2 options
            h2session: internals.h2session,
        };
    }
    getRequestFunction() {
        const { request: customRequest } = this.#internals;
        if (!customRequest) {
            return this.#getFallbackRequestFunction();
        }
        const requestWithFallback = (url, options, callback) => {
            const result = customRequest(url, options, callback);
            if (is.promise(result)) {
                return this.#resolveRequestWithFallback(result, url, options, callback);
            }
            if (result !== undefined) {
                return result;
            }
            return this.#callFallbackRequest(url, options, callback);
        };
        return requestWithFallback;
    }
    freeze() {
        const options = this.#internals;
        Object.freeze(options);
        Object.freeze(options.hooks);
        Object.freeze(options.hooks.afterResponse);
        Object.freeze(options.hooks.beforeError);
        Object.freeze(options.hooks.beforeRedirect);
        Object.freeze(options.hooks.beforeRequest);
        Object.freeze(options.hooks.beforeRetry);
        Object.freeze(options.hooks.init);
        Object.freeze(options.https);
        Object.freeze(options.cacheOptions);
        Object.freeze(options.agent);
        Object.freeze(options.headers);
        Object.freeze(options.timeout);
        Object.freeze(options.retry);
        Object.freeze(options.retry.errorCodes);
        Object.freeze(options.retry.methods);
        Object.freeze(options.retry.statusCodes);
    }
    #createHeadersProxy() {
        return new Proxy(this.#internals.headers, {
            get(target, property, receiver) {
                if (typeof property === 'string') {
                    if (Reflect.has(target, property)) {
                        return Reflect.get(target, property, receiver);
                    }
                    const normalizedProperty = property.toLowerCase();
                    return Reflect.get(target, normalizedProperty, receiver);
                }
                return Reflect.get(target, property, receiver);
            },
            set: (target, property, value) => {
                if (typeof property === 'string') {
                    const normalizedProperty = property.toLowerCase();
                    assertValidHeaderName(normalizedProperty);
                    const isSuccess = Reflect.set(target, normalizedProperty, value);
                    if (isSuccess) {
                        markHeaderAsExplicit(this.#explicitHeaders, this.#trackedStateMutations, normalizedProperty);
                    }
                    return isSuccess;
                }
                return Reflect.set(target, property, value);
            },
            deleteProperty: (target, property) => {
                if (typeof property === 'string') {
                    const normalizedProperty = property.toLowerCase();
                    const isSuccess = Reflect.deleteProperty(target, normalizedProperty);
                    if (isSuccess) {
                        this.#explicitHeaders.delete(normalizedProperty);
                        trackStateMutation(this.#trackedStateMutations, normalizedProperty);
                    }
                    return isSuccess;
                }
                return Reflect.deleteProperty(target, property);
            },
        });
    }
    #getFallbackRequestFunction() {
        const url = this.#internals.url;
        if (!url) {
            return;
        }
        if (url.protocol === 'https:') {
            if (this.#internals.http2) {
                if (major < 15 || (major === 15 && minor < 10)) {
                    const error = new Error('To use the `http2` option, install Node.js 15.10.0 or above');
                    error.code = 'EUNSUPPORTED';
                    throw error;
                }
                return http2wrapper.auto;
            }
            return https.request;
        }
        return http.request;
    }
    #callFallbackRequest(url, options, callback) {
        const fallbackRequest = this.#getFallbackRequestFunction();
        if (!fallbackRequest) {
            throw new TypeError('The request function must return a value');
        }
        const fallbackResult = fallbackRequest(url, options, callback);
        if (fallbackResult === undefined) {
            throw new TypeError('The request function must return a value');
        }
        if (is.promise(fallbackResult)) {
            return this.#resolveFallbackRequestResult(fallbackResult);
        }
        return fallbackResult;
    }
    async #resolveRequestWithFallback(requestResult, url, options, callback) {
        const result = await requestResult;
        if (result !== undefined) {
            return result;
        }
        return this.#callFallbackRequest(url, options, callback);
    }
    async #resolveFallbackRequestResult(fallbackResult) {
        const resolvedFallbackResult = await fallbackResult;
        if (resolvedFallbackResult === undefined) {
            throw new TypeError('The request function must return a value');
        }
        return resolvedFallbackResult;
    }
}
export const snapshotCrossOriginState = (options) => ({
    headers: { ...options.getInternalHeaders() },
    hadCookieJar: options.cookieJar !== undefined,
    cookieWasExplicitlySet: options.isHeaderExplicitlySet('cookie'),
    username: options.username,
    password: options.password,
    body: options.body,
    json: options.json,
    form: options.form,
    bodySnapshot: cloneCrossOriginBodyValue(options.body),
    jsonSnapshot: cloneCrossOriginBodyValue(options.json),
    formSnapshot: cloneCrossOriginBodyValue(options.form),
});
const cloneCrossOriginBodyValue = (value) => {
    if (value === undefined || value === null || typeof value !== 'object') {
        return value;
    }
    try {
        return structuredClone(value);
    }
    catch {
        return undefined;
    }
};
const isUnchangedCrossOriginBodyValue = (currentValue, previousValue, previousSnapshot) => {
    if (currentValue !== previousValue) {
        return false;
    }
    if (currentValue === undefined || currentValue === null || typeof currentValue !== 'object') {
        return true;
    }
    if (previousSnapshot === undefined) {
        return true;
    }
    return isDeepStrictEqual(currentValue, previousSnapshot);
};
export const isCrossOriginCredentialChanged = (previousUrl, nextUrl, credential) => (nextUrl[credential] !== '' && nextUrl[credential] !== previousUrl[credential]);
export const isBodyUnchanged = (options, previousState) => isUnchangedCrossOriginBodyValue(options.body, previousState.body, previousState.bodySnapshot)
    && isUnchangedCrossOriginBodyValue(options.json, previousState.json, previousState.jsonSnapshot)
    && isUnchangedCrossOriginBodyValue(options.form, previousState.form, previousState.formSnapshot);
