export declare type ApiFunction = (...args: any[]) => Promise<WithHeaders<ApiResponse>>;

export declare type ApiResponse = {
    status: number;
    data?: any;
};

export declare type Args<T> = T extends (...args: infer U) => any ? U : any;

export declare type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer V> ? V : never;

export declare type CustomHeaders = Record<string, string | null | boolean | number | undefined>;

/**
 * Type to access a response's data property for a given status.
 */
export declare type DataType<T extends ApiResponse, S extends number> = T extends {
    status: S;
} ? T["data"] : never;

export declare type Defaults<Headers extends RequestOpts["headers"] = CustomHeaders> = Omit<RequestOpts, "headers" | "baseUrl"> & {
    baseUrl: string;
    headers: Headers;
};

declare type FetchRequestOpts = RequestOpts & {
    body?: string | FormData | Blob;
};

declare type FormRequestOpts = RequestOpts & {
    body?: Record<string, any>;
};

export declare type FunctionReturnType<T> = T extends (...args: any[]) => any ? ReturnType<T> : never;

/**
 * Utility function to handle different status codes.
 *
 * Example:
 *
 * const userId = await handle(api.register({ email, password }), {
 *   200: (user: User) => user.id,
 *   400: (err: string) => console.log(err),
 * })
 **/
export declare function handle<T extends WithHeaders<ApiResponse>, H extends ResponseHandler<T>>(promise: Promise<T>, handler: H): Promise<FunctionReturnType<H[keyof H]>>;

export declare class HttpError extends Error {
    status: number;
    data?: any;
    headers: Headers;
    constructor(status: number, data: any, headers: Headers);
}

declare type JsonRequestOpts = RequestOpts & {
    body?: any;
};

declare function mergeHeaders(base: HeadersInit | CustomHeaders | undefined, overwrite?: HeadersInit | CustomHeaders): Headers;

declare type MultipartRequestOpts = RequestOpts & {
    body?: Record<string, unknown>;
};

/**
 * Utility function to directly return any successful response
 * and throw a HttpError otherwise.
 *
 * Example:
 *
 * try {
 *   const userId = await ok(api.register({ email, password }));
 * }
 * catch (err) {
 *   console.log(err.status)
 * }
 */
export declare function ok<T extends WithHeaders<ApiResponse>>(promise: Promise<T>): Promise<SuccessResponse<T>>;

export declare type Okify<T extends ApiFunction> = (...args: Args<T>) => Promise<OkResponse<T>>;

/**
 * Utility function to wrap an API function with `ok(...)`.
 */
export declare function okify<T extends ApiFunction>(fn: T): Okify<T>;

export declare type OkResponse<T extends ApiFunction> = SuccessResponse<AsyncReturnType<T>>;

/**
 * Utility to `okify` each function of an API.
 */
export declare function optimistic<T extends Record<string, ApiFunction | unknown>>(api: T): OptimisticApi<T>;

declare type OptimisticApi<T> = {
    [K in keyof T]: T[K] extends ApiFunction ? Okify<T[K]> : T[K];
};

export declare type RequestOpts = {
    baseUrl?: string;
    fetch?: typeof fetch;
    /** @deprecated Use RequestOpts.FormData instead */
    formDataConstructor?: new () => FormData;
    FormData?: new () => FormData;
    headers?: HeadersInit | CustomHeaders;
} & Omit<RequestInit, "body" | "headers">;

/**
 * Object with methods to handle possible status codes of an ApiResponse.
 */
export declare type ResponseHandler<T extends ApiResponse> = {
    [P in T["status"]]?: (res: DataType<T, P>) => any;
} & {
    default?: (status: number, data: any) => any;
};

export declare function runtime(defaults?: RequestOpts): {
    ok: typeof ok;
    fetchText: (url: string, req?: FetchRequestOpts) => Promise<{
        status: number;
        headers: Headers;
        contentType: string | null;
        data: string | undefined;
    }>;
    fetchJson: <T extends ApiResponse>(url: string, req?: FetchRequestOpts) => Promise<WithHeaders<T>>;
    fetchBlob: <T extends ApiResponse>(url: string, req?: FetchRequestOpts) => Promise<WithHeaders<T>>;
    mergeHeaders: typeof mergeHeaders;
    json({ body, headers, ...req }: JsonRequestOpts): {
        headers: Headers;
        body?: string | undefined;
        baseUrl?: string;
        fetch?: typeof fetch;
        /** @deprecated Use RequestOpts.FormData instead */
        formDataConstructor?: new () => FormData;
        FormData?: new () => FormData;
        cache?: RequestCache | undefined;
        credentials?: RequestCredentials | undefined;
        integrity?: string | undefined;
        keepalive?: boolean | undefined;
        method?: string | undefined;
        mode?: RequestMode | undefined;
        priority?: RequestPriority | undefined;
        redirect?: RequestRedirect | undefined;
        referrer?: string | undefined;
        referrerPolicy?: ReferrerPolicy | undefined;
        signal?: (AbortSignal | null) | undefined;
        window?: null | undefined;
    };
    form({ body, headers, ...req }: FormRequestOpts): {
        headers: Headers;
        body?: string | undefined;
        baseUrl?: string;
        fetch?: typeof fetch;
        /** @deprecated Use RequestOpts.FormData instead */
        formDataConstructor?: new () => FormData;
        FormData?: new () => FormData;
        cache?: RequestCache | undefined;
        credentials?: RequestCredentials | undefined;
        integrity?: string | undefined;
        keepalive?: boolean | undefined;
        method?: string | undefined;
        mode?: RequestMode | undefined;
        priority?: RequestPriority | undefined;
        redirect?: RequestRedirect | undefined;
        referrer?: string | undefined;
        referrerPolicy?: ReferrerPolicy | undefined;
        signal?: (AbortSignal | null) | undefined;
        window?: null | undefined;
    };
    multipart({ body, headers, ...req }: MultipartRequestOpts): {
        body: undefined;
        headers: Headers;
        baseUrl?: string;
        fetch?: typeof fetch;
        /** @deprecated Use RequestOpts.FormData instead */
        formDataConstructor?: new () => FormData;
        FormData?: new () => FormData;
        cache?: RequestCache | undefined;
        credentials?: RequestCredentials | undefined;
        integrity?: string | undefined;
        keepalive?: boolean | undefined;
        method?: string | undefined;
        mode?: RequestMode | undefined;
        priority?: RequestPriority | undefined;
        redirect?: RequestRedirect | undefined;
        referrer?: string | undefined;
        referrerPolicy?: ReferrerPolicy | undefined;
        signal?: (AbortSignal | null) | undefined;
        window?: null | undefined;
    } | {
        body: FormData;
        headers: Headers;
        baseUrl?: string;
        fetch?: typeof fetch;
        /** @deprecated Use RequestOpts.FormData instead */
        formDataConstructor?: new () => FormData;
        FormData?: new () => FormData;
        cache?: RequestCache | undefined;
        credentials?: RequestCredentials | undefined;
        integrity?: string | undefined;
        keepalive?: boolean | undefined;
        method?: string | undefined;
        mode?: RequestMode | undefined;
        priority?: RequestPriority | undefined;
        redirect?: RequestRedirect | undefined;
        referrer?: string | undefined;
        referrerPolicy?: ReferrerPolicy | undefined;
        signal?: (AbortSignal | null) | undefined;
        window?: null | undefined;
    };
};

export declare const SUCCESS_CODES: readonly [200, 201, 202, 204];

export declare type SuccessCodes = (typeof SUCCESS_CODES)[number];

export declare type SuccessResponse<T extends ApiResponse> = DataType<T, SuccessCodes>;

export declare type WithHeaders<T extends ApiResponse> = T & {
    headers: Headers;
};

export { }
