/**
 * Notes about these type definitions:
 *
 * - Callbacks returning multiple completion values using multiple arguments are not supported by these types.
 *   Prefer to use Node's style by grouping your values in a single object or array.
 *   Support for this kind of callback is blocked by Microsoft/TypeScript#5453
 *
 * - For ease of use, `asyncDone` lets you pass callback functions with a result type `T` instead of `T | undefined`.
 *   This matches Node's types but can lead to unsound code being typechecked.
 *
 *   The following code typechecks but fails at runtime:
 *   ```typescript
 *   async function getString(): Promise<string> {
 *     return "Hello, World!";
 *   }
 *
 *   async function evilGetString(): Promise<string> {
 *     throw new Error("Hello, World!");
 *   }
 *
 *   function cb(err: Error | null, result: string): void {
 *     // This is unsound because `result` is `undefined` when `err` is not `null`.
 *     console.log(result.toLowerCase());
 *   }
 *
 *   asyncDone(getString, cb); // Prints `hello, world!`
 *   asyncDone(evilGetString, cb); // Runtime error: `TypeError: Cannot read property 'toLowerCase' of undefined`
 *   ```
 *
 *   Enforcing stricter callbacks would require developers to use `result?: string` and assert the existence
 *   of the result either by checking it directly or using the `!` assertion operator after testing for errors.
 *   ```typescript
 *   function stricterCb1(err: Error | null, result?: string): void {
 *     if (err !== null) {
 *       console.error(err);
 *       return;
 *     }
 *     console.log(result!.toLowerCase());
 *   }
 *
 *   function stricterCb2(err: Error | null, result?: string): void {
 *     if (result === undefined) {
 *       console.error("Undefined result. Error:);
 *       console.error(err);
 *       return;
 *     }
 *     console.log(result.toLowerCase());
 *   }
 *   ```
 */
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { Stream } from 'stream';

declare namespace asyncDone {
  /**
   * Represents a callback function used to signal the completion of a
   * task without any result value.
   */
  type VoidCallback = (err: Error | null | void) => void;

  /**
   * Represents a callback function used to signal the completion of a
   * task with a single result value.
   */
  interface Callback<T> {
    (err: null, result: T): void;

    // Use `result?: T` or `result: undefined` to require the consumer to assert the existence of the result
    // (even in case of success). See comment at the top of the file.
    (err: Error, result?: any): void;
  }

  /**
   * Minimal `Observable` interface compatible with `async-done`.
   *
   * @see https://github.com/ReactiveX/rxjs/blob/c3c56867eaf93f302ac7cd588034c7d8712f2834/src/internal/Observable.ts#L77
   */
  interface Observable<T = any> {
    subscribe(
      next?: (value: T) => void,
      error?: (error: any) => void,
      complete?: () => void
    ): any;
  }

  /**
   * Represents an async operation.
   */
  export type AsyncTask<R = any> =
    | ((done: VoidCallback) => void)
    | ((done: Callback<R>) => void)
    | (() =>
        | ChildProcess
        | EventEmitter
        | Observable<R>
        | PromiseLike<R>
        | Stream);
}

/**
 * Takes a function to execute (`fn`) and a function to call on completion (`callback`).
 *
 * @param fn Function to execute.
 * @param callback Function to call on completion.
 */
declare function asyncDone<R = any>(
  fn: asyncDone.AsyncTask<R>,
  callback: asyncDone.Callback<R>
): void;

export = asyncDone;
