import EventEmitter from 'events';
import { DirectoryTreeOptions } from 'directory-tree';

interface Cache<TCache = any> {
    delete(id: string): boolean;
    get<T = TCache>(id: string): T;
    has(id: string): boolean;
    set<T = TCache>(id: string, value: T): void;
}
declare function createPluginCache(cache: any): Cache;

type MaybePromise<T> = Promise<T> | T;
type KubbUserConfig<IsJSON = false> = Omit<KubbConfig, 'root'> & {
    /**
     * Project root directory. Can be an absolute path, or a path relative from
     * the location of the config file itself.
     * @default process.cwd()
     */
    root?: string;
    plugins?: IsJSON extends true ? KubbJSONPlugin[] : KubbPlugin[];
};
/**
 * Global/internal config used through out the full generation.
 */
type KubbConfig = {
    /**
     * Project root directory. Can be an absolute path, or a path relative from
     * the location of the config file itself.
     * @default process.cwd()
     */
    root: string;
    input: {
        /**
         * Path to be used as the input. Can be an absolute path, or a path relative from
         * the defined root option.
         */
        path: string;
    } | string;
    output: {
        /**
         * Path to be used to export all generated files. Can be an absolute path, or a path relative based of the defined root option.
         */
        path: string;
        /**
         * Remove previous generated files and folders.
         */
        clean?: boolean;
    };
    /**
     * Array of Kubb plugins to use.
     * The plugin/package can forsee some options that you need to pass through.
     * Sometimes a plugin is depended on another plugin, if that's the case you will get an error back from the plugin you installed.
     */
    plugins?: KubbPlugin[];
    /**
     * Hooks that will be called when a specific action is triggered in Kubb.
     */
    hooks?: {
        /**
         * Hook that will be triggerend at the end of all executions.
         */
        done?: string | string[];
    };
    /**
     * Log level to report when using the CLI
     */
    logLevel?: LogLevel;
};
type CLIOptions = {
    config?: string;
    mode?: 'development' | 'production';
    debug?: boolean;
    watch?: string;
};
type KubbPluginKind = 'schema' | 'controller';
type KubbJSONPlugin = [string, Record<string, any>];
type KubbPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
    /**
     * Unique name used for the plugin
     * @example @kubb/typescript
     */
    name: string;
    /**
     * Kind/type for the plugin
     * Type 'schema' can be used for JSON schema's, Typescript types, ...
     * Type 'controller' can be used to create generate API calls, React-Query hooks, Axios controllers, ...
     * @default undefined
     */
    kind?: KubbPluginKind;
    /**
     * Defined an api that can be used by other plugins
     */
    api?: TOptions['api'];
} & Partial<PluginLifecycle>;
type PluginFactoryOptions<Options = unknown, Nested extends boolean = false, Api = any> = {
    options: Options;
    nested: Nested;
    api: Api;
};
type PluginLifecycle = {
    /**
     * Valdiate all plugins to see if their depended plugins are installed and configured.
     * @type hookParallel
     */
    validate: (this: PluginContext, plugins: KubbPlugin[]) => MaybePromise<true>;
    /**
     * Start of the lifecycle of a plugin.
     * @type hookParallel
     */
    buildStart: (this: PluginContext, kubbConfig: KubbConfig) => MaybePromise<void>;
    /**
     * Resolve to an id based on importee(example: `./Pet.ts`) and directory(example: `./models`).
     * @type hookFirst
     * @example ('./Pet.ts', './src/gen/')
     */
    resolveId: (this: PluginContext, fileName: string, directory?: string, options?: Record<string, any>) => OptionalPath;
    /**
     * Makes it possible to run async logic to override the path defined previously by `resolveId`.
     * @type hookFirst
     */
    load: (this: PluginContext, path: Path) => MaybePromise<TransformResult | null>;
    /**
     * Transform the source-code.
     * @type hookReduceArg0
     */
    transform: (this: PluginContext, source: string, path: Path) => MaybePromise<TransformResult>;
    /**
     * Write the result to the file-system based on the id(defined by `resolveId` or changed by `load`).
     * @type hookParallel
     */
    writeFile: (this: PluginContext, source: string | undefined, path: Path) => MaybePromise<void>;
    /**
     * End of the plugin lifecycle.
     * @type hookParallel
     */
    buildEnd: (this: PluginContext) => MaybePromise<void>;
};
type PluginLifecycleHooks = keyof PluginLifecycle;
type ResolveIdParams = {
    fileName: string;
    directory?: string | undefined;
    /**
     * When set, resolveId will only call resolveId of the name of the plugin set here.
     * If not defined it will fall back on the resolveId of the core plugin.
     */
    pluginName?: string;
    /**
     * Options to be passed to 'resolveId' 3th parameter
     */
    options?: Record<string, any>;
};
type PluginContext = {
    config: KubbConfig;
    cache: Cache;
    fileManager: FileManager;
    addFile: (emitedFile: EmittedFile | File, options?: {
        root?: true;
    }) => Promise<File>;
    resolveId: (params: ResolveIdParams) => MaybePromise<OptionalPath>;
    load: (id: string) => MaybePromise<TransformResult | void>;
};
type TransformResult = string | null;
/**
 * @description Computing the name of a file or directory together with its position in relation to other directories traced back in a line to the root
 */
type Path = string;
type OptionalPath = Path | null | undefined;
type FileName = string | null | undefined;
type LogType = 'error' | 'warn' | 'info';
type LogLevel = LogType | 'silent';

declare const isPromise: <T>(result: MaybePromise<T>) => result is Promise<T>;

type WriteOptions = {
    format: boolean;
};
declare const write: (data: string, path: string, options?: WriteOptions) => Promise<void>;
declare const clean: (path: string) => Promise<void>;

declare const format: (text: string) => string;

declare const getRelativePath: (root?: string | null, file?: string | null) => string;
type PathMode = 'file' | 'directory';
declare const getPathMode: (path: string | undefined | null) => PathMode | undefined;
declare const read: (path: string, encoding?: string) => Promise<string>;

/**
 * @deprecated Use userFile instead
 */
type EmittedFile = {
    /**
     *  equal to importee when getting passed through resolveId
     */
    id: string;
    /**
     * The importer is the fully resolved id of the importing module.
     */
    importer?: string;
    /**
     * Name to be used to dynamicly create the fileName(based on input.path)
     */
    name?: string;
    /**
     * FileName will be the end result so no input.path will not be added
     */
    fileName?: string;
    source?: string;
    options?: Record<string, any>;
};
type File = {
    /**
     * Name to be used to dynamicly create the fileName(based on input.path)
     */
    fileName: string;
    /**
     * Path will be full qualified path to a specified file
     */
    path: string;
    source: string;
};
type UUID = string;
type CacheStore = {
    id: UUID;
    file: File;
    status: Status;
};
type Status = 'new' | 'success' | 'removed';

declare class FileManager {
    private cache;
    emitter: EventEmitter;
    events: {
        emitFile: (id: string, file: File) => void;
        emitStatusChange: (file: File) => void;
        emitStatusChangeById: (id: string, status: Status) => void;
        emitSuccess: () => void;
        emitRemove: (id: string, file: File) => void;
        onAdd: (callback: (id: string, file: File) => void) => () => void;
        onStatusChange: (callback: (file: File) => void) => () => void;
        onStatusChangeById: (id: string, callback: (status: Status) => void) => () => void;
        onSuccess: (callback: () => void) => () => void;
        onRemove: (id: string, callback: (file: File) => void) => () => void;
    };
    constructor();
    private getCache;
    private getCacheByPath;
    private getCountByStatus;
    get files(): File[];
    add(file: File): Promise<File>;
    addOrAppend(file: File): Promise<File>;
    setStatus(id: UUID, status: Status): void;
    get(id: UUID): File | undefined;
    remove(id: UUID): void;
    write(...params: Parameters<typeof write>): Promise<void>;
    read(...params: Parameters<typeof read>): Promise<string>;
}

declare class TreeNode<T = unknown> {
    data: T;
    parent?: TreeNode<T>;
    children: Array<TreeNode<T>>;
    constructor(data: T, parent?: TreeNode<T>);
    addChild(data: T): any;
    find(data: T): any;
    leaves(): this[];
    root(): any;
    forEach(callback: (treeNode: TreeNode<T>) => void): this;
    static build(path: string, options?: DirectoryTreeOptions): TreeNode<{
        name: string;
        path: string;
        type: "file" | "directory";
    }> | undefined;
}

type BuildOutput = {
    fileManager: FileManager;
};
type Spinner = {
    start: (text?: string) => Spinner;
    succeed: (text: string) => Spinner;
    fail: (text?: string) => Spinner;
    stopAndPersist: (options: {
        text: string;
    }) => Spinner;
    render: () => Spinner;
    text: string;
    info: (text: string) => Spinner;
};
type Logger = {
    log: (message: string, logLevel: LogLevel) => void;
    spinner?: Spinner;
};
type BuildOptions = {
    config: PluginContext['config'];
    mode: 'development' | 'production';
    logger?: Logger;
};
type KubbBuild = (options: BuildOptions) => Promise<BuildOutput>;
declare function build(options: BuildOptions): Promise<BuildOutput>;

/**
 * Type helper to make it easier to use kubb.config.ts
 * accepts a direct {@link KubbConfig} object, or a function that returns it.
 * The function receives a {@link ConfigEnv} object that exposes two properties:
 */
declare const defineConfig: (options: MaybePromise<KubbUserConfig<false>> | ((cliOptions: CLIOptions) => MaybePromise<KubbUserConfig>)) => MaybePromise<KubbUserConfig<false>> | ((cliOptions: CLIOptions) => MaybePromise<KubbUserConfig>);

type KubbPluginFactory<T extends PluginFactoryOptions = PluginFactoryOptions> = (options: T['options']) => T['nested'] extends true ? Array<KubbPlugin<T>> : KubbPlugin<T>;
declare function createPlugin<T extends PluginFactoryOptions = PluginFactoryOptions>(factory: KubbPluginFactory<T>): (options: T['options']) => T["nested"] extends true ? KubbPlugin<T>[] : KubbPlugin<T>;
type Options = {
    config: PluginContext['config'];
    fileManager: FileManager;
    resolveId: PluginContext['resolveId'];
    load: PluginContext['load'];
};
type CorePluginOptions = PluginFactoryOptions<Options, false, PluginContext>;

/**
 * Get the type of the first argument in a function.
 * @example Arg0<(a: string, b: number) => void> -> string
 */
type Argument0<H extends keyof PluginLifecycle> = Parameters<PluginLifecycle[H]>[0];
type Strategy = 'hookFirst' | 'hookForPlugin' | 'hookParallel' | 'hookReduceArg0' | 'hookSeq';

declare const hooks: [keyof PluginLifecycle];
declare class PluginManager {
    plugins: KubbPlugin[];
    readonly fileManager: FileManager;
    private readonly logger?;
    private readonly config;
    readonly core: KubbPlugin<CorePluginOptions>;
    constructor(config: KubbConfig, options: {
        logger?: Logger;
    });
    resolveId: (params: ResolveIdParams) => Promise<OptionalPath>;
    load: (id: string) => Promise<TransformResult>;
    hookForPlugin<H extends PluginLifecycleHooks>(pluginName: string, hookName: H, parameters: Parameters<PluginLifecycle[H]>, skipped?: ReadonlySet<KubbPlugin> | null): Promise<ReturnType<PluginLifecycle[H]> | null>;
    hookFirst<H extends PluginLifecycleHooks>(hookName: H, parameters: Parameters<PluginLifecycle[H]>, skipped?: ReadonlySet<KubbPlugin> | null): Promise<ReturnType<PluginLifecycle[H]> | null>;
    hookParallel<H extends PluginLifecycleHooks, TOuput = void>(hookName: H, parameters?: Parameters<PluginLifecycle[H]> | undefined): Promise<Awaited<TOuput>[]>;
    hookReduceArg0<H extends PluginLifecycleHooks>(hookName: H, [argument0, ...rest]: Parameters<PluginLifecycle[H]>, reduce: (reduction: Argument0<H>, result: ReturnType<PluginLifecycle[H]>, plugin: KubbPlugin) => MaybePromise<Argument0<H> | null>): Promise<Argument0<H>>;
    hookSeq<H extends PluginLifecycleHooks>(hookName: H, parameters?: Parameters<PluginLifecycle[H]>): Promise<void>;
    private getSortedPlugins;
    /**
     * Run an async plugin hook and return the result.
     * @param hookName Name of the plugin hook. Must be either in `PluginHooks` or `OutputPluginValueHooks`.
     * @param args Arguments passed to the plugin hook.
     * @param plugin The actual pluginObject to run.
     */
    private run;
    /**
     * Run a sync plugin hook and return the result.
     * @param hookName Name of the plugin hook. Must be in `PluginHooks`.
     * @param args Arguments passed to the plugin hook.
     * @param plugin The acutal plugin
     * @param replaceContext When passed, the plugin context can be overridden.
     */
    private runSync;
    private catcher;
}

declare class ValidationPluginError extends Error {
}
declare const validatePlugins: (plugins: KubbPlugin[], dependedPluginNames: string | string[]) => true;

/**
 * Abstract class that contains the building blocks for plugins to create their own Generator
 * @link idea based on https://github.com/colinhacks/zod/blob/master/src/types.ts#L137
 */
declare abstract class Generator<TOptions extends object = object> {
    private _options;
    constructor(options?: TOptions);
    get options(): TOptions;
    abstract build(...params: unknown[]): unknown;
}

/**
 * Abstract class that contains the building blocks for plugins to create their own SchemaGenerator
 */
declare abstract class SchemaGenerator<TOptions extends object, TInput, TOutput> extends Generator<TOptions> {
    abstract build(schema: TInput, name: string, description?: string): TOutput;
}

export { Argument0, CLIOptions, Cache, CacheStore, CorePluginOptions, EmittedFile, File, FileManager, FileName, Generator, KubbBuild, KubbConfig, KubbJSONPlugin, KubbPlugin, KubbPluginKind, KubbUserConfig, LogLevel, LogType, Logger, MaybePromise, OptionalPath, Path, PathMode, PluginContext, PluginFactoryOptions, PluginLifecycle, PluginLifecycleHooks, PluginManager, ResolveIdParams, SchemaGenerator, Status, Strategy, TransformResult, TreeNode, UUID, ValidationPluginError, build, clean, createPlugin, createPluginCache, build as default, defineConfig, format, getPathMode, getRelativePath, hooks, isPromise, read, validatePlugins, write };
