/// <reference types="node" />
import type { AxiosResponse } from 'axios';
import { ServiceName, ServiceWithEndpoint, Service, Maybe } from '../utils';
export declare type GetServicesInput = (() => Promise<ServiceName[]>) | ((config: {
    verbose: false;
}) => Promise<ServiceName[]>) | ((config: {
    verbose: true;
}) => Promise<ServiceWithEndpoint[]>) | ((config: {
    verbose: boolean;
}) => Promise<ServiceName[] | ServiceWithEndpoint[]>);
interface GetServices {
    (): Promise<ServiceName[]>;
    (config: {
        verbose: false;
    }): Promise<ServiceName[]>;
    (config: {
        verbose: true;
    }): Promise<ServiceWithEndpoint[]>;
    (config: {
        verbose: boolean;
    }): Promise<Service[]>;
}
export interface Decision {
    stage: string;
    val?: unknown;
}
export declare type Backup = {
    block_difference: number;
    version: string;
};
export interface ServiceSelectionConfig {
    blacklist?: Set<string> | undefined | null;
    whitelist?: Set<string> | undefined | null;
    getServices: GetServicesInput;
    maxConcurrentRequests?: number;
    requestTimeout?: Maybe<number>;
    unhealthyTTL?: number;
    backupsTTL?: number;
}
/**
 * A class that assists with autoselecting services.
 * `ServiceSelection` is intended to be overridden with further
 * business logic that a particular sevice might preference.
 *
 * The general use case is as follows:
 *
 * ```
 *
 * const selector = new ServiceSelection({
 *   getServices: ethContracts.getDiscoveryProviders()
 * })
 *
 * const service = await selector.select()
 *
 * ```
 *
 * This class operates by taking a list of services and
 * round-robin makes requests at them until a suitable one is found.
 *
 * Two types of "bad" services are defined below:
 *  - Unhealthy: this service is bad and should not be used
 *  - Backup: this service is bad, but if we can't find anything better, maybe use it
 *
 * Classes that extend `ServiceSelection` can choose to implement custom logic on top
 * of them and is generally how this class is intended to be used.
 */
export declare class ServiceSelection {
    blacklist: Set<string> | undefined | null;
    whitelist: Set<string> | undefined | null;
    getServices: GetServices;
    maxConcurrentRequests: number;
    requestTimeout: number;
    unhealthyTTL: number;
    backupsTTL: number;
    unhealthy: Set<string>;
    backups: Record<string, Backup>;
    totalAttempts: number;
    decisionTree: Decision[];
    unhealthyCleanupTimeout: NodeJS.Timeout | null;
    backupCleanupTimeout: NodeJS.Timeout | null;
    constructor({ blacklist, whitelist, getServices, maxConcurrentRequests, requestTimeout, // 30s
    unhealthyTTL, // 1 hour
    backupsTTL }: ServiceSelectionConfig);
    /**
     * Selects a service
     * @param reset if reset is true, clear the decision tree
     */
    select(reset?: any): Promise<any>;
    /**
     * Finds all selectable services (respecting whitelist, health checks & timeouts).
     * Note: this method is potentially slow.
     * If you need just a single service, prefer calling `.select()`
     * @param {boolean} verbose whether or not to return full services metadata
     * @param {Set} whitelist a whitelist to override the set of endpoints
     */
    findAll({ verbose, whitelist }?: {
        verbose?: boolean | undefined;
        whitelist?: Set<string> | null | undefined;
    }): Promise<(Service | null)[]>;
    /** Triggers a clean up of unhealthy and backup services so they can be retried later */
    triggerCleanup(): void;
    clearUnhealthy(): void;
    clearBackups(): void;
    /** A short-circuit. If overriden, can be used to skip selection (which could be slow) */
    shortcircuit(): Promise<null | string>;
    /**
     * Filter out services that are in the blacklist
     * @param services endpoints
     */
    filterFromBlacklist(services: string[]): string[];
    /** Filter down services to those in the whitelist */
    filterToWhitelist(services: string[]): string[];
    /** Filter out known unhealthy services from the provided */
    filterOutKnownUnhealthy(services: string[]): string[];
    /** Given a list of services, samples maxConcurrentRequests from them */
    getSelectionRound(services: string[]): string[];
    /** Gets the total number of attempts we've made this instantiation */
    getTotalAttempts(): number;
    /** Where does the health check for this type of service live */
    static getHealthCheckEndpoint(service: string): string;
    /**
     * What the criteria is for a healthy service
     * @param response axios response
     * @param {{ [key: string]: string}} urlMap health check urls mapped to their cannonical url
     * e.g. https://discoveryprovider.audius.co/health_check => https://discoveryprovider.audius.co
     */
    isHealthy(response: AxiosResponse, _urlMap: Record<string, Service>): boolean;
    /** Races requests against each other with provided timeouts and health checks */
    race(services: string[]): Promise<{
        best: null;
        errored: (string | undefined)[];
    }>;
    /** Adds a service to the unhealthy set */
    addUnhealthy(service: ServiceName): void;
    /** Gets unhealthy set size */
    getUnhealthySize(): number;
    /**
     * Removes from unhealthy set
     * @param key service endpoint
     */
    removeFromUnhealthy(key: string): void;
    /**
     * Adds a service to the list of backups
     * @param service the service to add
     * @param response the services response. This can be used to weigh various
     * backups against eachother
     */
    addBackup(service: string, response: Backup): void;
    /**
     * Controls how a backup is picked. Overriding methods may choose to use the backup's response.
     * e.g. pick a backup that's the fewest versions behind
     */
    selectFromBackups(): Promise<string | undefined>;
    /**
     * Removes from backups
     * @param key service endpoint
     */
    removeFromBackups(key: string): void;
    /**
     * Returns the size of backups
     */
    getBackupsSize(): number;
}
export {};
