/// <reference types="node" />
import * as child_process from 'child_process';
import * as stream from 'stream';
import stream__default, { Writable, Readable } from 'stream';

type Dict<T> = Record<string, T>;
type Arrayable<T> = T | T[];
type Default = Dict<any>;

declare function mri<T=Default>(args?: string[], options?: mri.Options): mri.Argv<T>;

declare namespace mri {
	export interface Options {
		boolean?: Arrayable<string>;
		string?: Arrayable<string>;
		alias?: Dict<Arrayable<string>>;
		default?: Dict<any>;
		unknown?(flag: string): void;
	}

	export type Argv<T=Default> = T & {
		_: string[];
	}
}

declare type Content = Buffer | NodeJS.ReadableStream | null;
/**
 * Virtual File
 *
 * Like simplified vinyl, no dependencies.
 *
 * TODO: extends Blob (Node v15): https://nodejs.org/api/buffer.html#buffer_class_blob
 */
declare class VFile {
    #private;
    get contents(): Content;
    set contents(v: Content);
    get path(): string;
    set path(v: string);
    get dirname(): string;
    set dirname(v: string);
    get basename(): string;
    set basename(v: string);
    get extname(): string;
    set extname(v: string);
    get base(): string;
    set base(v: string);
    get relative(): string;
    isBuffer(): boolean;
}

// Type definitions for prompts 2.0


declare function prompts<T extends string = string>(
    questions: prompts.PromptObject<T> | Array<prompts.PromptObject<T>>,
    options?: prompts.Options
): Promise<prompts.Answers<T>>;

declare namespace prompts {
    // Circular reference from prompts
    const prompt: any;

    function inject(arr: ReadonlyArray<any>): void;

    namespace inject {
        const prototype: {};
    }

    function override(obj: { [key: string]: any }): void;

    namespace override {
        const prototype: {};
    }

    namespace prompts {
        function autocomplete(args: PromptObject): any;

        function confirm(args: PromptObject): void;

        function date(args: PromptObject): any;

        function invisible(args: PromptObject): any;

        function list(args: PromptObject): any;

        function multiselect(args: PromptObject): any;

        function number(args: PromptObject): void;

        function password(args: PromptObject): any;

        function select(args: PromptObject): void;

        function text(args: PromptObject): void;

        function toggle(args: PromptObject): void;
    }

    // Based upon: https://github.com/terkelg/prompts/blob/d7d2c37a0009e3235b2e88a7d5cdbb114ac271b2/lib/elements/select.js#L29
    interface Choice {
        title: string;
        value?: any;
        disabled?: boolean | undefined;
        selected?: boolean | undefined;
        description?: string | undefined;
    }

    interface Options {
        onSubmit?: ((prompt: PromptObject, answer: any, answers: any[]) => void) | undefined;
        onCancel?: ((prompt: PromptObject, answers: any) => void) | undefined;
    }

    interface PromptObject<T extends string = string> {
        type: PromptType | Falsy | PrevCaller<T, PromptType | Falsy>;
        name: ValueOrFunc<T>;
        message?: ValueOrFunc<string> | undefined;
        initial?: InitialReturnValue | PrevCaller<T, InitialReturnValue | Promise<InitialReturnValue>> | undefined;
        style?: string | PrevCaller<T, string | Falsy> | undefined;
        format?: PrevCaller<T, void> | undefined;
        validate?: PrevCaller<T, boolean | string | Promise<boolean | string>> | undefined;
        onState?: PrevCaller<T, void> | undefined;
        min?: number | PrevCaller<T, number | Falsy> | undefined;
        max?: number | PrevCaller<T, number | Falsy> | undefined;
        float?: boolean | PrevCaller<T, boolean | Falsy> | undefined;
        round?: number | PrevCaller<T, number | Falsy> | undefined;
        instructions?: string | boolean | undefined;
        increment?: number | PrevCaller<T, number | Falsy> | undefined;
        separator?: string | PrevCaller<T, string | Falsy> | undefined;
        active?: string | PrevCaller<T, string | Falsy> | undefined;
        inactive?: string | PrevCaller<T, string | Falsy> | undefined;
        choices?: Choice[] | PrevCaller<T, Choice[] | Falsy> | undefined;
        hint?: string | PrevCaller<T, string | Falsy> | undefined;
        warn?: string | PrevCaller<T, string | Falsy> | undefined;
        suggest?: ((input: any, choices: Choice[]) => Promise<any>) | undefined;
        limit?: number | PrevCaller<T, number | Falsy> | undefined;
        mask?: string | PrevCaller<T, string | Falsy> | undefined;
        stdout?: Writable | undefined;
        stdin?: Readable | undefined;
    }

    type Answers<T extends string> = { [id in T]: any };

    type PrevCaller<T extends string, R = T> = (
        prev: any,
        values: Answers<T>,
        prompt: PromptObject
    ) => R;

    type Falsy = false | null | undefined;

    type PromptType = "text" | "password" | "invisible" | "number" | "confirm" | "list" | "toggle" | "select" | "multiselect" | "autocomplete" | "date" | "autocompleteMultiselect";

    type ValueOrFunc<T extends string> = T | PrevCaller<T>;

    type InitialReturnValue = string | number | boolean | Date;
}

declare type Argv = {
    clone?: true;
};
declare type ParsedArgv = mri.Argv<Argv>;
declare type Generator = (api: API, context: Context) => Promise<void>;

declare type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
declare type ReturnPair = Awaited<ReturnType<typeof loadGenerator>>;
declare type API = ReturnPair[1];
declare type Context = Omit<ReturnPair[2], 'install' | 'gitInit' | 'prompts'>;
declare const loadGenerator: (argv: ParsedArgv, { mock }?: any) => Promise<readonly [any, {
    install: (deps?: string[], { dev, silent, cwd, stdio, client, }?: Pick<child_process.SpawnOptions, "cwd" | "stdio"> & {
        dev?: boolean | undefined;
        silent?: boolean | undefined;
        client?: "auto" | "yarn" | "npm" | undefined;
    }) => Promise<void>;
    gitInit: (message?: string, { cwd, stdio }?: child_process.SpawnOptions) => Promise<void>;
    prompts: typeof prompts;
    src: (globs: string[], options?: undefined) => stream__default.Transform;
    dest: (folder: string) => stream__default.Transform;
    pipeline: typeof stream__default.pipeline.__promisify__;
    packages: (patch: object | ((content: object, file: VFile) => object)) => stream__default.Transform;
    modify: {
        (match: (RegExp | ((file: VFile) => boolean)) | undefined, fn: (file: VFile) => void | VFile): stream__default.Transform;
        text: (match: RegExp | ((file: VFile) => boolean), fn: (file: VFile, content: string) => string) => stream__default.Transform;
        json: (match: (RegExp | ((file: VFile) => boolean)) | undefined, fn: (file: VFile, content: object) => object, { finalNewline, space }?: {
            finalNewline?: boolean | undefined;
            space?: string | undefined;
        }) => stream__default.Transform;
        rename: (match: RegExp | ((file: VFile) => boolean), fn: (file: VFile, info: {
            name: string;
            ext: string;
            dir: string;
        }) => void | {
            name: string;
            ext: string;
            dir: string;
        }) => stream__default.Transform;
    };
    template: (data: object, { ext, test, render, options, }?: {
        ext?: RegExp | undefined;
        test?: RegExp | undefined;
        render?: ((content: string, data: object, options?: object | undefined) => string) | undefined;
        options?: object | undefined;
    }) => stream__default.Transform;
}, {
    path: string;
    name: string;
    argv: ParsedArgv;
}]>;
declare const _default: (argv: ParsedArgv, opts?: any) => Promise<any>;

declare const run: (args: string[], usage: string | undefined, opts: mri.Options) => Promise<void>;

declare const mock: (generator: string, directory: string, { answers, api, context }?: any) => Promise<{
    data: Record<string, string>;
    dest(): stream.Transform;
    readonly files: string[];
    readFile(file: string): string;
}>;

export { Argv, Generator, ParsedArgv, VFile, _default as create, mri as minimist, mock, run };
