import { FeScriptContextOptions, Shell, FeReleaseConfig, FeScriptContext } from '@qlover/scripts-context';
import { AsyncExecutor, ExecutorContext, ExecutorPlugin } from '@qlover/fe-corekit';
import { LoggerInterface } from '@qlover/logger';
import { Env } from '@qlover/env-loader';
import { CommitField } from 'gitlog';
import { OptionValues } from 'commander';

declare class ReleaseTask {
    private executor;
    private defaultTuples;
    protected context: ReleaseContext;
    constructor(options?: ReleaseContextOptions, executor?: AsyncExecutor, defaultTuples?: PluginTuple<PluginClass>[]);
    getContext(): ReleaseContext;
    usePlugins(externalTuples?: PluginTuple<PluginClass>[]): Promise<Plugin[]>;
    run(): Promise<unknown>;
    exec(externalTuples?: PluginTuple<PluginClass>[]): Promise<unknown>;
}

interface WorkspacesProps {
    /**
     * Whether to skip workspaces
     *
     * @default `false`
     */
    skip?: boolean;
    /**
     * Whether to skip checking the package.json file
     *
     * @default `false`
     */
    skipCheckPackage?: boolean;
    /**
     * The workspace to publish
     */
    workspace?: WorkspaceValue;
    /**
     * The workspaces to publish
     * @private
     */
    workspaces?: WorkspaceValue[];
    /**
     * The change labels
     *
     * from `changePackagesLabel`
     */
    changeLabels?: string[];
    /**
     * The changed paths
     * @private
     */
    changedPaths?: string[];
    /**
     * The packages
     * @private
     */
    packages?: string[];
    /**
     * All project packages mapping
     * @private
     */
    projectWorkspaces?: WorkspaceValue[];
}
interface WorkspaceValue {
    name: string;
    version: string;
    /**
     * The relative path of the workspace
     */
    path: string;
    /**
     * The absolute path of the workspace
     */
    root: string;
    /**
     * The package.json of the workspace
     */
    packageJson: PackageJson;
    /**
     * The tag name of the workspace
     * @private
     */
    tagName?: string;
    /**
     * The last tag name of the workspace
     * @private
     */
    lastTag?: string;
    /**
     * The changelog of the workspace
     * @private
     */
    changelog?: string;
}

type ReleaseParamsConfig = {
    /**
     * Max number of workspaces to include in the release name
     *
     * @default 3
     */
    maxWorkspace?: number;
    /**
     * Multi-workspace separator
     *
     * @default '_'
     */
    multiWorkspaceSeparator?: string;
    /**
     * Workspace version separator
     *
     * @default '@'
     */
    workspaceVersionSeparator?: string;
    /**
     * The branch name for batch release
     *
     * @default `batch-${releaseName}-${length}-packages-${timestamp}`
     */
    batchBranchName?: string;
    /**
     * The tag name for batch release
     *
     * @default `batch-${length}-packages-${timestamp}`
     */
    batchTagName?: string;
    /**
     * The PR title for batch release
     *
     * default from feConfig.release.PRTitle
     *
     * @default `Release ${env} ${pkgName} ${tagName}`
     */
    PRTitle?: string;
    /**
     * The PR body for batch release
     *
     * default from feConfig.release.PRBody
     */
    PRBody?: string;
};

interface GitBaseProps {
    /**
     * The token for the GitHub API
     *
     * @default `GITHUB_TOKEN`
     */
    tokenRef?: string;
    /**
     * The timeout for the GitHub API
     */
    timeout?: number;
}

interface GithubPRProps extends ReleaseParamsConfig, GitBaseProps {
    /**
     * Whether to dry run the creation of the pull request
     *
     * - create pr
     * - changeset publish
     *
     * @default `false`
     */
    dryRunCreatePR?: boolean;
    /**
     * Whether to skip the release
     *
     * @default `false`
     */
    skip?: boolean;
    /**
     * Whether to publish a PR
     *
     * @default `false`
     */
    releasePR?: boolean;
    /**
     * The commit message of the release
     *
     * support WorkspaceValue
     *
     * @default 'chore(tag): {{name}} v${version}'
     */
    commitMessage?: string;
    /**
     * The commit args of the release
     *
     * @default []
     */
    commitArgs?: string[];
    /**
     * The release name of the release
     *
     * @default 'Release ${name} v${version}'
     */
    releaseName?: string;
    /**
     * Whether to create a draft release
     *
     * @default false
     */
    draft?: boolean;
    /**
     * Whether to create a pre-release
     *
     * @default false
     */
    preRelease?: boolean;
    /**
     * Whether to auto-generate the release notes
     *
     * @default false
     */
    autoGenerate?: boolean;
    /**
     * Whether to make the latest release
     *
     * @default true
     */
    makeLatest?: boolean | 'true' | 'false' | 'legacy';
    /**
     * The release notes of the release
     *
     * @default undefined
     */
    releaseNotes?: string;
    /**
     * The discussion category name of the release
     *
     * @default undefined
     */
    discussionCategoryName?: string;
    /**
     * Whether to push the changed labels to the release PR
     *
     * @default false
     */
    pushChangeLabels?: boolean;
}

interface ExecutorReleaseContext extends ExecutorContext<ReleaseContext> {
    returnValue: ReleaseReturnValue;
}
type ReleaseReturnValue = {
    githubToken?: string;
    [key: string]: unknown;
};
type DeepPartial<T> = {
    [P in keyof T]?: DeepPartial<T[P]>;
};
interface ReleaseConfig {
    githubPR?: GithubPRProps;
    workspaces?: WorkspacesProps;
}
interface ReleaseContextOptions<T extends ReleaseConfig = ReleaseConfig> extends Omit<FeScriptContextOptions<T>, 'constructor'> {
    shared?: SharedReleaseOptions;
}
type StepOption<T> = {
    label: string;
    enabled?: boolean;
    task: () => Promise<T>;
};
type PackageJson = Record<string, unknown>;
interface TemplateContext extends SharedReleaseOptions, WorkspaceValue {
    publishPath: string;
    /**
     * @deprecated  use `releaseEnv` from `shared`
     */
    env: string;
    /**
     * @deprecated  use `sourceBranch` from `shared`
     */
    branch: string;
}

declare abstract class Plugin<Props = unknown> implements ExecutorPlugin<ReleaseContext> {
    protected context: ReleaseContext;
    readonly pluginName: string;
    protected props: Props;
    readonly onlyOne = true;
    constructor(context: ReleaseContext, pluginName: string, props?: Props);
    getInitialProps(props?: Props): Props;
    get logger(): LoggerInterface;
    get shell(): Shell;
    get options(): Props;
    getEnv(key: string, defaultValue?: string): string | undefined;
    enabled(_name: string, _context: ExecutorReleaseContext): boolean;
    getConfig<T>(keys?: string | string[], defaultValue?: T): T;
    setConfig(config: DeepPartial<Props>): void;
    onBefore?(_context: ExecutorReleaseContext): void | Promise<void>;
    onExec?(_context: ExecutorReleaseContext): void | Promise<void>;
    onSuccess?(_context: ExecutorReleaseContext): void | Promise<void>;
    onError?(_context: ExecutorReleaseContext): void | Promise<void>;
    /**
     * run a step
     *
     * this will log the step and return the result of the task
     *
     * @param label - the label of the step
     * @param task - the task to run
     * @returns the result of the task
     */
    step<T>({ label, task }: StepOption<T>): Promise<T>;
}

/**
 * Represents a class that extends Plugin.
 */
type PluginClass<T extends unknown[] = any[]> = new (...args: T) => Plugin;
/**
 * Represents the constructor parameters for a specific Plugin class, excluding the first parameter.
 * This assumes that the constructor parameters are known and can be inferred.
 */
type PluginConstructorParams<T extends PluginClass> = T extends new (first: any, ...args: infer P) => unknown ? P : never;
type PluginTuple<T extends PluginClass> = [
    T | string,
    ...PluginConstructorParams<T>
];
declare function tuple<T extends PluginClass>(plugin: T | string, ...args: PluginConstructorParams<T>): PluginTuple<T>;

/**
 * This is the shared options for the release.
 *
 * extends `FeReleaseConfig`
 */
interface SharedReleaseOptions extends FeReleaseConfig {
    /**
     * The source branch of the project
     *
     * default:
     * - first, get from `FE_RELEASE_SOURCE_BRANCH`
     * - second, get from `FE_RELEASE_BRANCH`
     * - `master`
     *
     */
    sourceBranch?: string;
    /**
     * The environment of the project
     *
     * default:
     * - first, get from `FE_RELEASE_ENV`
     * - second, get from `NODE_ENV`
     * - `development`
     */
    releaseEnv?: string;
    /**
     * The root path of the project
     *
     * @default `process.cwd()`
     */
    rootPath?: string;
    /**
     * Plugins
     */
    plugins?: PluginTuple<PluginClass<unknown[]>>[];
    /**
     * The name of the repository
     */
    repoName?: string;
    /**
     * The name of the author
     */
    authorName?: string;
    /**
     * The current branch of the project
     */
    currentBranch?: string;
}

declare class ReleaseContext<T extends ReleaseConfig = ReleaseConfig> extends FeScriptContext<T> {
    protected readonly _env: Env;
    /**
     * Shared Config
     */
    shared: SharedReleaseOptions;
    constructor(context: ReleaseContextOptions<T>);
    private getDefaultShreadOptions;
    get rootPath(): string;
    get sourceBranch(): string;
    get releaseEnv(): string;
    get env(): Env;
    get workspaces(): WorkspaceValue[] | undefined;
    get workspace(): WorkspaceValue | undefined;
    setWorkspaces(workspaces: WorkspaceValue[]): void;
    setConfig(config: DeepPartial<ReleaseConfig>): void;
    getConfig<T = unknown>(key: string | string[], defaultValue?: T): T;
    setShared(shared: Partial<SharedReleaseOptions>): void;
    getPkg<T>(key?: string, defaultValue?: T): T;
    getTemplateContext(): TemplateContext;
    runChangesetsCli(name: string, args?: string[]): Promise<string>;
}

type ReleaseLabelCompare = (changedFilePath: string, packagePath: string) => boolean;
interface ReleaseLabelOptions {
    /**
     * The change packages label
     */
    changePackagesLabel: string;
    /**
     * The packages directories
     */
    packagesDirectories: string[];
    compare?: ReleaseLabelCompare;
}
declare class ReleaseLabel {
    private readonly options;
    constructor(options: ReleaseLabelOptions);
    compare(changedFilePath: string, packagePath: string): boolean;
    toChangeLabel(packagePath: string, label?: string): string;
    toChangeLabels(packages: string[], label?: string): string[];
    pick(changedFiles: Array<string> | Set<string>, packages?: string[]): string[];
}

type BaseCommit = {
    [key in CommitField]: string | undefined;
};
interface GitChangelogOptions {
    /**
     * start tag
     */
    from?: string;
    /**
     * end tag
     */
    to?: string;
    /**
     * log directory
     */
    directory?: string;
    /**
     * gitlog default fields
     * @default ["abbrevHash", "hash", "subject", "authorName", "authorDate"]
     */
    fileds?: CommitField[];
    /**
     * not include merge commit
     * @default true
     */
    noMerges?: boolean;
    /**
     * custom commit type
     */
    types?: {
        type: string;
        section?: string;
        hidden?: boolean;
    }[];
    /**
     * custom commit format
     *
     * - support `CommitValue` properties
     * - add scopeHeader, commitLink, prLink
     *
     * @default '\n- ${scopeHeader} ${commitlint.message} ${commitLink} ${prLink}'
     */
    formatTemplate?: string;
    /**
     * whether to include commit body
     * @since 2.3.0
     * @default false
     */
    commitBody?: boolean;
}
interface Commitlint {
    type?: string;
    scope?: string;
    message: string;
    /**
     * commit body, remove repeat title
     * @since 2.3.0
     */
    body?: string;
}
interface CommitValue {
    /**
     * git log base info
     */
    base: BaseCommit;
    /**
     * parsed commitlint info
     */
    commitlint: Commitlint;
    /**
     * parsed commitlint info
     */
    commits: CommitValue[];
    /**
     * pr number
     */
    prNumber?: string;
}
interface ChangelogFormatter {
    format<Opt extends GitChangelogOptions>(commits: unknown[], options?: Opt): string[];
}
interface ChangeLogInterface {
    getCommits(options?: GitChangelogOptions): Promise<CommitValue[]>;
}

declare const CHANGELOG_ALL_FIELDS: CommitField[];
interface GitChangelogProps extends GitChangelogOptions {
    shell: Shell;
    logger: LoggerInterface;
}
declare class GitChangelog implements ChangeLogInterface {
    protected options: GitChangelogProps;
    constructor(options: GitChangelogProps);
    /**
     * Get the git log
     *
     * @param options
     * @returns
     */
    getGitLog(options?: GitChangelogOptions): Promise<BaseCommit[]>;
    getCommits(options?: GitChangelogOptions): Promise<CommitValue[]>;
    protected createBaseCommit(message: string, target?: Partial<BaseCommit>): BaseCommit;
    /**
     * Tabify the body
     *
     * @since 2.3.2
     * @param body
     * @param size
     * @returns
     */
    tabify(body: string, size?: number): string;
    parseCommitlint(subject: string, rawBody?: string): Commitlint;
    toCommitValue(hash: string, message: string): CommitValue;
    protected resolveTag(tag?: string, fallback?: string): Promise<string>;
}

interface Options extends GitChangelogOptions {
    repoUrl?: string;
}
declare class GitChangelogFormatter implements ChangelogFormatter {
    protected options: Options & {
        shell: Shell;
    };
    constructor(options: Options & {
        shell: Shell;
    });
    format(commits: CommitValue[], options?: Options): string[];
    formatCommit(commit: CommitValue, options?: Options): string;
    foramtLink(target: string, url?: string): string;
    formatCommitLink(target: string, url?: string): string;
    formatScope(scope: string): string;
}

interface GithubChangelogProps extends GitChangelogProps {
    mergePRcommit?: boolean;
    githubRootPath?: string;
}

declare function load<T>(pluginName: string): Promise<[string, T]>;
declare function loaderPluginsFromPluginTuples<T extends Plugin<unknown>>(context: ReleaseContext, pluginsTuples: PluginTuple<PluginClass>[], maxLimit?: number): Promise<T[]>;

type ConstructorType<T, Args extends unknown[]> = (new (...args: Args) => T) | ((...args: Args) => T);
declare function factory<T, Args extends unknown[]>(Constructor: ConstructorType<T, Args>, ...args: Args): T;

declare function reduceOptions(opts: OptionValues, commonKey?: string): OptionValues;

export { CHANGELOG_ALL_FIELDS, GitChangelog, GitChangelogFormatter, Plugin, ReleaseContext, ReleaseLabel, ReleaseTask, factory, load, loaderPluginsFromPluginTuples, reduceOptions, tuple };
export type { ConstructorType, DeepPartial, ExecutorReleaseContext, GitChangelogProps, GithubChangelogProps, Options, PackageJson, PluginClass, PluginConstructorParams, PluginTuple, ReleaseConfig, ReleaseContextOptions, ReleaseLabelCompare, ReleaseLabelOptions, ReleaseReturnValue, StepOption, TemplateContext };
