// Type definitions for Inquirer.js
// Project: https://github.com/SBoudrias/Inquirer.js
// Definitions by: Qubo <https://github.com/tkQubo>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped

/// <reference path="./rx-lite.d.ts" />
/// <reference path="./through.d.ts" />

declare module "inquirer" {
    import through = require('through');

    namespace inquirer {
        type Prompts = { [name: string]: PromptModule };
        type ChoiceType = string|objects.ChoiceOption|objects.Separator;
        type Questions = Question|Question[]|Rx.Observable<Question>;

        interface Inquirer {
            restoreDefaultPrompts(): void;
            /**
             * Expose helper functions on the top level for easiest usage by common users
             * @param name
             * @param prompt
             */
            registerPrompt(name: string, prompt: PromptModule): void;
            /**
             * Create a new self-contained prompt module.
             */
            createPromptModule(): PromptModule;
            /**
             * Public CLI helper interface
             * @param questions Questions settings array
             * @param cb Callback being passed the user answers
             * @return
             */
            prompt(questions: Questions, cb?: (answers: Answers) => any): ui.Prompt;
            prompts: Prompts;
            Separator: objects.SeparatorStatic;
            ui: {
                BottomBar: ui.BottomBar;
                Prompt: ui.Prompt;
            }
        }

        interface PromptModule {
            (questions: Questions, cb: (answers: Answers) => any): ui.Prompt;
            /**
             * Register a prompt type
             * @param name Prompt type name
             * @param prompt Prompt constructor
             */
            registerPrompt(name: string, prompt: PromptModule): ui.Prompt;
            /**
             * Register the defaults provider prompts
             */
            restoreDefaultPrompts(): void;
        }

        interface Question {
            /**
             * Type of the prompt.
             * Possible values:
             * <ul>
             *      <li>input</li>
             *      <li>confirm</li>
             *      <li>list</li>
             *      <li>rawlist</li>
             *      <li>password</li>
             * </ul>
             * @defaults: 'input'
             */
            type?: string;
            /**
             * The name to use when storing the answer in the anwers hash.
             */
            name?: string;
            /**
             * The question to print. If defined as a function,
             * the first parameter will be the current inquirer session answers.
             */
            message?: string|((answers: Answers) => string);
            /**
             * Default value(s) to use if nothing is entered, or a function that returns the default value(s).
             * If defined as a function, the first parameter will be the current inquirer session answers.
             */
            default?: any|((answers: Answers) => any);
            /**
             * Choices array or a function returning a choices array. If defined as a function,
             * the first parameter will be the current inquirer session answers.
             * Array values can be simple strings, or objects containing a name (to display) and a value properties
             * (to save in the answers hash). Values can also be a Separator.
             */
            choices?: ChoiceType[]|((answers: Answers) => ChoiceType[]);
            /**
             * Receive the user input and should return true if the value is valid, and an error message (String)
             * otherwise. If false is returned, a default error message is provided.
             */
            validate?(input: string): boolean|string;
            /**
             * Receive the user input and return the filtered value to be used inside the program.
             * The value returned will be added to the Answers hash.
             */
            filter?(input: string): string;
            /**
             * Receive the current user answers hash and should return true or false depending on whether or
             * not this question should be asked. The value can also be a simple boolean.
             */
            when?: boolean|((answers: Answers) => boolean);
            paginated?: boolean;
        }

        /**
         * A key/value hash containing the client answers in each prompt.
         */
        interface Answers {
            [key: string]: string|boolean;
        }

        namespace ui {
            /**
             * Base interface class other can inherits from
             */
            interface Prompt extends BaseUI<Prompts> {
                new(promptModule: Prompts): Prompt;
                /**
                 * Once all prompt are over
                 */
                onCompletion(): void;
                processQuestion(question: Question): any;
                fetchAnswer(question: Question): any;
                setDefaultType(question: Question): any;
                filterIfRunnable(question: Question): any;
                then:any;
            }

            /**
             * Sticky bottom bar user interface
             */
            interface BottomBar extends BaseUI<BottomBarOption> {
                new(opt?: BottomBarOption): BottomBar;
                /**
                 * Render the prompt to screen
                 * @return self
                 */
                render(): BottomBar;
                /**
                 * Update the bottom bar content and rerender
                 * @param bottomBar Bottom bar content
                 * @return self
                 */
                updateBottomBar(bottomBar: string): BottomBar;
                /**
                 * Rerender the prompt
                 * @return self
                 */
                writeLog(data: any): BottomBar;
                /**
                 * Make sure line end on a line feed
                 * @param str Input string
                 * @return The input string with a final line feed
                 */
                enforceLF(str: string): string;
                /**
                 * Helper for writing message in Prompt
                 * @param message The message to be output
                 */
                write(message: string): void;
                log: through.ThroughStream;
            }

            interface BottomBarOption {
                bottomBar?: string;
            }
            /**
             * Base interface class other can inherits from
             */
            interface BaseUI<TOpt> {
                new(opt: TOpt): void;
                /**
                 * Handle the ^C exit
                 * @return {null}
                 */
                onForceClose(): void;
                /**
                 * Close the interface and cleanup listeners
                 */
                close(): void;
                /**
                 * Handle and propagate keypress events
                 */
                onKeypress(s: string, key: Key): void;
            }

            interface Key {
                sequence: string;
                name: string;
                meta: boolean;
                shift: boolean;
                ctrl: boolean;
            }
        }

        namespace objects {
            /**
             * Choice object
             * Normalize input as choice object
             * @constructor
             * @param {String|Object} val  Choice value. If an object is passed, it should contains
             *                             at least one of `value` or `name` property
             */
            interface Choice {
                new(str: string): Choice;
                new(separator: Separator): Choice;
                new(option: ChoiceOption): Choice;
            }

            interface ChoiceOption {
                name?: string;
                value?: string;
                type?: string;
                extra?: any;
                key?: string;
                checked?: boolean;
                disabled?: string|((answers: Answers) => any);
            }

            /**
             * Choices collection
             * Collection of multiple `choice` object
             * @constructor
             * @param choices  All `choice` to keep in the collection
             */
            interface Choices {
                new(choices: (string|Separator|ChoiceOption)[], answers?: Answers): Choices;
                choices: Choice[];
                realChoices: Choice[];
                length: number;
                realLength: number;
                /**
                 * Get a valid choice from the collection
                 * @param selector The selected choice index
                 * @return Return the matched choice or undefined
                 */
                getChoice(selector: number): Choice;
                /**
                 * Get a raw element from the collection
                 * @param selector The selected index value
                 * @return Return the matched choice or undefined
                 */
                get(selector: number): Choice;
                /**
                 * Match the valid choices against a where clause
                 * @param whereClause Lodash `where` clause
                 * @return Matching choices or empty array
                 */
                where<U extends {}>(whereClause: U): Choice[];
                /**
                 * Pluck a particular key from the choices
                 * @param propertyName Property name to select
                 * @return Selected properties
                 */
                pluck(propertyName: string): any[];
                forEach<T>(application: (choice: Choice) => T): T[];
            }

            interface SeparatorStatic {
                /**
                 * @param line Separation line content (facultative)
                 */
                new(line?: string): Separator;
                /**
                 * Helper function returning false if object is a separator
                 * @param obj object to test against
                 * @return `false` if object is a separator
                 */
                exclude(obj: any): boolean;
            }

            /**
             * Separator object
             * Used to space/separate choices group
             * @constructor
             * @param {String} line   Separation line content (facultative)
             */
            interface Separator {
                type: string;
                line: string;
                /**
                 * Stringify separator
                 * @return {String} the separator display string
                 */
                toString(): string;
            }
        }
    }

    var inquirer: inquirer.Inquirer;

    export = inquirer;
}
