import { Interface as ReadlineInterface } from "readline";
import { Observable } from "rxjs";
import { ThroughStream } from "through";
import Choice from "./lib/objects/choice.js";
import Choices from "./lib/objects/choices.js";
import Separator from "./lib/objects/separator.js";
import "./lib/prompts/base.js";
import CheckboxPrompt from "./lib/prompts/checkbox.js";
import ConfirmPrompt from "./lib/prompts/confirm.js";
import EditorPrompt from "./lib/prompts/editor.js";
import ExpandPrompt from "./lib/prompts/expand.js";
import InputPrompt from "./lib/prompts/input.js";
import ListPrompt from "./lib/prompts/list.js";
import NumberPrompt from "./lib/prompts/number.js";
import PasswordPrompt from "./lib/prompts/password.js";
import RawListPrompt from "./lib/prompts/rawlist.js";
import UI from "./lib/ui/baseUI.js";
import "./lib/ui/bottom-bar.js";
import "./lib/ui/prompt.js";
import "./lib/utils/events.js";
import "./lib/utils/paginator.js";
import "./lib/utils/readline.js";
import "./lib/utils/screen-manager.js";

/**
 * Represents a union which preserves autocompletion.
 *
 * @template T
 * The keys which are available for autocompletion.
 *
 * @template F
 * The fallback-type.
 */
type LiteralUnion<T extends F, F = string> = T | (F & {});

/**
 * Represents a function for prompting questions to the user.
 */
export interface PromptFunction {
    /**
     * Prompts the questions to the user.
     */
    <T extends Answers = Answers>(questions: QuestionCollection<T>, initialAnswers?: Partial<T>): Promise<T>;
}

/**
 * Provides prompts for answering questions.
 */
export interface PromptModuleBase extends PromptFunction {
    /**
     * Registers a new prompt-type.
     *
     * @param name
     * The name of the prompt.
     *
     * @param prompt
     * The constructor of the prompt.
     */
    registerPrompt(name: string, prompt: inquirer.prompts.PromptConstructor): void;

    /**
     * Registers the default prompts.
     */
    restoreDefaultPrompts(): void;
}

/**
 * Represents a function for registering a prompt.
 */
type RegisterFunction = PromptModuleBase["registerPrompt"];

/**
 * Represents a function for restoring a prompt.
 */
type RestoreFunction = PromptModuleBase["restoreDefaultPrompts"];

/**
 * Represents a list-based question.
 *
 * @template T
 * The type of the answers.
 *
 * @template TChoiceMap
 * The valid choices for the question.
 */
interface ListQuestionOptionsBase<T extends Answers, TChoiceMap> extends Question<T> {
    /**
     * The choices of the prompt.
     */
    choices?: AsyncDynamicQuestionProperty<ReadonlyArray<DistinctChoice<T, TChoiceMap>>, T> | undefined;

    /**
     * The number of elements to show on each page.
     */
    pageSize?: number | undefined;
}

/**
 * Creates a prompt-module.
 *
 * @param opt
 * The streams for the prompt-module.
 *
 * @returns
 * The new prompt-module.
 */
export function createPromptModule(opt?: StreamOptions): PromptModule;

/**
 * A component for creating {@link PromptModule `PromptModule`}s.
 */
type PromptModuleCreator = typeof createPromptModule;

/**
 * Represents either a key of {@link T `T`} or a {@link String `string`}.
 *
 * @template T
 * The type of the keys to suggest.
 */
export type KeyUnion<T> = LiteralUnion<Extract<keyof T, string>>;

/**
 * Converts the specified union-type {@link U `U`} to an intersection-type.
 *
 * @template U
 * The union to convert to an intersection.
 */
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

/**
 * A set of answers.
 */
export interface Answers extends Record<string, any> {}

/**
 * Provides the functionality to validate answers.
 *
 * @template T
 * The type of the answers.
 */
export type Validator<T extends Answers = Answers> = Question<T>["validate"];

/**
 * Provides the functionality to transform an answer.
 *
 * @template T
 * The type of the answers.
 */
export type Transformer<T extends Answers = Answers> = InputQuestionOptions<T>["transformer"];

/**
 * Represents a dynamic property for a question.
 *
 * @template T
 * The type of the property.
 *
 * @template TAnswers
 * The type of the answers.
 */
export type DynamicQuestionProperty<T, TAnswers extends Answers = Answers> = T | ((answers: TAnswers) => T);

/**
 * Represents a dynamic property for a question which can be fetched asynchronously.
 *
 * @template T
 * The type of the property.
 *
 * @template TAnswers
 * The type of the answers.
 */
export type AsyncDynamicQuestionProperty<T, TAnswers extends Answers = Answers> = DynamicQuestionProperty<
    T | Promise<T>,
    TAnswers
>;

/**
 * Represents a choice-item.
 */
export interface ChoiceBase {
    /**
     * The type of the choice.
     */
    type?: string | undefined;
}

/**
 * Provides options for a choice of the {@link ListPrompt `ListPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ListChoiceOptions<T extends Answers = Answers> extends ChoiceOptions {
    /**
     * A value indicating whether the choice is disabled.
     */
    disabled?: DynamicQuestionProperty<boolean | string, T> | undefined;
}

/**
 * Provides options for a choice of the {@link CheckboxPrompt `CheckboxPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface CheckboxChoiceOptions<T extends Answers = Answers> extends ListChoiceOptions<T> {
    /**
     * A value indicating whether the choice should be initially checked.
     */
    checked?: boolean | undefined;
}

/**
 * Provides options for a choice of the {@link ExpandPrompt `ExpandPrompt<TQuestion>`}.
 */
export interface ExpandChoiceOptions extends ChoiceOptions {
    /**
     * The key to press for selecting the choice.
     */
    key?: string | undefined;
}

/**
 * Represents a separator.
 */
export interface SeparatorOptions extends ChoiceBase {
    /**
     * Gets the type of the choice.
     */
    type: "separator";

    /**
     * Gets or sets the text of the separator.
     */
    line?: string | undefined;
}

/**
 * Provides options for a choice.
 */
export interface ChoiceOptions extends ChoiceBase {
    /**
     * @inheritdoc
     */
    type?: "choice" | undefined;

    /**
     * The name of the choice to show to the user.
     */
    name?: string | undefined;

    /**
     * The value of the choice.
     */
    value?: any;

    /**
     * The short form of the name of the choice.
     */
    short?: string | undefined;

    /**
     * The extra properties of the choice.
     */
    extra?: any;
}

/**
 * Provides all valid choice-types for any kind of question.
 *
 * @template T
 * The type of the answers.
 */
export interface BaseChoiceMap<T extends Answers = Answers> {
    Choice: Choice<T>;
    ChoiceOptions: ChoiceOptions;
    Separator: Separator;
    SeparatorOptions: SeparatorOptions;
}

/**
 * Provides all valid choice-types for the {@link ListQuestion `ListQuestion<T>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ListChoiceMap<T extends Answers = Answers> extends BaseChoiceMap<T> {
    ListChoiceOptions: ListChoiceOptions<T>;
}

/**
 * Provides all valid choice-types for the {@link CheckboxQuestion `CheckboxQuestion<T>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface CheckboxChoiceMap<T extends Answers = Answers> extends BaseChoiceMap<T> {
    CheckboxChoiceOptions: CheckboxChoiceOptions<T>;
}

/**
 * Provides all valid choice-types for the {@link ExpandQuestion `ExpandQuestion<T>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ExpandChoiceMap<T extends Answers = Answers> extends BaseChoiceMap<T> {
    ExpandChoiceOptions: ExpandChoiceOptions;
}

/**
 * Provides all valid choice-types.
 *
 * @template T
 * The type of the answers.
 */
export interface AllChoiceMap<T extends Answers = Answers> {
    BaseChoiceMap: BaseChoiceMap<T>[keyof BaseChoiceMap<T>];
    ListChoiceMap: ListChoiceMap<T>[keyof ListChoiceMap<T>];
    CheckboxChoiceMap: CheckboxChoiceMap<T>[keyof CheckboxChoiceMap<T>];
    ExpandChoiceMap: ExpandChoiceMap<T>[keyof ExpandChoiceMap<T>];
}

/**
 * Provides valid choices for the question of the {@link TChoiceMap `TChoiceMap`}.
 *
 * @template TAnswers
 * The type of the answers.
 *
 * @template TChoiceMap
 * The choice-types to provide.
 */
export type DistinctChoice<TAnswers extends Answers = Answers, TChoiceMap = AllChoiceMap<TAnswers>> =
    | string
    | TChoiceMap[keyof TChoiceMap];

/**
 * Represents a set of choices.
 *
 * @template T
 * The type of the answers.
 */
export type ChoiceCollection<T extends Answers = Answers> = Array<DistinctChoice<T, AllChoiceMap<T>>>;

/**
 * Provides options for a question.
 *
 * @template T
 * The type of the answers.
 */
export interface Question<T extends Answers = Answers> {
    /**
     * The type of the question.
     */
    type?: string | undefined;

    /**
     * The key to save the answer to the answers-hash.
     */
    name?: KeyUnion<T> | undefined;

    /**
     * The message to show to the user.
     */
    message?: AsyncDynamicQuestionProperty<string, T> | undefined;

    /**
     * The default value of the question.
     */
    default?: AsyncDynamicQuestionProperty<any, T> | undefined;

    /**
     * The prefix of the {@link message `message`}.
     */
    prefix?: string | undefined;

    /**
     * The suffix of the {@link message `message`}.
     */
    suffix?: string | undefined;

    /**
     * Post-processes the answer.
     *
     * @param input
     * The answer provided by the user.
     *
     * @param answers
     * The answers provided by the user.
     */
    filter?(input: any, answers: T): any;

    /**
     * A value indicating whether the question should be prompted.
     */
    when?: AsyncDynamicQuestionProperty<boolean, T> | undefined;

    /**
     * Validates the integrity of the answer.
     *
     * @param input
     * The answer provided by the user.
     *
     * @param answers
     * The answers provided by the user.
     *
     * @returns
     * Either a value indicating whether the answer is valid or a {@link String `string`} which describes the error.
     */
    validate?(input: any, answers?: T): boolean | string | Promise<boolean | string>;

    /**
     * Force to prompt the question if the answer already exists.
     */
    askAnswered?: boolean;
}

/**
 * Represents the possible answers of each question in the prompt
 */
export type QuestionAnswer<T extends Answers = Answers> = {
    [K in keyof T]: {
        name: K;
        answer: T[K];
    };
}[keyof T];

/**
 * Provides options for a question for the {@link InputPrompt `InputPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface InputQuestionOptions<T extends Answers = Answers> extends Question<T> {
    /**
     * Transforms the value to display to the user.
     *
     * @param input
     * The input provided by the user.
     *
     * @param answers
     * The answers provided by the users.
     *
     * @param flags
     * Additional information about the value.
     *
     * @returns
     * The value to display to the user.
     */
    transformer?(input: any, answers: T, flags: { isFinal?: boolean | undefined }): string | Promise<string>;
}

/**
 * Provides options for a question for the {@link InputPrompt `InputPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface InputQuestion<T extends Answers = Answers> extends InputQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type?: "input" | undefined;
}

/**
 * Provides options for a question for the {@link NumberPrompt `NumberPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface NumberQuestionOptions<T extends Answers = Answers> extends InputQuestionOptions<T> {}

/**
 * Provides options for a question for the {@link NumberPrompt `NumberPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface NumberQuestion<T extends Answers = Answers> extends NumberQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "number";
}

/**
 * Provides options for a question for the {@link PasswordPrompt `PasswordPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface PasswordQuestionOptions<T extends Answers = Answers> extends InputQuestionOptions<T> {
    /**
     * The character to replace the user-input.
     */
    mask?: string | undefined;
}

/**
 * Provides options for a question for the {@link PasswordPrompt `PasswordPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface PasswordQuestion<T extends Answers = Answers> extends PasswordQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "password";
}

/**
 * Represents a list-based question that can loop.
 *
 * @template T
 * The type of the answers.
 *
 * @template TChoiceMap
 * The valid choices for the question.
 */
interface LoopableListQuestionOptionsBase<T extends Answers, TChoiceMap>
    extends ListQuestionOptionsBase<T, TChoiceMap>
{
    /**
     * A value indicating whether choices in a list should be looped.
     */
    loop?: boolean | undefined;
}

/**
 * Provides options for a question for the {@link ListPrompt `ListPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ListQuestionOptions<T extends Answers = Answers>
    extends LoopableListQuestionOptionsBase<T, ListChoiceMap<T>>
{}

/**
 * Provides options for a question for the {@link ListPrompt `ListPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ListQuestion<T extends Answers = Answers> extends ListQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "list";
}

/**
 * Provides options for a question for the {@link RawListPrompt `RawListPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface RawListQuestionOptions<T extends Answers = Answers> extends ListQuestionOptions<T> {}

/**
 * Provides options for a question for the {@link RawListPrompt `RawListPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface RawListQuestion<T extends Answers = Answers> extends RawListQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "rawlist";
}

/**
 * Provides options for a question for the {@link ExpandPrompt `ExpandPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ExpandQuestionOptions<T extends Answers = Answers>
    extends ListQuestionOptionsBase<T, ExpandChoiceMap<T>>
{}

/**
 * Provides options for a question for the {@link ExpandPrompt `ExpandPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ExpandQuestion<T extends Answers = Answers> extends ExpandQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "expand";
}

/**
 * Provides options for a question for the {@link CheckboxPrompt `CheckboxPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface CheckboxQuestionOptions<T extends Answers = Answers>
    extends LoopableListQuestionOptionsBase<T, CheckboxChoiceMap<T>>
{}

/**
 * Provides options for a question for the {@link CheckboxPrompt `CheckboxPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface CheckboxQuestion<T extends Answers = Answers> extends CheckboxQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "checkbox";
}

/**
 * Provides options for a question for the {@link ConfirmPrompt `ConfirmPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ConfirmQuestionOptions<T extends Answers = Answers> extends Question<T> {}

/**
 * Provides options for a question for the {@link ConfirmPrompt `ConfirmPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface ConfirmQuestion<T extends Answers = Answers> extends ConfirmQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "confirm";
}

/**
 * Provides options for a question for the {@link EditorPrompt `EditorPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface EditorQuestionOptions<T extends Answers = Answers> extends Question<T> {
    /**
     * The postfix of the file being edited.
     *
     * Adding this will add color highlighting to the file content in most editors.
     */
    postfix?: string;
}

/**
 * Provides options for a question for the {@link EditorPrompt `EditorPrompt<TQuestion>`}.
 *
 * @template T
 * The type of the answers.
 */
export interface EditorQuestion<T extends Answers = Answers> extends EditorQuestionOptions<T> {
    /**
     * @inheritdoc
     */
    type: "editor";
}

/**
 * Provides the available question-types.
 *
 * @template T
 * The type of the answers.
 */
export interface QuestionMap<T extends Answers = Answers> {
    /**
     * The {@link InputQuestion `InputQuestion<T>`} type.
     */
    input: InputQuestion<T>;

    /**
     * The {@link NumberQuestion `NumberQuestion<T>`} type.
     */
    number: NumberQuestion<T>;

    /**
     * The {@link PasswordQuestion `PasswordQuestion<T>`} type.
     */
    password: PasswordQuestion<T>;

    /**
     * The {@link ListQuestion `ListQuestion<T>`} type.
     */
    list: ListQuestion<T>;

    /**
     * The {@link RawListQuestion `RawListQuestion<T>`} type.
     */
    rawList: RawListQuestion<T>;

    /**
     * The {@link ExpandQuestion `ExpandQuestion<T>`} type.
     */
    expand: ExpandQuestion<T>;

    /**
     * The {@link CheckboxQuestion `CheckboxQuestion<T>`} type.
     */
    checkbox: CheckboxQuestion<T>;

    /**
     * The {@link ConfirmQuestion `ConfirmQuestion<T>`} type.
     */
    confirm: ConfirmQuestion<T>;

    /**
     * The {@link EditorQuestion `EditorQuestion<T>`} type.
     */
    editor: EditorQuestion<T>;
}

/**
 * Represents one of the available questions.
 *
 * @template T
 * The type of the answers.
 */
export type DistinctQuestion<T extends Answers = Answers> = QuestionMap<T>[keyof QuestionMap<T>];

/**
 * Indicates the type of a question
 */
export type QuestionTypeName = DistinctQuestion["type"];

/**
 * Represents a collection of questions.
 *
 * @template T
 * The type of the answers.
 */
export type QuestionCollection<T extends Answers = Answers> =
    | DistinctQuestion<T>
    | ReadonlyArray<DistinctQuestion<T>>
    | Observable<DistinctQuestion<T>>
    | { [P in KeyUnion<T>]?: DistinctQuestion<T> & { name?: never } };

/**
 * Provides an input and an output-stream.
 */
export interface StreamOptions {
    /**
     * A stream to read the input from.
     */
    input?: NodeJS.ReadStream | undefined;

    /**
     * A stream to write the output to.
     */
    output?: NodeJS.WriteStream | undefined;

    /**
     * Whether to display prompts if input is not a TTY.
     */
    skipTTYChecks?: boolean | undefined;
}

/**
 * Provides the functionality to prompt questions to the user.
 */
export interface PromptModule extends PromptModuleBase {
    /**
     * The prompts of the prompt-module.
     */
    prompts: inquirer.prompts.PromptCollection;

    /**
     * Prompts the questions to the user.
     */
    <T extends Answers = Answers>(
        questions: QuestionCollection<T>,
        initialAnswers?: Partial<T>,
    ): Promise<T> & { ui: inquirer.ui.Prompt<T> };

    /**
     * Registers a new prompt-type.
     *
     * @param name
     * The name of the prompt.
     *
     * @param prompt
     * The constructor of the prompt.
     */
    registerPrompt(name: string, prompt: inquirer.prompts.PromptConstructor): this;
}

/**
 * Provides the functionality to prompt questions.
 */
declare namespace inquirer {
    /**
     * Provides components for the prompts.
     */
    namespace prompts {
        /**
         * Provides options for a prompt.
         *
         * @template T
         * The type of the answers.
         */
        type PromptOptions<T extends Question = Question> = T & {
            /**
             * The choices of the prompt.
             */
            choices: Choices;
        };

        /**
         * Represents the state of a prompt.
         */
        type PromptState = LiteralUnion<"pending" | "idle" | "loading" | "answered" | "done">;

        /**
         * Represents a prompt.
         */
        interface PromptBase {
            /**
             * Gets or sets a string which represents the state of the prompt.
             */
            status: PromptState;

            /**
             * Runs the prompt.
             *
             * @returns
             * The result of the prompt.
             */
            run(): Promise<any>;
        }

        /**
         * Provides the functionality to initialize new prompts.
         */
        interface PromptConstructor {
            /**
             * Initializes a new instance of a prompt.
             *
             * @param question
             * The question to prompt.
             *
             * @param readLine
             * An object for reading from the command-line.
             *
             * @param answers
             * The answers provided by the user.
             */
            new(question: any, readLine: ReadlineInterface, answers: Answers): PromptBase;
        }

        /**
         * Provides a set of prompt-constructors.
         */
        type PromptCollection = Record<string, PromptConstructor>;

        /**
         * Provides data about the state of a prompt.
         */
        interface PromptStateData {
            /**
             * Either a {@link String `string`} which describes the error of the prompt or a {@link Boolean `boolean`} indicating whether the prompt-value is valid.
             */
            isValid: string | boolean;
        }

        /**
         * Provides data about the successful state of a prompt.
         *
         * @param T
         * The type of the answer.
         */
        interface SuccessfulPromptStateData<T = any> extends PromptStateData {
            /**
             * @inheritdoc
             */
            isValid: true;

            /**
             * The value of the prompt.
             */
            value: T;
        }

        /**
         * Provides data about the failed state of a prompt.
         */
        interface FailedPromptStateData extends PromptStateData {
            /**
             * @inheritdoc
             */
            isValid: false | string;
        }

        /**
         * Provides pipes for handling events of a prompt.
         *
         * @param T
         * The type of the answer.
         */
        interface PromptEventPipes<T = any> {
            /**
             * A pipeline for successful inputs.
             */
            success: Observable<SuccessfulPromptStateData<T>>;

            /**
             * An object representing an error.
             */
            error: Observable<FailedPromptStateData>;
        }
    }

    /**
     * Provides components for the ui.
     */
    namespace ui {
        /**
         * Represents the bottom-bar UI.
         */
        class BottomBar extends UI {
            /**
             * Gets or sets a stream to write logs to.
             */
            log: ThroughStream;

            /**
             * Initializes a new instance of the {@link BottomBar `BottomBar`} class.
             *
             * @param options
             * Provides options for the bottom-bar ui.
             */
            constructor(options?: BottomBarOptions);

            /**
             * Renders the specified {@link text `text`} to the bottom bar.
             *
             * @param text
             * The text to print to the bottom bar.
             */
            updateBottomBar(text: string): this;

            /**
             * Renders the bottom bar.
             */
            protected render(): this;

            /**
             * Cleans the bottom bar.
             */
            protected clean(): this;

            /**
             * Writes a message to the bottom bar.
             *
             * @param message
             * The message to write.
             */
            protected write(message: string): void;

            /**
             * Writes the specified {@link data `data`} to the log-zone.
             *
             * @param data
             * The data to write to the log-zone.
             */
            protected writeLog(data: any): this;

            /**
             * Fixes the new-line characters of the specified {@link text `text`}.
             *
             * @param text
             * The text to process.
             */
            protected enforceLF(text: string): string;
        }

        /**
         * Represents the prompt ui.
         */
        class Prompt<T extends Answers = Answers> extends UI {
            /**
             * Gets or sets the prompts of the ui.
             */
            prompts: prompts.PromptCollection;

            /**
             * Gets or sets the answers provided by the user.
             */
            answers: T;

            /**
             * Gets or sets the event-flow of the process.
             */
            process: Observable<QuestionAnswer<T>>;

            /**
             * Initializes a new instance of the {@link Prompt `Prompt`} class.
             *
             * @param prompts
             * The prompts for the ui.
             *
             * @param options
             * The input- and output-stream of the ui.
             */
            constructor(prompts: prompts.PromptCollection, options?: StreamOptions);

            /**
             * Runs the prompt-UI.
             *
             * @param questions
             * The questions to prompt the user to answer.
             *
             * @returns
             * The answers provided by the user.
             */
            run(questions: Array<DistinctQuestion<T>>): Promise<T>;

            /**
             * Finishes the process.
             */
            protected onCompletion(): T;

            /**
             * Processes a question.
             *
             * @param question
             * The question to process.
             *
             * @returns
             * The answer to the question.
             */
            protected processQuestion(
                question: DistinctQuestion<T>,
            ): Observable<FetchedAnswer>;

            /**
             * Fetches the answer to a question.
             *
             * @param question
             * The question to fetch the answer for.
             *
             * @returns
             * The answer to the question.
             */
            protected fetchAnswer(
                question: FetchedQuestion<T>,
            ): Observable<FetchedAnswer>;

            /**
             * Sets the type of the question if no question-type is specified.
             *
             * @param question
             * The question to set the default type for.
             *
             * @returns
             * The processed question.
             */
            protected setDefaultType(
                question: DistinctQuestion<T>,
            ): Observable<DistinctQuestion<T>>;

            /**
             * Filters the question if it is runnable.
             *
             * @param question
             * The question to filter.
             *
             * @returns
             * Either the event-flow of the question if it is runnable or an empty event-flow.
             */
            protected filterIfRunnable(
                question: DistinctQuestion<T>,
            ): Observable<DistinctQuestion<T>>;
        }

        /**
         * Provides options for the bottom-bar UI.
         */
        interface BottomBarOptions extends StreamOptions {
            /**
             * The initial text to display.
             */
            bottomBar?: string | undefined;
        }

        /**
         * Represents a fetched answer.
         *
         * @template T
         * The type of the answers.
         */
        type FetchedQuestion<T extends Answers = Answers> = DistinctQuestion<T> & {
            /**
             * The type of the question.
             */
            type: string;

            /**
             * The message to show to the user.
             */
            message: string;

            /**
             * The default value of the question.
             */
            default: any;

            /**
             * The choices of the question.
             */
            choices: ChoiceCollection<T>;
        };

        /**
         * Represents a fetched answer.
         */
        interface FetchedAnswer {
            /**
             * The name of the answer.
             */
            name: string;

            /**
             * The value of the answer.
             */
            answer: any;
        }
    }

    /**
     * Represents a choice-item separator.
     */
    class Separator implements SeparatorOptions {
        /**
         * @inheritdoc
         */
        readonly type: "separator";

        /**
         * @inheritdoc
         */
        line: string;

        /**
         * Initializes a new instance of the {@link Separator `Separator`} class.
         *
         * @param line
         * The text of the separator.
         */
        constructor(line?: string);

        /**
         * Checks whether the specified {@link item `item`} is not a separator.
         *
         * @param item
         * The item to check.
         *
         * @returns
         * A value indicating whether the item is not a separator.
         */
        static exclude(item: any): boolean;
    }

    /**
     * Registers a new prompt-type.
     *
     * @param name
     * The name of the prompt.
     *
     * @param prompt
     * The constructor of the prompt.
     */
    let registerPrompt: RegisterFunction;

    /**
     * Registers the default prompts.
     */
    let restoreDefaultPrompts: RestoreFunction;

    /**
     * Creates a prompt-module.
     *
     * @param opt
     * The streams for the prompt-module.
     *
     * @returns
     * The new prompt-module.
     */
    let createPromptModule: PromptModuleCreator;

    /**
     * The default prompt-module.
     */
    let prompt: PromptModule;
}

export default inquirer;
