import { AxiosResponse, AxiosError, AxiosRequestConfig, AxiosStatic, AxiosInstance, Method } from 'axios';
import { MagicalClass } from 'js-magic';

type RequestResponse<T = unknown> = Promise<AxiosResponse<T>>;
type ErrorHandlingStrategy = 'throwError' | 'reject' | 'silent' | 'defaultResponse';
type RequestError = AxiosError<unknown>;
interface ErrorHandlerClass {
    process(error?: RequestError): unknown;
}
type ErrorHandlerFunction = (error: RequestError) => unknown;
type EndpointsConfig<T extends string | number | symbol> = {
    [K in T]: EndpointConfig;
};
interface EndpointConfig extends AxiosRequestConfig {
    cancellable?: boolean;
    rejectCancelled?: boolean;
    strategy?: ErrorHandlingStrategy;
    onError?: ErrorHandlerFunction | ErrorHandlerClass;
}
interface RequestHandlerConfig extends EndpointConfig {
    axios: AxiosStatic;
    flattenResponse?: boolean;
    defaultResponse?: unknown;
    logger?: unknown;
    onError?: ErrorHandlerFunction | ErrorHandlerClass;
}
interface APIHandlerConfig<EndpointsList = {
    [x: string]: unknown;
}> extends RequestHandlerConfig {
    apiUrl: string;
    endpoints: EndpointsConfig<keyof EndpointsList>;
}
interface RequestData {
    type: string;
    url: string;
    data?: unknown;
    config: EndpointConfig;
}

declare type APIQueryParams = Record<string, unknown>;
declare type APIUrlParams = Record<string, unknown>;
declare type APIRequestConfig = EndpointConfig;
declare type APIResponse = unknown;
declare type Endpoint<Response = APIResponse, QueryParamsOrData = APIQueryParams, DynamicUrlParams = APIUrlParams> = <ResponseData = Response, QueryParams = QueryParamsOrData, UrlParams = DynamicUrlParams>(queryParams?: QueryParams | null, urlParams?: UrlParams, requestConfig?: EndpointConfig) => Promise<ResponseData>;
interface Endpoints {
    [x: string]: Endpoint;
}

/**
 * Generic Request Handler
 * It creates an Axios instance and handles requests within that instance
 * It handles errors depending on a chosen error handling strategy
 */
declare class RequestHandler implements MagicalClass {
    /**
     * @var requestInstance Provider's instance
     */
    requestInstance: AxiosInstance;
    /**
     * @var timeout Request timeout
     */
    timeout: number;
    /**
     * @var cancellable Response cancellation
     */
    cancellable: boolean;
    /**
     * @var strategy Request timeout
     */
    strategy: ErrorHandlingStrategy;
    /**
     * @var flattenResponse Response flattening
     */
    flattenResponse: boolean;
    /**
     * @var defaultResponse Response flattening
     */
    defaultResponse: any;
    /**
     * @var axios Axios instance
     */
    protected axios: AxiosStatic;
    /**
     * @var logger Logger
     */
    protected logger: any;
    /**
     * @var requestErrorService HTTP error service
     */
    protected requestErrorService: any;
    /**
     * @var requestsQueue    Queue of requests
     */
    protected requestsQueue: Map<string, AbortController>;
    /**
     * Creates an instance of HttpRequestHandler
     *
     * @param {string} config.axios                Axios instance
     * @param {string} config.baseURL              Base URL for all API calls
     * @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.logger                    Instance of Logger Class
     * @param {*} config.requestErrorService   Instance of Error Service Class
     */
    constructor({ axios, baseURL, timeout, cancellable, strategy, flattenResponse, defaultResponse, logger, onError, ...config }: RequestHandlerConfig);
    /**
     * Get Provider Instance
     *
     * @returns {AxiosInstance} Provider's instance
     */
    getInstance(): AxiosInstance;
    /**
     * Maps all API requests
     *
     * @private
     * @param {string} url                  Url
     * @param {*} data                      Payload
     * @param {EndpointConfig} config       Config
     * @throws {RequestError}               If request fails
     * @returns {Promise}                   Request response or error info
     */
    __get(prop: string): any;
    /**
     * Prepare Request
     *
     * @param {string} url                  Url
     * @param {*} data                      Payload
     * @param {EndpointConfig} config       Config
     * @throws {RequestError}               If request fails
     * @returns {Promise}                   Request response or error info
     */
    prepareRequest(type: Method, url: string, data?: any, config?: EndpointConfig): Promise<RequestResponse>;
    /**
     * Build request configuration
     *
     * @param {string} method               Request method
     * @param {string} url                  Request url
     * @param {*}      data                 Request data
     * @param {EndpointConfig} config       Request config
     * @returns {AxiosInstance}             Provider's instance
     */
    protected buildRequestConfig(method: string, url: string, data: any, config: EndpointConfig): EndpointConfig;
    /**
     * Process global Request Error
     *
     * @param {RequestError} error      Error instance
     * @param {EndpointConfig} requestConfig   Per endpoint request config
     * @returns {AxiosInstance} Provider's instance
     */
    protected processRequestError(error: RequestError, requestConfig: EndpointConfig): void;
    /**
     * Output error response depending on chosen strategy
     *
     * @param {RequestError} error      Error instance
     * @param {EndpointConfig} requestConfig   Per endpoint request config
     * @returns {AxiosInstance} Provider's instance
     */
    protected outputErrorResponse(error: RequestError, requestConfig: EndpointConfig): Promise<RequestResponse>;
    /**
     * Output error response depending on chosen strategy
     *
     * @param {RequestError} error                     Error instance
     * @param {EndpointConfig} _requestConfig    Per endpoint request config
     * @returns {*}                             Error response
     */
    isRequestCancelled(error: RequestError, _requestConfig: EndpointConfig): boolean;
    /**
     * Automatically Cancel Previous Requests
     *
     * @param {EndpointConfig} requestConfig   Per endpoint request config
     * @returns {AxiosInstance} Provider's instance
     */
    protected addCancellationToken(requestConfig: EndpointConfig): {
        signal?: undefined;
    } | {
        signal: AbortSignal;
    };
    /**
     * Handle Request depending on used strategy
     *
     * @param {object} payload                      Payload
     * @param {string} payload.type                 Request type
     * @param {string} payload.url                  Request url
     * @param {*} payload.data                      Request data
     * @param {EndpointConfig} payload.config       Request config
     * @throws {RequestError}
     * @returns {Promise} Response Data
     */
    protected handleRequest({ type, url, data, config, }: RequestData): Promise<RequestResponse>;
    /**
     * Process request response
     *
     * @param response Response object
     * @returns {*} Response data
     */
    protected processResponseData(response: any): any;
}

/**
 * Handles dispatching of API requests
 */
declare class ApiHandler<EndpointsList = {
    [x: string]: unknown;
}> implements MagicalClass {
    /**
     * TS Index signature
     */
    [x: string]: unknown;
    /**
     * @var requestHandler Request Wrapper Instance
     */
    requestHandler: RequestHandler;
    /**
     * Endpoints
     */
    protected endpoints: EndpointsConfig<string>;
    /**
     * Logger
     */
    protected logger: any;
    /**
     * Creates an instance of API Handler
     *
     * @param {string} config.apiUrl               Base URL for all API calls
     * @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.logger                    Instance of Logger Class
     * @param {*} config.onError                   Instance of Error Service Class
     */
    constructor({ axios, apiUrl, endpoints, timeout, cancellable, strategy, flattenResponse, defaultResponse, logger, onError, ...config }: APIHandlerConfig<EndpointsList>);
    /**
     * Get Provider Instance
     *
     * @returns {AxiosInstance} Provider's instance
     */
    getInstance(): AxiosInstance;
    /**
     * Maps all API requests
     *
     * @private
     * @param {*} prop          Caller
     * @returns {Function}      Tailored request function
     */
    __get(prop: any): any;
    /**
     * Handle Single API Request
     *
     * @param {*} args      Arguments
     * @returns {Promise}   Resolvable API provider promise
     */
    handleRequest(...args: string[]): Promise<RequestResponse>;
    /**
     * Triggered when trying to use non-existent endpoints
     *
     * @param prop Method Name
     * @returns {Promise}
     */
    protected handleNonImplemented(prop: string): Promise<null>;
}
declare const createApiFetcher: <AllEndpointsList = {
    [x: string]: unknown;
}>(options: APIHandlerConfig<AllEndpointsList>) => ApiHandler & AllEndpointsList;

declare class RequestErrorHandler {
    /**
     * Logger Class
     *
     * @type {*}
     * @memberof RequestErrorHandler
     */
    protected logger: any;
    /**
     * Error Service Class
     *
     * @type {*}
     * @memberof RequestErrorHandler
     */
    requestErrorService: any;
    constructor(logger: any, requestErrorService: any);
    /**
     * Process and Error
     *
     * @param {*} error Error instance or message
     * @throws          Request error context
     * @returns {void}
     */
    process(error: string | Error): void;
}

export { type APIHandlerConfig, type APIQueryParams, type APIRequestConfig, type APIResponse, type APIUrlParams, ApiHandler, type Endpoint, type EndpointConfig, type Endpoints, type EndpointsConfig, type ErrorHandlingStrategy, type RequestData, type RequestError, RequestErrorHandler, RequestHandler, type RequestHandlerConfig, type RequestResponse, createApiFetcher };
