import { setTimeout } from 'timers/promises';
import { readdir, rm } from 'fs/promises';
import chalk from 'chalk';
import gitignore from 'parse-gitignore';
import semver from 'semver';
import { ResetMode } from 'simple-git';
import BaseGenerator from '../base/index.js';
import { packageJson } from '../../lib/index.js';
import EnvironmentBuilder from '../../cli/environment-builder.mjs';
import { SERVER_MAIN_RES_DIR } from '../generator-constants.js';
import { GIT_VERSION_NOT_ALLOW_MERGE_UNRELATED_HISTORIES, UPGRADE_BRANCH } from './support/index.js';
const GENERATOR_JHIPSTER = 'generator-begcode';
const GENERATOR_APP = 'app';
const DEFAULT_CLI_OPTIONS = '--force --skip-install --skip-git --ignore-errors --no-insight --skip-checks';
const DEFAULT_NON_INTERATIVE_OPTIONS = {
    skipInstall: true,
    skipGit: true,
    skipChecks: true,
    force: true,
    ignoreErrors: true,
    skipPriorities: ['prompting'],
};
const DEFAULT_MERGE_OPTIONS = ['--strategy', 'ours'];
export default class UpgradeGenerator extends BaseGenerator {
    requiredPackage = GENERATOR_JHIPSTER;
    createEnvBuilder;
    actualApplicationBranch;
    silent;
    applyConfig;
    spawnStdio = 'inherit';
    executable;
    verbose;
    async beforeQueue() {
        if (!this.fromBlueprint) {
            await this.composeWithBlueprints();
        }
    }
    get initializing() {
        return this.asInitializingTaskGroup({
            displayLogo() {
                this.log.log(chalk.green('Welcome to the JHipster Upgrade Sub-Generator'));
                this.log.log(chalk.green(`This will upgrade your current application codebase to JHipster version ${packageJson.version}`));
                this.log.log(chalk.green('For advanced options, please use the jhipster-migrate blueprint (https://github.com/jhipster/generator-jhipster-migrate/)'));
                this.log.log('');
            },
            initializeOptions() {
                if (this.silent) {
                    this.spawnStdio = 'ignore';
                }
                this.executable = this.executable ?? this.options.programName ?? 'jhipster';
                this.createEnvBuilder = this.options.createEnvBuilder ?? EnvironmentBuilder.createDefaultBuilder;
            },
            assertJHipsterProject() {
                if (!this.config.existed) {
                    throw new Error("Could not find a valid BegCode application configuration, check if the '.yo-rc.json' file exists and if the 'generator-begcode' key exists inside it.");
                }
                if (!this.config.get('baseName')) {
                    throw new Error('Current directory does not contain a JHipster project.');
                }
            },
            async checkoutDependency() {
                if (this.applyConfig)
                    return;
                const jhipsterVersion = this.getPackageJsonVersion();
                if (!jhipsterVersion) {
                    throw new Error('No generator-jhipster dependency found in your package.json');
                }
                if (this.isV7(jhipsterVersion)) {
                    if (jhipsterVersion !== '7.9.4') {
                        throw new Error('Upgrade the project to JHipster 7.9.4 before upgrading to the latest version.');
                    }
                    if ((this.jhipsterConfig.blueprints ?? []).length > 0) {
                        const errorMessage = 'Upgrade does not support upgrading a v7 project with blueprints. Please use the jhipster-migrate blueprint.';
                        if (!this.skipChecks) {
                            throw new Error(errorMessage);
                        }
                        else {
                            this.log.warn(errorMessage);
                        }
                    }
                }
            },
            async assertGitPresent() {
                if (!(await this.checkGitVersion(GIT_VERSION_NOT_ALLOW_MERGE_UNRELATED_HISTORIES))) {
                    this.log.warn('git is not found on your computer.\n', ` Install git: ${chalk.yellow('https://git-scm.com/')}`);
                    throw new Error('Exiting the process.');
                }
            },
            async assertGitRepository() {
                const git = this.createGit();
                if (!(await git.checkIsRepo())) {
                    this.log.warn('Current directory is not a git repository. Initializing git.');
                    await git.init().add('.').commit('initial commit', ['--allow-empty', '--no-verify']);
                }
            },
            async assertNoLocalChanges() {
                const result = await this.createGit().status();
                if (!result.isClean()) {
                    throw new Error(` local changes found.\n\tPlease commit/stash them before upgrading\n\t${result.files
                        .map(file => `${file.index} ${file.path}`)
                        .join('\n\t')}`);
                }
            },
            async detectCurrentBranch() {
                this.actualApplicationBranch = await this.createGit().revparse(['--abbrev-ref', 'HEAD']);
                if (this.actualApplicationBranch === UPGRADE_BRANCH) {
                    throw new Error('You are on the upgrade branch, please switch to another branch before upgrading.');
                }
            },
        });
    }
    get [BaseGenerator.INITIALIZING]() {
        return this.delegateTasksToBlueprint(() => this.initializing);
    }
    get default() {
        return this.asDefaultTaskGroup({
            async prepareUpgradeBranch() {
                const git = this.createGit();
                try {
                    await git.revparse(['--verify', UPGRADE_BRANCH]);
                    await git.checkout(UPGRADE_BRANCH);
                }
                catch {
                    await git.checkout(['--orphan', UPGRADE_BRANCH]);
                }
                await this.cleanUp();
                if (this.applyConfig) {
                    await this.runNonInteractive(false);
                }
                else {
                    await this.spawnCommand('npm install', { stdio: this.spawnStdio });
                    const customCliOptions = [];
                    if (this.getPackageJsonVersion() === '7.9.4') {
                        customCliOptions.push('--with-entities');
                    }
                    this.log.info(`Regenerating sources using ${this.executable} executable`);
                    await this.spawn('npx', ['--no', this.executable, ...customCliOptions, ...DEFAULT_CLI_OPTIONS.split(' ')], {
                        stdio: this.spawnStdio,
                    });
                }
                await this.rmRf(`${SERVER_MAIN_RES_DIR}config/tls/keystore.p12`);
                await git.add('.').commit(`generated ${UPGRADE_BRANCH} using JHipster ${this.getPackageJsonVersion()}`, ['--no-verify']);
                this.log.info('reference application created');
                await setTimeout(1000);
            },
            async prepareSourceBranch() {
                const git = this.createGit();
                await git
                    .checkout(this.actualApplicationBranch, ['-f'])
                    .merge([
                    UPGRADE_BRANCH,
                    ...DEFAULT_MERGE_OPTIONS,
                    '-m',
                    `reference merge of ${UPGRADE_BRANCH} branch into ${this.actualApplicationBranch}`,
                    '--allow-unrelated-histories',
                ]);
                this.log.info('reference application merged into source branch');
                await setTimeout(1000);
            },
            async updateUpgradeBranch() {
                const git = this.createGit();
                await git.checkout(UPGRADE_BRANCH);
                await this.cleanUp();
                await this.runNonInteractive();
                await this.rmRf(`${SERVER_MAIN_RES_DIR}config/tls/keystore.p12`);
                await git.add('.').commit(`generated ${UPGRADE_BRANCH} using JHipster ${packageJson.version}`, ['--no-verify']);
                this.log.info('upgrade commit created');
                await setTimeout(1000);
            },
            async upgradeSourceBranch() {
                const git = this.createGit();
                await git
                    .checkout(this.actualApplicationBranch, ['-f'])
                    .merge([UPGRADE_BRANCH, '-m', `upgrade merge of ${UPGRADE_BRANCH} branch into ${this.actualApplicationBranch}`])
                    .reset(ResetMode.HARD);
                this.log.info('upgrade application merged into source branch');
                if (!this.applyConfig) {
                    await this.rmRf('node_modules');
                    await this.rmRf('package-lock.json');
                    this.log.info('node_modules and package-lock.json removed to allow a clean install');
                }
            },
        });
    }
    get [BaseGenerator.DEFAULT]() {
        return this.delegateTasksToBlueprint(() => this.default);
    }
    get end() {
        return this.asEndTaskGroup({
            async end() {
                const diff = await this.createGit().diff(['--name-only', '--diff-filter', 'U']);
                this.log.ok(chalk.bold('upgraded successfully.'));
                if (diff) {
                    this.log.warn(`please fix conflicts listed below and commit!\n${diff}`);
                }
            },
        });
    }
    get [BaseGenerator.END]() {
        return this.delegateTasksToBlueprint(() => this.end);
    }
    async rmRf(file) {
        const absolutePath = this.destinationPath(file);
        if (this.verbose) {
            this.log.verboseInfo(`Removing ${absolutePath}`);
        }
        try {
            await rm(absolutePath, { recursive: true });
        }
        catch {
        }
    }
    async cleanUp() {
        const gitignoreContent = this.readDestination('.gitignore', { defaults: '' });
        const ignoredFiles = gitignoreContent ? (gitignore(gitignoreContent).patterns ?? []) : [];
        const filesToKeep = ['.yo-rc.json', '.jhipster', 'package.json', 'package-lock.json', 'node_modules', '.git', ...ignoredFiles];
        for (const file of await readdir(this.destinationPath())) {
            if (!filesToKeep.includes(file)) {
                await this.rmRf(file);
            }
        }
        this.log.info('cleaned up project directory');
    }
    getPackageJsonVersion() {
        return this.readDestinationJSON('package.json').devDependencies?.['generator-jhipster'];
    }
    isV7(version) {
        return version.includes('.') && parseInt(version.split('.', 2), 10) < 8;
    }
    async runNonInteractive(inherit = true) {
        const adapter = this.env.adapter.newAdapter?.();
        const sharedFs = inherit ? this.env.sharedFs : undefined;
        const inheritedOptions = inherit ? this.options : {};
        const envOptions = { sharedFs, adapter };
        const generatorOptions = { ...inheritedOptions, ...DEFAULT_NON_INTERATIVE_OPTIONS };
        delete generatorOptions.sharedData;
        const envBuilder = await this.createEnvBuilder(envOptions);
        const env = envBuilder.getEnvironment();
        await env.run([`begcode:${GENERATOR_APP}`], generatorOptions);
    }
    async checkGitVersion(minVersion) {
        try {
            const rawVersion = await this.createGit().raw('--version');
            const gitVersion = String(rawVersion.match(/([0-9]+\.[0-9]+\.[0-9]+)/g));
            if (minVersion) {
                return semver.gte(gitVersion, minVersion);
            }
            return true;
        }
        catch {
            return false;
        }
    }
}
