import { HttpSchema, HttpSchemaMethod, HttpSchemaPath, HttpRequestSchema, HttpHeaders, HttpSearchParams, JSONValue, HttpMethod, HttpHeadersSchema, HttpSearchParamsSchema, HttpRequest, HttpMethodSchema, HttpRequestHeadersSchema, AllowAnyStringInPathParams, HttpStatusCode, HttpResponseSchemaStatusCode, HttpResponse, HttpResponseBodySchema, HttpResponseHeadersSchema, LiteralHttpSchemaPathFromNonLiteral } from '@zimic/http';

declare const value: unique symbol;
/**
 * Represents a value stringified by `JSON.stringify`, maintaining a reference to the original type.
 *
 * This type is used to validate that the expected stringified body is passed to `fetch`.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#using-a-json-body}
 */
type JSONStringified<Value> = string & {
    [value]: Value;
};
declare global {
    interface JSON {
        stringify<Value>(value: Value, replacer?: ((this: any, key: string, value: Value) => any) | (number | string)[] | null, space?: string | number): JSONStringified<Value>;
    }
}

type Default<Type, IfEmpty = never> = [undefined | void] extends [Type] ? IfEmpty : Exclude<Type, undefined | void>;
type DefaultNoExclude<Type, IfEmpty = never> = [undefined | void] extends Type ? IfEmpty : Type;
type IfNever<Type, Yes, No = Type> = [Type] extends [never] ? Yes : No;
type PossiblePromise<Type> = Type | PromiseLike<Type>;
type ReplaceBy<Type, Source, Target> = Type extends Source ? Target : Type;
type RequiredByKey<Type, Key extends keyof Type> = Omit<Type, Key> & Required<Pick<Type, Key>>;

type FetchRequestInitHeaders<RequestSchema extends HttpRequestSchema> = RequestSchema['headers'] | HttpHeaders<Default<RequestSchema['headers']>>;
type FetchRequestInitWithHeaders<RequestSchema extends HttpRequestSchema> = 'headers' extends keyof RequestSchema ? [RequestSchema['headers']] extends [never] ? {
    headers?: undefined;
} : undefined extends RequestSchema['headers'] ? {
    headers?: FetchRequestInitHeaders<RequestSchema>;
} : {
    headers: FetchRequestInitHeaders<RequestSchema>;
} : {
    headers?: undefined;
};
type FetchRequestInitSearchParams<RequestSchema extends HttpRequestSchema> = RequestSchema['searchParams'] | HttpSearchParams<Default<RequestSchema['searchParams']>>;
type FetchRequestInitWithSearchParams<RequestSchema extends HttpRequestSchema> = 'searchParams' extends keyof RequestSchema ? [RequestSchema['searchParams']] extends [never] ? {
    searchParams?: undefined;
} : undefined extends RequestSchema['searchParams'] ? {
    searchParams?: FetchRequestInitSearchParams<RequestSchema>;
} : {
    searchParams: FetchRequestInitSearchParams<RequestSchema>;
} : {
    searchParams?: undefined;
};
type FetchRequestInitWithBody<RequestSchema extends HttpRequestSchema> = 'body' extends keyof RequestSchema ? [RequestSchema['body']] extends [never] ? {
    body?: null;
} : RequestSchema['body'] extends string ? undefined extends RequestSchema['body'] ? {
    body?: ReplaceBy<RequestSchema['body'], undefined, null>;
} : {
    body: RequestSchema['body'];
} : RequestSchema['body'] extends JSONValue ? undefined extends RequestSchema['body'] ? {
    body?: JSONStringified<ReplaceBy<RequestSchema['body'], undefined, null>>;
} : {
    body: JSONStringified<RequestSchema['body']>;
} : undefined extends RequestSchema['body'] ? {
    body?: ReplaceBy<RequestSchema['body'], undefined, null>;
} : {
    body: RequestSchema['body'];
} : {
    body?: null;
};
type FetchRequestInitPerPath<RequestSchema extends HttpRequestSchema> = FetchRequestInitWithHeaders<RequestSchema> & FetchRequestInitWithSearchParams<RequestSchema> & FetchRequestInitWithBody<RequestSchema>;
/**
 * The options to create a {@link FetchRequest} instance, compatible with
 * {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`}.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
 * @see {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`}
 */
type FetchRequestInit<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath<Schema, Method>, Redirect extends RequestRedirect = 'follow'> = Omit<RequestInit, 'method' | 'headers' | 'body'> & {
    /** The HTTP method of the request. */
    method: Method;
    /** The base URL to prefix the path of the request. */
    baseURL?: string;
    redirect?: Redirect;
} & (Path extends Path ? FetchRequestInitPerPath<Default<Default<Schema[Path][Method]>['request']>> : never);
declare namespace FetchRequestInit {
    /** The default options for each request sent by a fetch instance. */
    interface Defaults extends Omit<RequestInit, 'headers'> {
        baseURL: string;
        /** The HTTP method of the request. */
        method?: HttpMethod;
        /** The headers of the request. */
        headers?: HttpHeadersSchema;
        /** The search parameters of the request. */
        searchParams?: HttpSearchParamsSchema;
    }
    /** A loosely typed version of {@link FetchRequestInit `FetchRequestInit`}. */
    type Loose = Partial<Defaults>;
}
type AllFetchResponseStatusCode<MethodSchema extends HttpMethodSchema> = HttpResponseSchemaStatusCode<Default<MethodSchema['response']>>;
type FilterFetchResponseStatusCodeByError<StatusCode extends HttpStatusCode, ErrorOnly extends boolean> = ErrorOnly extends true ? Extract<StatusCode, HttpStatusCode.ClientError | HttpStatusCode.ServerError> : StatusCode;
type FilterFetchResponseStatusCodeByRedirect<StatusCode extends HttpStatusCode, Redirect extends RequestRedirect> = Redirect extends 'error' ? FilterFetchResponseStatusCodeByRedirect<StatusCode, 'follow'> : Redirect extends 'follow' ? Exclude<StatusCode, Exclude<HttpStatusCode.Redirection, 304>> : StatusCode;
type FetchResponseStatusCode<MethodSchema extends HttpMethodSchema, ErrorOnly extends boolean, Redirect extends RequestRedirect> = FilterFetchResponseStatusCodeByRedirect<FilterFetchResponseStatusCodeByError<AllFetchResponseStatusCode<MethodSchema>, ErrorOnly>, Redirect>;
type HttpRequestBodySchema<MethodSchema extends HttpMethodSchema> = ReplaceBy<ReplaceBy<IfNever<DefaultNoExclude<Default<MethodSchema['request']>['body']>, null>, undefined, null>, ArrayBuffer, Blob>;
/**
 * A request instance typed with an HTTP schema, closely compatible with the
 * {@link https://developer.mozilla.org/docs/Web/API/Request native Request class}.
 *
 * On top of the properties available in native {@link https://developer.mozilla.org/docs/Web/API/Request `Request`}
 * instances, fetch requests have their URL automatically prefixed with the base URL of their fetch instance. Default
 * options are also applied, if present in the fetch instance.
 *
 * The path of the request is extracted from the URL, excluding the base URL, and is available in the `path` property.
 *
 * @example
 *   import { type HttpSchema } from '@zimic/http';
 *   import { createFetch } from '@zimic/fetch';
 *
 *   interface User {
 *     id: string;
 *     username: string;
 *   }
 *
 *   type Schema = HttpSchema<{
 *     '/users': {
 *       POST: {
 *         request: {
 *           headers: { 'content-type': 'application/json' };
 *           body: { username: string };
 *         };
 *         response: {
 *           201: { body: User };
 *         };
 *       };
 *     };
 *   }>;
 *
 *   const fetch = createFetch<Schema>({
 *     baseURL: 'http://localhost:3000',
 *   });
 *
 *   const request = new fetch.Request('/users', {
 *     method: 'POST',
 *     headers: { 'content-type': 'application/json' },
 *     body: JSON.stringify({ username: 'me' }),
 *   });
 *
 *   console.log(request); // FetchRequest<Schema, 'POST', '/users'>
 *   console.log(request.path); // '/users'
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchrequest `FetchRequest` API reference}
 * @see {@link https://developer.mozilla.org/docs/Web/API/Request}
 */
interface FetchRequest<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>> extends HttpRequest<HttpRequestBodySchema<Default<Schema[Path][Method]>>, HttpRequestHeadersSchema<Default<Schema[Path][Method]>>> {
    /** The path of the request, excluding the base URL. */
    path: AllowAnyStringInPathParams<Path>;
    /** The HTTP method of the request. */
    method: Method;
}
declare namespace FetchRequest {
    /** A loosely typed version of a {@link FetchRequest `FetchRequest`}. */
    interface Loose extends Request {
        /** The path of the request, excluding the base URL. */
        path: string;
        /** The HTTP method of the request. */
        method: HttpMethod;
        /** Clones the request instance, returning a new instance with the same properties. */
        clone: () => Loose;
    }
}
/**
 * A plain object representation of a {@link FetchRequest `FetchRequest`}, compatible with JSON.
 *
 * If the body is included in the object, it is represented as a string or null if empty.
 */
type FetchRequestObject = Pick<FetchRequest.Loose, 'url' | 'path' | 'method' | 'cache' | 'destination' | 'credentials' | 'integrity' | 'keepalive' | 'mode' | 'redirect' | 'referrer' | 'referrerPolicy'> & {
    /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */
    headers: HttpHeadersSchema;
    /**
     * The body of the response, represented as a string or null if empty.
     *
     * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body)
     */
    body?: string | null;
};
/**
 * A {@link FetchResponse `FetchResponse`} instance with a specific status code.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
 * @see {@link https://developer.mozilla.org/docs/Web/API/Response}
 */
interface FetchResponsePerStatusCode<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>, StatusCode extends HttpStatusCode = HttpStatusCode> extends HttpResponse<HttpResponseBodySchema<Default<Schema[Path][Method]>, StatusCode>, StatusCode, HttpResponseHeadersSchema<Default<Schema[Path][Method]>, StatusCode>> {
    /** The request that originated the response. */
    request: FetchRequest<Schema, Method, Path>;
    /**
     * An error representing a response with a failure status code (4XX or 5XX). It can be thrown to handle the error
     * upper in the call stack.
     *
     * If the response has a success status code (1XX, 2XX or 3XX), this property will be null.
     */
    error: StatusCode extends HttpStatusCode.ClientError | HttpStatusCode.ServerError ? FetchResponseError<Schema, Method, Path> : null;
}
/**
 * A response instance typed with an HTTP schema, closely compatible with the
 * {@link https://developer.mozilla.org/docs/Web/API/Response native Response class}.
 *
 * On top of the properties available in native Response instances, fetch responses have a reference to the request that
 * originated them, available in the `request` property.
 *
 * If the response has a failure status code (4XX or 5XX), an error is available in the `error` property.
 *
 * @example
 *   import { type HttpSchema } from '@zimic/http';
 *   import { createFetch } from '@zimic/fetch';
 *
 *   interface User {
 *     id: string;
 *     username: string;
 *   }
 *
 *   type Schema = HttpSchema<{
 *     '/users/:userId': {
 *       GET: {
 *         response: {
 *           200: { body: User };
 *           404: { body: { message: string } };
 *         };
 *       };
 *     };
 *   }>;
 *
 *   const fetch = createFetch<Schema>({
 *     baseURL: 'http://localhost:3000',
 *   });
 *
 *   const response = await fetch(`/users/${userId}`, {
 *     method: 'GET',
 *   });
 *
 *   console.log(response); // FetchResponse<Schema, 'GET', '/users'>
 *
 *   if (response.status === 404) {
 *     const errorBody = await response.json(); // { message: string }
 *     console.error(errorBody.message);
 *     return null;
 *   } else {
 *     const user = await response.json(); // User
 *     return user;
 *   }
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
 * @see {@link https://developer.mozilla.org/docs/Web/API/Response}
 */
type FetchResponse<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>, ErrorOnly extends boolean = false, Redirect extends RequestRedirect = 'follow', StatusCode extends FetchResponseStatusCode<Default<Schema[Path][Method]>, ErrorOnly, Redirect> = FetchResponseStatusCode<Default<Schema[Path][Method]>, ErrorOnly, Redirect>> = StatusCode extends StatusCode ? FetchResponsePerStatusCode<Schema, Method, Path, StatusCode> : never;
declare namespace FetchResponse {
    /** A loosely typed version of a {@link FetchResponse}. */
    interface Loose extends Response {
        /** The request that originated the response. */
        request: FetchRequest.Loose;
        /**
         * An error representing a response with a failure status code (4XX or 5XX). It can be thrown to handle the error
         * upper in the call stack.
         *
         * If the response has a success status code (1XX, 2XX or 3XX), this property will be null.
         */
        error: AnyFetchRequestError | null;
        /** Clones the request instance, returning a new instance with the same properties. */
        clone: () => Loose;
    }
}
/**
 * A plain object representation of a {@link FetchResponse `FetchResponse`}, compatible with JSON.
 *
 * If the body is included in the object, it is represented as a string or null if empty.
 */
type FetchResponseObject = Pick<FetchResponse.Loose, 'url' | 'type' | 'status' | 'statusText' | 'ok' | 'redirected'> & {
    /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */
    headers: HttpHeadersSchema;
    /**
     * The body of the response, represented as a string or null if empty.
     *
     * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body)
     */
    body?: string | null;
};
/**
 * A constructor for {@link FetchRequest} instances, typed with an HTTP schema and compatible with the
 * {@link https://developer.mozilla.org/docs/Web/API/Request Request class constructor}.
 *
 * @example
 *   import { type HttpSchema } from '@zimic/http';
 *   import { createFetch } from '@zimic/fetch';
 *
 *   type Schema = HttpSchema<{
 *     // ...
 *   }>;
 *
 *   const fetch = createFetch<Schema>({
 *     baseURL: 'http://localhost:3000',
 *   });
 *
 *   const request = new fetch.Request('POST', '/users', {
 *     body: JSON.stringify({ username: 'me' }),
 *   });
 *   console.log(request); // FetchRequest<Schema, 'POST', '/users'>
 *
 * @param input The resource to fetch, either a path, a URL, or a {@link FetchRequest request}. If a path is provided, it
 *   is automatically prefixed with the base URL of the fetch instance when the request is sent. If a URL or a request
 *   is provided, it is used as is.
 * @param init The request options. If a path or a URL is provided as the first argument, this argument is required and
 *   should contain at least the method of the request. If the first argument is a {@link FetchRequest request}, this
 *   argument is optional.
 * @returns A promise that resolves to the response to the request.
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
 * @see {@link https://developer.mozilla.org/docs/Web/API/Request}
 */
type FetchRequestConstructor<Schema extends HttpSchema> = new <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>>(input: FetchInput<Schema, Method, Path>, init: FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>) => FetchRequest<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>;

/**
 * Options for converting a {@link FetchResponseError `FetchResponseError`} into a plain object.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerrortoobject `FetchResponseError#toObject` API reference}
 */
interface FetchResponseErrorObjectOptions {
    /**
     * Whether to include the body of the request in the plain object.
     *
     * @default false
     */
    includeRequestBody?: boolean;
    /**
     * Whether to include the body of the response in the plain object.
     *
     * @default false
     */
    includeResponseBody?: boolean;
}
declare namespace FetchResponseErrorObjectOptions {
    /**
     * Options for converting a {@link FetchResponseError `FetchResponseError`} into a plain object, including the body of
     * the request and/or response.
     */
    type WithBody = FetchResponseErrorObjectOptions & ({
        includeRequestBody: true;
    } | {
        includeResponseBody: true;
    });
    /**
     * Options for converting a {@link FetchResponseError `FetchResponseError`} into a plain object, excluding the body of
     * the request and/or response.
     */
    type WithoutBody = FetchResponseErrorObjectOptions & ({
        includeRequestBody?: false;
    } | {
        includeResponseBody?: false;
    });
}
/**
 * A plain object representation of a {@link FetchResponseError `FetchResponseError`}, compatible with JSON. It is useful
 * for serialization, debugging, and logging purposes.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerrortoobject `FetchResponseError#toObject` API reference}
 */
interface FetchResponseErrorObject {
    name: string;
    message: string;
    request: FetchRequestObject;
    response: FetchResponseObject;
}
/**
 * An error representing a response with a failure status code (4XX or 5XX).
 *
 * @example
 *   import { type HttpSchema } from '@zimic/http';
 *   import { createFetch } from '@zimic/fetch';
 *
 *   interface User {
 *     id: string;
 *     username: string;
 *   }
 *
 *   type Schema = HttpSchema<{
 *     '/users/:userId': {
 *       GET: {
 *         response: {
 *           200: { body: User };
 *           404: { body: { message: string } };
 *         };
 *       };
 *     };
 *   }>;
 *
 *   const fetch = createFetch<Schema>({
 *     baseURL: 'http://localhost:3000',
 *   });
 *
 *   const response = await fetch(`/users/${userId}`, {
 *     method: 'GET',
 *   });
 *
 *   if (!response.ok) {
 *     console.log(response.status); // 404
 *
 *     console.log(response.error); // FetchResponseError<Schema, 'GET', '/users'>
 *     console.log(response.error.request); // FetchRequest<Schema, 'GET', '/users'>
 *     console.log(response.error.response); // FetchResponse<Schema, 'GET', '/users'>
 *
 *     const plainError = response.error.toObject();
 *     console.log(JSON.stringify(plainError));
 *     // {"name":"FetchResponseError","message":"...","request":{...},"response":{...}}
 *   }
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerror `FetchResponseError` API reference}
 */
declare class FetchResponseError<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>> extends Error {
    request: FetchRequest<Schema, Method, Path>;
    response: FetchResponse<Schema, Method, Path, true, 'manual'>;
    constructor(request: FetchRequest<Schema, Method, Path>, response: FetchResponse<Schema, Method, Path, true, 'manual'>);
    /**
     * Converts this error into a plain object. This method is useful for serialization, debugging, and logging purposes.
     *
     * @example
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:3000',
     *   });
     *
     *   const response = await fetch(`/users/${userId}`, {
     *     method: 'GET',
     *   });
     *
     *   if (!response.ok) {
     *     const plainError = response.error.toObject();
     *     console.log(JSON.stringify(plainError));
     *     // {"name":"FetchResponseError","message":"...","request":{...},"response":{...}}
     *   }
     *
     * @param options Options for converting this error into a plain object. By default, the body of the request and
     *   response will not be included.
     * @returns A plain object representing this error. If `options.includeRequestBody` or `options.includeResponseBody`
     *   is `true`, the body of the request and response will be included, respectively, and the return is a `Promise`.
     *   Otherwise, the return is the plain object itself without the bodies.
     * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerrortoobject `FetchResponseError#toObject` API reference}
     */
    toObject(options: FetchResponseErrorObjectOptions.WithBody): Promise<FetchResponseErrorObject>;
    toObject(options: FetchResponseErrorObjectOptions.WithoutBody): FetchResponseErrorObject;
    toObject(options?: FetchResponseErrorObjectOptions): Promise<FetchResponseErrorObject> | FetchResponseErrorObject;
    private convertRequestToObject;
    private convertResponseToObject;
}
type AnyFetchRequestError = FetchResponseError<any, any, any>;

/**
 * The input to fetch a resource, either a path, a URL, or a {@link FetchRequest request}.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
 */
type FetchInput<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>> = Path | URL | FetchRequest<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>;
/**
 * A fetch instance typed with an HTTP schema, closely compatible with the
 * {@link https://developer.mozilla.org/docs/Web/API/Fetch_API native Fetch API}. All requests and responses are typed by
 * default with the schema, including methods, paths, status codes, parameters, and bodies.
 *
 * Requests sent by the fetch instance have their URL automatically prefixed with the base URL of the instance. Default
 * options are also applied to the requests, if present in the instance.
 *
 * @example
 *   import { type HttpSchema } from '@zimic/http';
 *   import { createFetch } from '@zimic/fetch';
 *
 *   interface User {
 *     id: string;
 *     username: string;
 *   }
 *
 *   type Schema = HttpSchema<{
 *     '/users': {
 *       GET: {
 *         request: {
 *           searchParams: { query?: string };
 *         };
 *         response: {
 *           200: { body: User[] };
 *           404: { body: { message: string } };
 *           500: { body: { message: string } };
 *         };
 *       };
 *     };
 *   }>;
 *
 *   const fetch = createFetch<Schema>({
 *     baseURL: 'http://localhost:3000',
 *     headers: { 'accept-language': 'en' },
 *   });
 *
 *   const response = await fetch('/users', {
 *     method: 'GET',
 *     searchParams: { query: 'u' },
 *   });
 *
 *   if (response.status === 404) {
 *     return null; // User not found
 *   }
 *
 *   if (!response.ok) {
 *     throw response.error;
 *   }
 *
 *   const users = await response.json();
 *   return users; // User[]
 *
 * @param input The resource to fetch, either a path, a URL, or a {@link FetchRequest request}. If a path is provided, it
 *   is automatically prefixed with the base URL of the fetch instance when the request is sent. If a URL or a request
 *   is provided, it is used as is.
 * @param init The request options. If a path or a URL is provided as the first argument, this argument is required and
 *   should contain at least the method of the request. If the first argument is a {@link FetchRequest request}, this
 *   argument is optional.
 * @returns A promise that resolves to the response to the request.
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
 * @see {@link https://developer.mozilla.org/docs/Web/API/Fetch_API}
 * @see {@link https://developer.mozilla.org/docs/Web/API/Request}
 * @see {@link https://developer.mozilla.org/docs/Web/API/RequestInit}
 * @see {@link https://developer.mozilla.org/docs/Web/API/Response}
 */
interface Fetch<Schema extends HttpSchema> extends Pick<FetchOptions<Schema>, 'onRequest' | 'onResponse'> {
    <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Redirect extends RequestRedirect = 'follow'>(input: Path | URL, init: FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Redirect>): Promise<FetchResponse<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, false, Redirect>>;
    <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Redirect extends RequestRedirect = 'follow'>(input: FetchRequest<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>, init?: FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Redirect>): Promise<FetchResponse<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, false, Redirect>>;
    /**
     * The default options for each request sent by the fetch instance. The available options are the same as the
     * {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`} options, plus `baseURL`.
     *
     * @example
     *   import { type HttpSchema } from '@zimic/http';
     *   import { createFetch } from '@zimic/fetch';
     *
     *   interface Post {
     *     id: string;
     *     title: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/posts': {
     *       POST: {
     *         request: {
     *           headers: { 'content-type': 'application/json' };
     *           body: { title: string };
     *         };
     *         response: {
     *           201: { body: Post };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:3000',
     *     headers: { 'accept-language': 'en' },
     *   });
     *
     *   // Set the authorization header for all requests
     *   const { accessToken } = await authenticate();
     *
     *   fetch.defaults.headers.authorization = `Bearer ${accessToken}`;
     *   console.log(fetch.defaults.headers);
     *
     *   const response = await fetch('/posts', {
     *     method: 'POST',
     *     headers: { 'content-type': 'application/json' },
     *     body: JSON.stringify({ title: 'My post' }),
     *   });
     *
     *   const post = await response.json(); // Post
     */
    defaults: FetchDefaults;
    /**
     * A loosely-typed version of {@link Fetch `fetch`}. This can be useful to make requests with fewer type constraints,
     * such as in {@link onRequest `onRequest`} and {@link onResponse `onResponse`} listeners.
     *
     * @example
     *   import { type HttpSchema } from '@zimic/http';
     *   import { createFetch } from '@zimic/fetch';
     *
     *   interface User {
     *     id: string;
     *     username: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/auth/login': {
     *       POST: {
     *         request: {
     *           headers: { 'content-type': 'application/json' };
     *           body: { username: string; password: string };
     *         };
     *         response: {
     *           201: { body: { accessToken: string } };
     *         };
     *       };
     *     };
     *
     *     '/auth/refresh': {
     *       POST: {
     *         response: {
     *           201: { body: { accessToken: string } };
     *         };
     *       };
     *     };
     *
     *     '/users': {
     *       GET: {
     *         request: {
     *           headers: { authorization: string };
     *         };
     *         response: {
     *           200: { body: User[] };
     *           401: { body: { message: string } };
     *           403: { body: { message: string } };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL,
     *
     *     async onResponse(response) {
     *       if (response.status === 401) {
     *         const body = await response.clone().json();
     *
     *         if (body.message === 'Access token expired') {
     *           // Refresh the access token
     *           const refreshResponse = await this('/auth/refresh', { method: 'POST' });
     *           const { accessToken } = await refreshResponse.json();
     *
     *           // Clone the original request and update its headers
     *           const updatedRequest = response.request.clone();
     *           updatedRequest.headers.set('authorization', `Bearer ${accessToken}`);
     *
     *           // Retry the original request with the updated headers
     *           return this.loose(updatedRequest);
     *         }
     *       }
     *
     *       return response;
     *     },
     *   });
     *
     *   // Authenticate to your service before requests
     *   const loginRequest = await fetch('/auth/login', {
     *     method: 'POST',
     *     headers: { 'content-type': 'application/json' },
     *     body: JSON.stringify({ username: 'me', password: 'password' }),
     *   });
     *   const { accessToken } = await loginRequest.json();
     *
     *   // Set the authorization header for all requests
     *   fetch.defaults.headers.authorization = `Bearer ${accessToken}`;
     *
     *   const request = await fetch('/users', {
     *     method: 'GET',
     *     searchParams: { query: 'u' },
     *   });
     *
     *   const users = await request.json(); // User[]
     *
     * @param input The resource to fetch, either a path, a URL, or a {@link FetchRequest request}. If a path is provided,
     *   it is automatically prefixed with the base URL of the fetch instance when the request is sent. If a URL or a
     *   request is provided, it is used as is.
     * @param init The request options. If a path or a URL is provided as the first argument, this argument is required
     *   and should contain at least the method of the request. If the first argument is a {@link FetchRequest request},
     *   this argument is optional.
     * @returns A promise that resolves to the response to the request.
     * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchloose `fetch.loose` API reference}
     * @see {@link https://developer.mozilla.org/docs/Web/API/Fetch_API}
     * @see {@link https://developer.mozilla.org/docs/Web/API/Request}
     * @see {@link https://developer.mozilla.org/docs/Web/API/RequestInit}
     * @see {@link https://developer.mozilla.org/docs/Web/API/Response}
     */
    loose: Fetch.Loose;
    /**
     * A constructor for creating
     * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchrequest-1 `FetchRequest`}, closely compatible with
     * the native {@link https://developer.mozilla.org/docs/Web/API/Request Request} constructor.
     *
     * @example
     *   import { type HttpSchema } from '@zimic/http';
     *   import { createFetch } from '@zimic/fetch';
     *
     *   interface User {
     *     id: string;
     *     username: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/users': {
     *       POST: {
     *         request: {
     *           headers: { 'content-type': 'application/json' };
     *           body: { username: string };
     *         };
     *         response: {
     *           201: { body: User };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:3000',
     *   });
     *
     *   const request = new fetch.Request('/users', {
     *     method: 'POST',
     *     headers: { 'content-type': 'application/json' },
     *     body: JSON.stringify({ username: 'me' }),
     *   });
     *
     *   console.log(request); // FetchRequest<Schema, 'POST', '/users'>
     *   console.log(request.path); // '/users'
     *
     * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchrequest-1 `FetchRequest`}
     */
    Request: FetchRequestConstructor<Schema>;
    /**
     * A type guard that checks if a request is a {@link FetchRequest}, was created by the fetch instance, and has a
     * specific method and path. This is useful to narrow down the type of a request before using it.
     *
     * @example
     *   import { type HttpSchema } from '@zimic/http';
     *   import { createFetch } from '@zimic/fetch';
     *
     *   interface User {
     *     id: string;
     *     username: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/users': {
     *       POST: {
     *         request: {
     *           headers: { 'content-type': 'application/json' };
     *           body: { username: string };
     *         };
     *         response: {
     *           201: { body: User };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:3000',
     *   });
     *
     *   const request = new fetch.Request('/users', {
     *     method: 'POST',
     *     headers: { 'content-type': 'application/json' },
     *     body: JSON.stringify({ username: 'me' }),
     *   });
     *
     *   if (fetch.isRequest(request, 'POST', '/users')) {
     *     // request is a FetchRequest<Schema, 'POST', '/users'>
     *
     *     const contentType = request.headers.get('content-type'); // 'application/json'
     *     const body = await request.json(); // { username: string }
     *   }
     *
     * @param request The request to check.
     * @param method The method to check.
     * @param path The path to check.
     * @returns `true` if the request was created by the fetch instance and has the specified method and path; `false`
     *   otherwise.
     */
    isRequest: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(request: unknown, method: Method, path: Path) => request is FetchRequest<Schema, Method, Path>;
    /**
     * A type guard that checks if a response is a {@link FetchResponse}, was received by the fetch instance, and has a
     * specific method and path. This is useful to narrow down the type of a response before using it.
     *
     * @example
     *   import { type HttpSchema } from '@zimic/http';
     *   import { createFetch } from '@zimic/fetch';
     *
     *   interface User {
     *     id: string;
     *     username: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/users': {
     *       GET: {
     *         request: {
     *           searchParams: { query?: string };
     *         };
     *         response: {
     *           200: { body: User[] };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:3000',
     *   });
     *
     *   const response = await fetch('/users', {
     *     method: 'GET',
     *     searchParams: { query: 'u' },
     *   });
     *
     *   if (fetch.isResponse(response, 'GET', '/users')) {
     *     // response is a FetchResponse<Schema, 'GET', '/users'>
     *
     *     const users = await response.json(); // User[]
     *   }
     *
     * @param response The response to check.
     * @param method The method to check.
     * @param path The path to check.
     * @returns `true` if the response was received by the fetch instance and has the specified method and path; `false`
     *   otherwise.
     */
    isResponse: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(response: unknown, method: Method, path: Path) => response is FetchResponse<Schema, Method, Path>;
    /**
     * A type guard that checks if an error is a {@link FetchResponseError} related to a {@link FetchResponse response}
     * received by the fetch instance with a specific method and path. This is useful to narrow down the type of an error
     * before handling it.
     *
     * @example
     *   import { type HttpSchema } from '@zimic/http';
     *   import { createFetch } from '@zimic/fetch';
     *
     *   interface User {
     *     id: string;
     *     username: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/users': {
     *       GET: {
     *         request: {
     *           searchParams: { query?: string };
     *         };
     *         response: {
     *           200: { body: User[] };
     *           400: { body: { message: string } };
     *           500: { body: { message: string } };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:3000',
     *   });
     *
     *   try {
     *     const response = await fetch('/users', {
     *       method: 'GET',
     *       searchParams: { query: 'u' },
     *     });
     *
     *     if (!response.ok) {
     *       throw response.error; // FetchResponseError<Schema, 'GET', '/users'>
     *     }
     *   } catch (error) {
     *     if (fetch.isResponseError(error, 'GET', '/users')) {
     *       // error is a FetchResponseError<Schema, 'GET', '/users'>
     *
     *       const status = error.response.status; // 400 | 500
     *       const { message } = await error.response.json(); // { message: string }
     *
     *       console.error('Could not fetch users:', { status, message });
     *     }
     *   }
     *
     * @param error The error to check.
     * @param method The method to check.
     * @param path The path to check.
     * @returns `true` if the error is a response error received by the fetch instance and has the specified method and
     *   path; `false` otherwise.
     */
    isResponseError: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(error: unknown, method: Method, path: Path) => error is FetchResponseError<Schema, Method, Path>;
}
declare namespace Fetch {
    /** A loosely-typed version of {@link Fetch `fetch`}. This can be useful to make requests with fewer type constraints, */
    type Loose = (input: string | URL | FetchRequest.Loose, init?: FetchRequestInit.Loose) => Promise<FetchResponse.Loose>;
}
/**
 * The options to create a {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch fetch instance}.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#createfetch `createFetch(options)` API reference}
 */
interface FetchOptions<Schema extends HttpSchema> extends Omit<FetchRequestInit.Defaults, 'method'> {
    /**
     * A listener function that is called for each request. It can modify the requests before they are sent.
     *
     * @example
     *   import { createFetch } from '@zimic/fetch';
     *   import { type HttpSchema } from '@zimic/http';
     *
     *   interface User {
     *     id: string;
     *     username: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/users': {
     *       GET: {
     *         request: {
     *           searchParams: { page?: number; limit?: number };
     *         };
     *         response: {
     *           200: { body: User[] };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:80',
     *
     *     onRequest(request) {
     *       if (this.isRequest(request, 'GET', '/users')) {
     *         const url = new URL(request.url);
     *         url.searchParams.append('limit', '10');
     *
     *         const updatedRequest = new Request(url, request);
     *         return updatedRequest;
     *       }
     *
     *       return request;
     *     },
     *   });
     *
     * @param request The original request.
     * @returns The request to be sent. It can be the original request or a modified version of it.
     * @this {Fetch<Schema>} The fetch instance that is sending the request.
     */
    onRequest?: (this: Fetch<Schema>, request: FetchRequest.Loose) => PossiblePromise<Request>;
    /**
     * A listener function that is called after each response is received. It can modify the responses before they are
     * returned to the fetch caller.
     *
     * @example
     *   import { type HttpSchema } from '@zimic/http';
     *   import { createFetch } from '@zimic/fetch';
     *
     *   interface User {
     *     id: string;
     *     username: string;
     *   }
     *
     *   type Schema = HttpSchema<{
     *     '/users': {
     *       GET: {
     *         response: {
     *           200: {
     *             headers: { 'content-encoding'?: string };
     *             body: User[];
     *           };
     *         };
     *       };
     *     };
     *   }>;
     *
     *   const fetch = createFetch<Schema>({
     *     baseURL: 'http://localhost:80',
     *
     *     onResponse(response) {
     *       if (this.isResponse(response, 'GET', '/users')) {
     *         console.log(response.headers.get('content-encoding'));
     *       }
     *       return response;
     *     },
     *   });
     *
     * @param response The original response.
     * @returns The response to be returned.
     * @this {Fetch<Schema>} The fetch instance that received the response.
     */
    onResponse?: (this: Fetch<Schema>, response: FetchResponse.Loose) => PossiblePromise<Response>;
}
/**
 * The default options for each request sent by the fetch instance.
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchdefaults `fetch.defaults` API reference}
 */
type FetchDefaults = RequiredByKey<FetchRequestInit.Defaults, 'headers' | 'searchParams'>;
/**
 * Infers the schema of a {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch fetch instance}.
 *
 * @example
 *   import { type HttpSchema } from '@zimic/http';
 *   import { createFetch, InferFetchSchema } from '@zimic/fetch';
 *
 *   const fetch = createFetch<{
 *     '/users': {
 *       GET: {
 *         response: { 200: { body: User[] } };
 *       };
 *     };
 *   }>({
 *     baseURL: 'http://localhost:3000',
 *   });
 *
 *   type Schema = InferFetchSchema<typeof fetch>;
 *   // {
 *   //   '/users': {
 *   //     GET: {
 *   //      response: { 200: { body: User[] } };
 *   //    };
 *   // };
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
 */
type InferFetchSchema<FetchInstance> = FetchInstance extends Fetch<infer Schema> ? Schema : never;

/**
 * Creates a {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch fetch instance} typed with an HTTP
 * schema, closely compatible with the {@link https://developer.mozilla.org/docs/Web/API/Fetch_API native Fetch API}. All
 * requests and responses are typed by default with the schema, including methods, paths, status codes, parameters, and
 * bodies.
 *
 * Requests sent by the fetch instance have their URL automatically prefixed with the base URL of the instance.
 * {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch.defaults Default options} are also applied to the
 * requests, if provided.
 *
 * @example
 *   import { type HttpSchema } from '@zimic/http';
 *   import { createFetch } from '@zimic/fetch';
 *
 *   interface User {
 *     id: string;
 *     username: string;
 *   }
 *
 *   type Schema = HttpSchema<{
 *     '/users': {
 *       POST: {
 *         request: {
 *           headers: { 'content-type': 'application/json' };
 *           body: { username: string };
 *         };
 *         response: {
 *           201: { body: User };
 *         };
 *       };
 *
 *       GET: {
 *         request: {
 *           searchParams: {
 *             query?: string;
 *             page?: number;
 *             limit?: number;
 *           };
 *         };
 *         response: {
 *           200: { body: User[] };
 *         };
 *       };
 *     };
 *   }>;
 *
 *   const fetch = createFetch<Schema>({
 *     baseURL: 'http://localhost:3000',
 *   });
 *
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#createfetch `createFetch(options)` API reference}
 * @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
 */
declare function createFetch<Schema extends HttpSchema>(options: FetchOptions<Schema>): Fetch<Schema>;

export { Fetch, type FetchDefaults, type FetchInput, type FetchOptions, FetchRequest, type FetchRequestConstructor, FetchRequestInit, type FetchRequestObject, FetchResponse, FetchResponseError, type FetchResponseErrorObject, FetchResponseErrorObjectOptions, type FetchResponseObject, type FetchResponsePerStatusCode, type InferFetchSchema, type JSONStringified, createFetch };
