/**
 * Various utilities and convenience functions for working with promises.
 *
 * @since 4.2
 */

/**
 * Callback to be called for each entry of an array to be filtered.
 *
 * @param value - The value of the array entry.
 * @param index - The index of the entry within the array.
 * @returns A promise that resolves to a boolean value, indicating whether the entry should be kept
 *   in the filtered array.
 */
export type FilterPredicateCallback<T> = (value: T, index: number) => Promise<boolean>;

/**
 * A convenience utility method for filtering an array of values using an asynchronous predicate function.
 *
 * @param input - The array of input values to filter.
 * @param predicate - A predicate function returning a promise.
 *   Only array entries for which the returned promise contains `true` are kept.
 * @returns A promise containing an array of values that passed the predicate check.
 */
export function filter<T>(input: T[], predicate: FilterPredicateCallback<T>): Promise<T[]>;

/** The result object for a promise passed to [eachAlways()](https://developers.arcgis.com/javascript/latest/references/core/core/promiseUtils/#eachAlways). */
export interface EachAlwaysResult<T> {
  /** The promise that has been fulfilled. */
  promise: Promise<T>;
  /**
   * The value with which the promise resolved. Defined
   *   only if the promise resolved.
   */
  value?: T;
  /**
   * The error with which the promise rejected. Defined
   *   only if the promise rejected.
   */
  error?: any;
}

/** Abort options that can be passed into an asynchronous method to allow termination. */
export interface AbortOptions {
  /**
   * Signal object that can be used to abort the asynchronous task.
   *   The returned promise will be rejected with an [Error](https://developers.arcgis.com/javascript/latest/references/core/core/Error/) named `AbortError` when an abort is signaled.
   *   See also [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) for more information on how to construct a controller that
   *   can be used to deliver abort signals.
   *
   * Example:
   * ```js
   * const controller = new AbortController();
   * const signal = controller.signal;
   *
   * esriRequest(url, { signal })
   *   .then((response) => {
   *     // The request went OK
   *   })
   *   .catch((err) => {
   *     if (err.name === 'AbortError') {
   *       console.log('Request aborted');
   *     } else {
   *       console.error('Error encountered', err);
   *     }
   *   });
   *
   * // Abort requests that are aware of the controller's signal
   * controller.abort();
   * ```
   */
  signal?: AbortSignal | null;
}

/**
 * Creates a special error object which is used to signal aborted requests in promise chains. Promises that are rejected
 * due to [abort signals](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) should reject with this kind
 * of error.
 *
 * @returns A special error object that signals an aborted request.
 * @example
 * // Request multiple files and return results in an array
 * function requestMultiple(urls, abortSignal) {
 *   // Fire off requests
 *   let promises = urls.map(url => request(url, { signal: abortSignal });
 *
 *   // Wait until all requests have either resolved or rejected
 *   promiseUtils.eachAlways(urls)
 *     .then(results => {
 *       if (abortSignal && abortSignal.aborted) {
 *         // If the user has triggered the abortSignal, all requests will react and reject. eachAlways resolves with
 *         // an array that contains the reject error for each request. Instead of returning this array as a result, we
 *         // should reject the promise returned to the user with an abort error.
 *         throw promiseUtils.createAbortError();
 *       }
 *       return results;
 *   });
 * }
 */
export function createAbortError(): Error;

/**
 * Check if the provided error object is the special kind of error with which promises are rejected when they are
 * aborted.
 *
 * @param error - The error object to test.
 * @returns `true` if the error is an abort error, `false` otherwise.
 * @example
 * // This function fetches a document via HTTP and logs its content to the console once available
 * function logHTTPDocumentToConsole(url, abortSignal) {
 *   // Pass the abort signal into `request` to make it cancelable
 *   request(url, { signal: abortSignal })
 *     .then((response) => {
 *       console.log(response.data);
 *     })
 *     .catch((error) => {
 *       if (!promiseUtils.isAbortError(error)) {
 *         // Only log request failures and ignore cancellations
 *         console.error("request error", error);
 *       }
 *     });
 * }
 *
 * let controller = new AbortController();
 *
 * let url = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer";
 * logHTTPDocumentToConsole(url, controller.signal);
 *
 * // Cancel the request
 * controller.abort();
 */
export function isAbortError(error: Error): boolean;

/**
 * Catches and "drops" abort errors. All other errors are re-thrown. The returned promise resolves with `undefined` if
 * an abort error occurred.
 *
 * @param promise - The promise to catch abort errors on.
 * @returns A promise that resolves with the original promise's value, or `undefined` if an abort error occurred.
 * @since 4.32
 */
export function ignoreAbortErrors<T>(promise: Promise<T>): Promise<T | undefined>;

/**
 * Convenience utility method to wait for a number of promises to either resolve or
 * reject. The resulting promise resolves to an array of
 * [result objects](https://developers.arcgis.com/javascript/latest/references/core/core/promiseUtils/#EachAlwaysResult) containing
 * the promise and either a value if the promise resolved, or an error if the
 * promise rejected. This is similar to the native [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)
 * method with the additional feature of taking an object parameter.
 *
 * @param promises - Array of promises, or object where each
 *   property is a promise.
 * @returns If called
 * with an array of promises, resolves to an array of
 * [result objects](https://developers.arcgis.com/javascript/latest/references/core/core/promiseUtils/#EachAlwaysResult)
 * containing the original promise and either a value (if the promise resolved)
 * or an error (if the promise rejected). If called with an object where each
 * property is a promise, then resolves to an object with the same properties
 * where each property value is a
 * [result object](https://developers.arcgis.com/javascript/latest/references/core/core/promiseUtils/#EachAlwaysResult).
 * @example
 * const controller = new AbortController();
 *
 * // Query for the number of features from
 * // multiple feature layers
 * function queryLayerFeatureCount(whereClauses) {
 *   // pass each whereClause item into callback function
 *   return promiseUtils.eachAlways(whereClauses.map(function (whereClause) {
 *     return layer.queryFeatureCount(whereClause, {
 *       signal: controller.signal
 *     });
 *   }));
 * }
 *
 * queryLayerFeatureCount(whereClauses).then(function(eachAlwaysResults) {
 *    eachAlwaysResults.forEach(function(result) {
 *      // If a Promise was rejected, you can check for the rejected error
 *      if (result.error) {
 *        console.log("There was an error in your query.", result.error);
 *      }
 *      // The results of the Promise are returned in the value property
 *      else {
 *        console.log("The number of features are: " + result.value);
 *      }
 *    });
 * });
 */
export function eachAlways(promises: Promise<any>[] | any): Promise<EachAlwaysResult<any>[] | any>;

/**
 * A utility for ensuring an input function is not simultaneously invoked more than once at a time. This
 * is useful for highly interactive applications such as those that execute statistic queries on mouse-move
 * or mouse-drag events. Rather than execute the query for each such event, you can "debounce", or cancel
 * the function execution, until the previous execution of the same function call finishes. This improves
 * the performance and user experience of such applications.
 *
 * @param callback - A function to prevent executing during the execution of a previous call
 *   to the same function. This is typically a function that may be called on mouse-move or mouse-drag events.
 * @returns A function with the same signature as the callback.
 * @since 4.12
 * @see [Samples that use debounce() method](https://developers.arcgis.com/javascript/latest/sample-code/?search=debounce)
 * @example
 * // Queries a layer for the count of features that intersect a
 * // 1 km buffer of the pointer location. The debounce() method
 * // prevents the updateStatistics function from executing before
 * // a previous invocation of the same function finishes.
 * const updateStatistics = promiseUtils.debounce(function (geometry) {
 *   return layerView.queryFeatures({
 *     geometry,
 *     distance: 1,
 *     units: "kilometers",
 *     outStatistics: [{
 *       onStatisticField: "*",
 *       outStatisticFieldName: "feature_count",
 *       statisticType: "count"
 *     }]
 *   }).then(function(featureSet) {
 *     console.log(`Feature Count: ${featureSet.features[0].attributes["feature_count"]}`);
 *   });
 * });
 *
 * view.on("drag", (event) => updateStatistics(view.toMap(event)));
 */
export function debounce<T>(callback: T): T;