type RequestInterceptor = (config: RequestHandlerConfig) => RequestHandlerConfig | Promise<RequestHandlerConfig>;
type ResponseInterceptor = <ResponseData = unknown>(response: FetchResponse<ResponseData>) => Promise<FetchResponse<ResponseData>>;

type Method = 'get' | 'GET' | 'delete' | 'DELETE' | 'head' | 'HEAD' | 'options' | 'OPTIONS' | 'post' | 'POST' | 'put' | 'PUT' | 'patch' | 'PATCH' | 'purge' | 'PURGE' | 'link' | 'LINK' | 'unlink' | 'UNLINK';
type NativeFetch = typeof fetch;
type FetcherInstance = unknown | null;
type ErrorHandlingStrategy = 'reject' | 'silent' | 'defaultResponse' | 'softFail';
type ErrorHandlerInterceptor = (error: ResponseError) => unknown;
interface BaseRequestConfig<D = any> {
    url?: string;
    method?: Method | string;
    baseURL?: string;
    transformRequest?: Transformer | Transformer[];
    transformResponse?: Transformer | Transformer[];
    headers?: HeadersInit;
    params?: QueryParams;
    paramsSerializer?: (params: any) => string;
    data?: D;
    timeout?: number;
    timeoutErrorMessage?: string;
    withCredentials?: boolean;
    adapter?: Adapter;
    auth?: BasicCredentials;
    responseType?: ResponseType;
    xsrfCookieName?: string;
    xsrfHeaderName?: string;
    onUploadProgress?: (progressEvent: any) => void;
    onDownloadProgress?: (progressEvent: any) => void;
    maxContentLength?: number;
    validateStatus?: ((status: number) => boolean) | null;
    maxBodyLength?: number;
    maxRedirects?: number;
    socketPath?: string | null;
    httpAgent?: any;
    httpsAgent?: any;
    proxy?: ProxyConfig | false;
    cancelToken?: CancelToken;
    decompress?: boolean;
    transitional?: TransitionalOptions;
    signal?: AbortSignal;
    insecureHTTPParser?: boolean;
}
interface ExtendedResponse<T = any> extends Omit<Response, 'headers'> {
    data: T;
    error: ResponseError<T>;
    headers: HeadersObject & HeadersInit;
    config: ExtendedRequestConfig;
    request?: ExtendedRequestConfig;
}
type FetchResponse<T = any> = ExtendedResponse<T>;
interface HeadersObject {
    [key: string]: string;
}
interface ResponseError<T = any> extends Error {
    code?: string;
    isAxiosError: boolean;
    config: ExtendedRequestConfig;
    request?: ExtendedRequestConfig;
    response?: FetchResponse<T>;
    toJSON?: () => object;
}
interface Transformer {
    (data: any, headers?: HeadersInit): any;
}
interface Adapter {
    (config: BaseRequestConfig): ReturnedPromise;
}
interface BasicCredentials {
    username: string;
    password: string;
}
type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream';
interface ProxyConfig {
    host: string;
    port: number;
    protocol?: string;
    auth?: {
        username: string;
        password: string;
    };
}
interface CancelToken {
    promise: Promise<Cancel>;
    reason?: Cancel;
    throwIfRequested(): void;
}
interface Cancel {
    message: string;
}
interface TransitionalOptions {
    silentJSONParsing?: boolean;
    forcedJSONParsing?: boolean;
    clarifyTimeoutError?: boolean;
}
interface ReturnedPromise<T = any> extends Promise<FetchResponse<T>> {
}
/**
 * Interface for configuring retry options.
 */
interface RetryOptions {
    /**
     * Maximum number of retry attempts.
     * @default 0
     */
    retries?: number;
    /**
     * Delay between retries in milliseconds.
     * @default 1000
     */
    delay?: number;
    /**
     * Exponential backoff.
     * @default 1.5
     */
    backoff?: number;
    /**
     * Maximum delay between retries in milliseconds.
     * @default 30000
     */
    maxDelay?: number;
    /**
     * Retry only on specific status codes.
     * @default [
     *   408, // Request Timeout
     *   409, // Conflict
     *   425, // Too Early
     *   429, // Too Many Requests
     *   500, // Internal Server Error
     *   502, // Bad Gateway
     *   503, // Service Unavailable
     *   504, // Gateway Timeout
     * ]
     */
    retryOn: number[];
    /**
     * A function to determine whether to retry based on the error and attempt number.
     */
    shouldRetry?: <T = any>(error: ResponseError<T>, attempt: number) => Promise<boolean>;
}
interface ExtendedRequestConfig extends BaseRequestConfig, RequestInit {
    cancellable?: boolean;
    rejectCancelled?: boolean;
    defaultResponse?: unknown;
    flattenResponse?: boolean;
    retry?: RetryOptions;
    strategy?: ErrorHandlingStrategy;
    onRequest?: RequestInterceptor | RequestInterceptor[];
    onResponse?: ResponseInterceptor | ResponseInterceptor[];
    onError?: ErrorHandlerInterceptor;
    headers?: HeadersInit;
    signal?: AbortSignal;
    urlPathParams?: UrlPathParams;
    body?: (BodyInit | null) & (Record<string, any> | object);
}
interface BaseRequestHandlerConfig extends RequestConfig {
    fetcher?: FetcherInstance;
    apiUrl?: string;
    logger?: unknown;
}
type RequestConfig<CustomConfig = object> = ExtendedRequestConfig & CustomConfig;
type RequestHandlerConfig<CustomConfig = object> = BaseRequestHandlerConfig & CustomConfig;

/**
 * Generic Request Handler
 * It creates an Request Fetcher instance and handles requests within that instance
 * It handles errors depending on a chosen error handling strategy
 */
declare class RequestHandler {
    /**
     * @var requestInstance Provider's instance
     */
    requestInstance: FetcherInstance;
    /**
     * @var baseURL Base API url
     */
    baseURL: string;
    /**
     * @var timeout Request timeout
     */
    timeout: number;
    /**
     * @var cancellable Response cancellation
     */
    cancellable: boolean;
    /**
     * @var rejectCancelled Whether to reject cancelled requests or not
     */
    rejectCancelled: boolean;
    /**
     * @var strategy Request timeout
     */
    strategy: ErrorHandlingStrategy;
    /**
     * @var method Request method
     */
    method: Method | string;
    /**
     * @var flattenResponse Response flattening
     */
    flattenResponse: boolean;
    /**
     * @var defaultResponse Response flattening
     */
    defaultResponse: any;
    /**
     * @var fetcher Request Fetcher instance
     */
    protected fetcher: FetcherInstance;
    /**
     * @var logger Logger
     */
    protected logger: any;
    /**
     * @var onError HTTP error service
     */
    protected onError: any;
    /**
     * @var requestsQueue    Queue of requests
     */
    protected requestsQueue: WeakMap<object, AbortController>;
    /**
     * Request Handler Config
     */
    protected retry: RetryOptions;
    /**
     * Request Handler Config
     */
    config: RequestHandlerConfig;
    /**
     * Creates an instance of RequestHandler.
     *
     * @param {Object} config - Configuration object for the request.
     * @param {string} config.baseURL - The base URL for the request.
     * @param {Object} config.endpoints - An object containing endpoint definitions.
     * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.
     * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.
     * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.
     * @param {number} config.timeout - Request timeout
     * @param {string} config.strategy - Error Handling Strategy
     * @param {string} config.flattenResponse - Whether to flatten response "data" object within "data" one
     * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's "null" by default
     * @param {Object} [config.retry] - Options for retrying requests.
     * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.
     * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.
     * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.
     * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.
     * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.
     * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.
     * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.
     * These functions will be called with the response object after the response is received. an be used to modify or log the response data.
     * @param {Function} [config.onError] - Optional callback function for handling errors.
     * @param {Object} [config.headers] - Optional default headers to include in every request.
     * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.
     * @param {*} config.logger - Instance of custom logger. Either class or an object similar to "console". Console is used by default.
     */
    constructor({ fetcher, timeout, rejectCancelled, strategy, flattenResponse, defaultResponse, logger, onError, ...config }: RequestHandlerConfig);
    /**
     * Get Provider Instance
     *
     * @returns {FetcherInstance} Provider's instance
     */
    getInstance(): FetcherInstance;
    /**
     * Build request configuration
     *
     * @param {string} url - Request url
     * @param {QueryParamsOrBody} data - Query Params in case of GET and HEAD requests, body payload otherwise
     * @param {RequestConfig} config - Request config passed when making the request
     * @returns {RequestConfig} - Provider's instance
     */
    protected buildConfig(url: string, data: QueryParamsOrBody, config: RequestConfig): RequestConfig;
    /**
     * Process global Request Error
     *
     * @param {ResponseError} error      Error instance
     * @param {RequestConfig} requestConfig   Per endpoint request config
     * @returns {void}
     */
    protected processError(error: ResponseError, requestConfig: RequestConfig): void;
    /**
     * Output default response in case of an error, depending on chosen strategy
     *
     * @param {ResponseError} error      Error instance
     * @param {RequestConfig} requestConfig   Per endpoint request config
     * @returns {*} Error response
     */
    protected outputErrorResponse(error: ResponseError, requestConfig: RequestConfig): Promise<any>;
    /**
     * Output error response depending on chosen strategy
     *
     * @param {ResponseError} error               Error instance
     * @returns {boolean}                        True if request is aborted
     */
    isRequestCancelled(error: ResponseError): boolean;
    /**
     * Detects if a custom fetcher is utilized
     *
     * @returns {boolean}                        True if it's a custom fetcher
     */
    protected isCustomFetcher(): boolean;
    /**
     * Automatically Cancel Previous Requests using AbortController when "cancellable" is defined
     *
     * @param {RequestConfig} requestConfig   Per endpoint request config
     * @returns {Object} Controller Signal to abort
     */
    protected addCancellationToken(requestConfig: RequestConfig): Partial<Record<'signal', AbortSignal>>;
    /**
     * Handle Request depending on used strategy
     *
     * @param {string} url - Request url
     * @param {QueryParamsOrBody} data - Query Params in case of GET and HEAD requests, body payload otherwise
     * @param {RequestConfig} config - Request config
     * @throws {ResponseError}
     * @returns {Promise<ResponseData & FetchResponse<ResponseData>>} Response Data
     */
    request<ResponseData = APIResponse>(url: string, data?: QueryParamsOrBody, config?: RequestConfig): Promise<ResponseData & FetchResponse<ResponseData>>;
    /**
     * Parses the response data based on the Content-Type header.
     *
     * @param response - The Response object to parse.
     * @returns A Promise that resolves to the parsed data.
     */
    parseData<ResponseData = APIResponse>(response: FetchResponse<ResponseData>): Promise<any>;
    processHeaders<ResponseData>(response: FetchResponse<ResponseData>): HeadersObject;
    /**
     * Output response
     *
     * @param response - Response payload
     * @param {RequestConfig} requestConfig - Request config
     * @param {*} error - whether the response is erroneous
     * @returns {ResponseData | FetchResponse<ResponseData>} Response data
     */
    protected outputResponse<ResponseData = APIResponse>(response: FetchResponse<ResponseData>, requestConfig: RequestConfig, error?: any): ResponseData | FetchResponse<ResponseData>;
}

type NameValuePair = {
    name: string;
    value: string;
};
declare type QueryParams<T = unknown> = Record<string, T> | URLSearchParams | NameValuePair[] | null;
declare type BodyPayload<T = unknown> = Record<string, T> | null;
declare type QueryParamsOrBody<T = unknown> = QueryParams<T> | BodyPayload<T>;
declare type UrlPathParams<T = unknown> = Record<string, T> | null;
declare type APIResponse = unknown;
declare type Endpoint<ResponseData = APIResponse, QueryParams = QueryParamsOrBody, PathParams = UrlPathParams> = {
    (queryParams?: QueryParams, urlPathParams?: PathParams, requestConfig?: RequestConfig): Promise<ResponseData & FetchResponse<ResponseData>>;
} | {
    <ReturnedData = ResponseData, T = QueryParams, T2 = PathParams>(queryParams?: T, urlPathParams?: T2, requestConfig?: RequestConfig): Promise<ReturnedData & FetchResponse<ReturnedData>>;
};
type EndpointDefaults = Endpoint<never>;
type EndpointsRecord<EndpointsMethods> = {
    [K in keyof EndpointsMethods]: EndpointsMethods[K] extends Endpoint<infer ResponseData, infer QueryParams, infer UrlPathParams> ? Endpoint<ResponseData, QueryParams, UrlPathParams> : Endpoint<never>;
};
type DefaultEndpoints<EndpointsCfg> = {
    [K in keyof EndpointsCfg]: EndpointDefaults;
};
type EndpointsConfig<EndpointsMethods> = Record<keyof EndpointsMethods | string, RequestConfig>;
type EndpointsConfigPart<EndpointsCfg, EndpointsMethods extends object> = [
    EndpointsCfg
] extends [never] ? unknown : DefaultEndpoints<Omit<EndpointsCfg, keyof EndpointsMethods>>;
type ApiHandlerReturnType<EndpointsMethods extends object, EndpointsCfg> = EndpointsRecord<EndpointsMethods> & EndpointsConfigPart<EndpointsCfg, EndpointsMethods> & ApiHandlerMethods<EndpointsMethods>;
type ApiHandlerMethods<EndpointsMethods> = {
    config: ApiHandlerConfig<EndpointsMethods>;
    endpoints: EndpointsConfig<EndpointsMethods>;
    requestHandler: RequestHandler;
    getInstance: () => FetcherInstance;
    request: <ResponseData = APIResponse>(endpointName: keyof EndpointsMethods | string, queryParams?: QueryParams, urlPathParams?: UrlPathParams, requestConfig?: RequestConfig) => Promise<ResponseData & FetchResponse<ResponseData>>;
};
interface ApiHandlerConfig<EndpointsMethods> extends RequestHandlerConfig {
    apiUrl: string;
    endpoints: EndpointsConfig<EndpointsMethods>;
}

/**
 * Creates an instance of API Handler.
 * It creates an API fetcher function using native fetch() or Axios if it is passed as "fetcher".
 *
 * @param {Object} config - Configuration object for the API fetcher.
 * @param {string} config.apiUrl - The base URL for the API.
 * @param {Object} config.endpoints - An object containing endpoint definitions.
 * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.
 * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.
 * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.
 * @param {number} config.timeout - Request timeout
 * @param {string} config.strategy - Error Handling Strategy
 * @param {string} config.flattenResponse - Whether to flatten response "data" object within "data" one
 * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's "null" by default
 * @param {Object} [config.retry] - Options for retrying requests.
 * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.
 * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.
 * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.
 * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.
 * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.
 * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.
 * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.
 * These functions will be called with the response object after the response is received. an be used to modify or log the response data.
 * @param {Function} [config.onError] - Optional callback function for handling errors.
 * @param {Object} [config.headers] - Optional default headers to include in every request.
 * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.
 * @param {*} config.logger - Instance of custom logger. Either class or an object similar to "console". Console is used by default.
 * @returns API handler functions and endpoints to call
 *
 * @example
 * // Import axios (optional)
 * import axios from 'axios';
 *
 * // Define endpoint paths
 * const endpoints = {
 *   getUser: '/user',
 *   createPost: '/post',
 * };
 *
 * // Create the API fetcher with configuration
 * const api = createApiFetcher({
 *   fetcher: axios, // Axios instance (optional)
 *   endpoints,
 *   apiUrl: 'https://example.com/api',
 *   onError(error) {
 *     console.log('Request failed', error);
 *   },
 *   headers: {
 *     'my-auth-key': 'example-auth-key-32rjjfa',
 *   },
 * });
 *
 * // Fetch user data
 * const response = await api.getUser({ userId: 1, ratings: [1, 2] })
 */
declare function createApiFetcher<EndpointsMethods extends object, EndpointsCfg = never>(config: ApiHandlerConfig<EndpointsMethods>): ApiHandlerReturnType<EndpointsMethods, EndpointsCfg>;

/**
 * Simple wrapper for request fetching.
 * It abstracts the creation of RequestHandler, making it easy to perform API requests.
 *
 * @param {string | URL | globalThis.Request} url - Request URL.
 * @param {RequestHandlerConfig} config - Configuration object for the request handler.
 * @returns {Promise<ResponseData & FetchResponse<ResponseData>>} Response Data.
 */
declare function fetchf<ResponseData = APIResponse>(url: string, config?: RequestHandlerConfig): Promise<ResponseData & FetchResponse<ResponseData>>;

export { type APIResponse, type Adapter, type ApiHandlerConfig, type ApiHandlerMethods, type ApiHandlerReturnType, type BaseRequestConfig, type BasicCredentials, type BodyPayload, type Cancel, type CancelToken, type Endpoint, type EndpointsConfig, type ErrorHandlingStrategy, type ExtendedResponse, type FetchResponse, type FetcherInstance, type HeadersObject, type Method, type NativeFetch, type ProxyConfig, type QueryParams, type QueryParamsOrBody, type RequestConfig, type RequestHandlerConfig, type ResponseError, type ResponseType, type RetryOptions, type ReturnedPromise, type Transformer, type TransitionalOptions, type UrlPathParams, createApiFetcher, fetchf };
