// The following TSLint rules have been disabled:
// unified-signatures: Because there is useful information in the argument names of the overloaded signatures

// Convention:
// Use 'union types' when:
//  - parameter types have similar signature type (i.e. 'string | ReadonlyArray<string>')
//  - parameter names have the same semantic meaning (i.e. ['command', 'commands'] , ['key', 'keys'])
//    An example for not using 'union types' is the declaration of 'env' where `prefix` and `enable` parameters
//    have different semantics. On the other hand, in the declaration of 'usage', a `command: string` parameter
//    has the same semantic meaning with declaring an overload method by using `commands: ReadonlyArray<string>`,
//    thus it's preferred to use `command: string | ReadonlyArray<string>`
// Use parameterless declaration instead of declaring all parameters optional,
// when all parameters are optional and more than one

import { Configuration, DetailedArguments } from "yargs-parser";

declare namespace yargs {
    type BuilderCallback<T, R> =
        | ((args: Argv<T>) => PromiseLike<Argv<R>>)
        | ((args: Argv<T>) => Argv<R>)
        | ((args: Argv<T>) => void);

    type ParserConfigurationOptions = Configuration & {
        /** Sort commands alphabetically. Default is `false` */
        "sort-commands": boolean;
    };

    /**
     * The type parameter `T` is the expected shape of the parsed options.
     * `Arguments<T>` is those options plus `_` and `$0`, and an indexer falling
     * back to `unknown` for unknown options.
     *
     * For the return type / `argv` property, we create a mapped type over
     * `Arguments<T>` to simplify the inferred type signature in client code.
     */
    interface Argv<T = {}> {
        (): { [key in keyof Arguments<T>]: Arguments<T>[key] };
        (args: readonly string[], cwd?: string): Argv<T>;

        /**
         * Set key names as equivalent such that updates to a key will propagate to aliases and vice-versa.
         *
         * Optionally `.alias()` can take an object that maps keys to aliases.
         * Each key of this object should be the canonical version of the option, and each value should be a string or an array of strings.
         */
        // Aliases for previously declared options can inherit the types of those options.
        alias<K1 extends keyof T, K2 extends string>(
            shortName: K1,
            longName: K2 | readonly K2[],
        ): Argv<T & { [key in K2]: T[K1] }>;
        alias<K1 extends keyof T, K2 extends string>(
            shortName: K2,
            longName: K1 | readonly K1[],
        ): Argv<T & { [key in K2]: T[K1] }>;
        alias(shortName: string | readonly string[], longName: string | readonly string[]): Argv<T>;
        alias(aliases: { [shortName: string]: string | readonly string[] }): Argv<T>;

        /**
         * Get the arguments as a plain old object.
         *
         * Arguments without a corresponding flag show up in the `argv._` array.
         *
         * The script name or node command is available at `argv.$0` similarly to how `$0` works in bash or perl.
         *
         * If `yargs` is executed in an environment that embeds node and there's no script name (e.g. Electron or nw.js),
         * it will ignore the first parameter since it expects it to be the script name. In order to override
         * this behavior, use `.parse(process.argv.slice(1))` instead of .argv and the first parameter won't be ignored.
         */
        argv: { [key in keyof Arguments<T>]: Arguments<T>[key] };

        /**
         * Tell the parser to interpret `key` as an array.
         * If `.array('foo')` is set, `--foo foo bar` will be parsed as `['foo', 'bar']` rather than as `'foo'`.
         * Also, if you use the option multiple times all the values will be flattened in one array so `--foo foo --foo bar` will be parsed as `['foo', 'bar']`
         *
         * When the option is used with a positional, use `--` to tell `yargs` to stop adding values to the array.
         */
        array<K extends keyof T>(key: K | readonly K[]): Argv<Omit<T, K> & { [key in K]: ToArray<T[key]> }>;
        array<K extends string>(
            key: K | readonly K[],
        ): Argv<T & { [key in K]: Array<string | number> | undefined }>;

        /**
         * Interpret `key` as a boolean. If a non-flag option follows `key` in `process.argv`, that string won't get set as the value of `key`.
         *
         * `key` will default to `false`, unless a `default(key, undefined)` is explicitly set.
         *
         * If `key` is an array, interpret all the elements as booleans.
         */
        boolean<K extends keyof T>(key: K | readonly K[]): Argv<Omit<T, K> & { [key in K]: boolean | undefined }>;
        boolean<K extends string>(key: K | readonly K[]): Argv<T & { [key in K]: boolean | undefined }>;

        /**
         * Check that certain conditions are met in the provided arguments.
         * @param func Called with two arguments, the parsed `argv` hash and an array of options and their aliases.
         * If `func` throws or returns a non-truthy value, show the thrown error, usage information, and exit.
         * @param global Indicates whether `check()` should be enabled both at the top-level and for each sub-command.
         */
        check(func: (argv: Arguments<T>, aliases: { [alias: string]: string }) => any, global?: boolean): Argv<T>;

        /**
         * Limit valid values for key to a predefined set of choices, given as an array or as an individual value.
         * If this method is called multiple times, all enumerated values will be merged together.
         * Choices are generally strings or numbers, and value matching is case-sensitive.
         *
         * Optionally `.choices()` can take an object that maps multiple keys to their choices.
         *
         * Choices can also be specified as choices in the object given to `option()`.
         */
        choices<K extends keyof T, C extends readonly any[]>(
            key: K,
            values: C,
        ): Argv<Omit<T, K> & { [key in K]: C[number] | undefined }>;
        choices<K extends string, C extends readonly any[]>(
            key: K,
            values: C,
        ): Argv<T & { [key in K]: C[number] | undefined }>;
        choices<C extends { [key: string]: readonly any[] }>(
            choices: C,
        ): Argv<Omit<T, keyof C> & { [key in keyof C]: C[key][number] | undefined }>;

        /**
         * Provide a synchronous function to coerce or transform the value(s) given on the command line for `key`.
         *
         * The coercion function should accept one argument, representing the parsed value from the command line, and should return a new value or throw an error.
         * The returned value will be used as the value for `key` (or one of its aliases) in `argv`.
         *
         * If the function throws, the error will be treated as a validation failure, delegating to either a custom `.fail()` handler or printing the error message in the console.
         *
         * Coercion will be applied to a value after all other modifications, such as `.normalize()`.
         *
         * Optionally `.coerce()` can take an object that maps several keys to their respective coercion function.
         *
         * You can also map the same function to several keys at one time. Just pass an array of keys as the first argument to `.coerce()`.
         *
         * If you are using dot-notion or arrays, .e.g., `user.email` and `user.password`, coercion will be applied to the final object that has been parsed
         */
        coerce<K extends keyof T, V>(
            key: K | readonly K[],
            func: (arg: any) => V,
        ): Argv<Omit<T, K> & { [key in K]: V | undefined }>;
        coerce<K extends string, V>(
            key: K | readonly K[],
            func: (arg: any) => V,
        ): Argv<T & { [key in K]: V | undefined }>;
        coerce<O extends { [key: string]: (arg: any) => any }>(
            opts: O,
        ): Argv<Omit<T, keyof O> & { [key in keyof O]: ReturnType<O[key]> | undefined }>;

        /**
         * Define the commands exposed by your application.
         * @param command Should be a string representing the command or an array of strings representing the command and its aliases.
         * @param description Use to provide a description for each command your application accepts (the values stored in `argv._`).
         * Set `description` to false to create a hidden command. Hidden commands don't show up in the help output and aren't available for completion.
         * @param [builder] Object to give hints about the options that your command accepts.
         * Can also be a function. This function is executed with a yargs instance, and can be used to provide advanced command specific help.
         *
         * Note that when `void` is returned, the handler `argv` object type will not include command-specific arguments.
         * @param [handler] Function, which will be executed with the parsed `argv` object.
         */
        command<U = T>(
            command: string | readonly string[],
            description: string,
            builder?: BuilderCallback<T, U>,
            handler?: (args: Arguments<U>) => void,
            middlewares?: Array<MiddlewareFunction<U>>,
            deprecated?: boolean | string,
        ): Argv<U>;
        command<O extends { [key: string]: Options }>(
            command: string | readonly string[],
            description: string,
            builder?: O,
            handler?: (args: Arguments<InferredOptionTypes<O>>) => void,
            middlewares?: Array<MiddlewareFunction<O>>,
            deprecated?: boolean | string,
        ): Argv<T>;
        command<U>(command: string | readonly string[], description: string, module: CommandModule<T, U>): Argv<U>;
        command<U = T>(
            command: string | readonly string[],
            showInHelp: false,
            builder?: BuilderCallback<T, U>,
            handler?: (args: Arguments<U>) => void,
            middlewares?: Array<MiddlewareFunction<U>>,
            deprecated?: boolean | string,
        ): Argv<T>;
        command<O extends { [key: string]: Options }>(
            command: string | readonly string[],
            showInHelp: false,
            builder?: O,
            handler?: (args: Arguments<InferredOptionTypes<O>>) => void,
        ): Argv<T>;
        command<U>(command: string | readonly string[], showInHelp: false, module: CommandModule<T, U>): Argv<U>;
        command<U>(module: CommandModule<T, U>): Argv<U>;

        // Advanced API
        /** Apply command modules from a directory relative to the module calling this method. */
        commandDir(dir: string, opts?: RequireDirectoryOptions): Argv<T>;

        /**
         * Enable bash/zsh-completion shortcuts for commands and options.
         *
         * If invoked without parameters, `.completion()` will make completion the command to output the completion script.
         *
         * @param [cmd] When present in `argv._`, will result in the `.bashrc` or `.zshrc` completion script being outputted.
         * To enable bash/zsh completions, concat the generated script to your `.bashrc` or `.bash_profile` (or `.zshrc` for zsh).
         * @param [description] Provide a description in your usage instructions for the command that generates the completion scripts.
         * @param [func] Rather than relying on yargs' default completion functionality, which shiver me timbers is pretty awesome, you can provide your own completion method.
         */
        completion(): Argv<T>;
        completion(cmd: string, func?: AsyncCompletion