/* This file is a part of @mdn/browser-compat-data
 * See LICENSE file for more information. */

import { styleText } from 'node:util';

import esMain from 'es-main';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { temporaryWriteTask } from 'tempy';

import { spawn } from '../../utils/index.js';

import { getSemverBumpPulls } from './semver-pulls.js';
import { getStats } from './stats.js';
import { getChanges } from './changes.js';
import { getNotes, addNotes } from './notes.js';
import {
  requireGitHubCLI,
  requireWriteAccess,
  getLatestTag,
  getRefDate,
  keypress,
  fetchMain,
} from './utils.js';

/**
 * Perform the commit and submit a pull request
 * @param {string} message The commit message
 * @param {boolean} wait Whether to wait for user to update the release notes (used when semver bump is minor or major)
 * @param {object} options Commit options
 * @param {string} options.branch the branch to commit to
 * @param {{title: string, body: string}} options.pr PR options
 * @returns {Promise<void>}
 */
const commitAndPR = async (message, wait, { branch, pr }) => {
  if (wait) {
    console.log('');
    console.log(
      styleText(
        'yellow',
        `Please ${styleText('bold', 'modify RELEASE_NOTES.md')} and fill out the ${styleText('bold', 'Notable changes')} section. I'll wait for you.`,
      ),
    );
    console.log(styleText('yellow', 'Press any key to continue.'));
    await keypress();
    console.log('');
  }

  console.log(styleText('blue', `Preparing ${branch} branch...`));
  spawn('git', ['stash']);
  spawn('git', ['switch', '-C', branch, 'origin/main']);
  spawn('git', ['stash', 'pop']);
  spawn('git', [
    'add',
    'package.json',
    'package-lock.json',
    'RELEASE_NOTES.md',
    'release_notes/',
  ]);

  console.log(styleText('blue', 'Committing changes...'));
  await temporaryWriteTask(message, (commitFile) =>
    spawn('git', ['commit', '--file', commitFile]),
  );

  console.log(styleText('blue', `Pushing ${branch} branch...`));
  spawn('git', ['push', '--force', '--set-upstream', 'origin', branch]);

  console.log(styleText('blue', 'Creating/editing pull request...'));
  await temporaryWriteTask(pr.body, (bodyFile) => {
    const commonArgs = ['--title', pr.title, '--body-file', bodyFile];
    try {
      const stdout = spawn('gh', ['pr', 'create', '--draft', ...commonArgs]);
      console.log(stdout);
    } catch {
      const stdout = spawn('gh', ['pr', 'edit', ...commonArgs]);
      console.log(stdout);
    }
  });

  spawn('git', ['switch', '-']);
  spawn('git', ['branch', '-d', branch]);
};

/**
 * Perform the release
 * @param {object} options The release options
 * @param {boolean} options.dryRun Whether to simulate the release locally
 * @returns {Promise<void>}
 */
const main = async ({ dryRun }) => {
  if (dryRun) {
    console.log(styleText('green', 'Simulating release...'));
  }

  requireGitHubCLI();
  if (!dryRun) {
    requireWriteAccess();
  }

  console.log(styleText('blue', 'Fetching main branch...'));
  fetchMain();

  console.log(styleText('blue', 'Getting last version...'));
  const lastVersion = getLatestTag();
  const lastVersionDate = getRefDate(lastVersion);

  // Determine what semver part to bump
  console.log(
    styleText('blue', 'Checking merged PRs to determine semver bump level...'),
  );
  const semverBumpPulls = getSemverBumpPulls(lastVersionDate);
  /** @type {'major' | 'minor' | 'patch'} */
  const versionBump = semverBumpPulls.major.length
    ? 'major'
    : semverBumpPulls.minor.length
      ? 'minor'
      : 'patch';

  // Perform version bump
  spawn('npm', ['version', '--no-git-tag-version', versionBump]);
  const thisVersion =
    'v' +
    JSON.parse(spawn('npm', ['version', '--json']))['@mdn/browser-compat-data'];

  console.log(
    styleText(
      'green',
      `Performed ${styleText('bold', versionBump)} bump from ${styleText('bold', lastVersion)} to ${styleText('bold', thisVersion)}`,
    ),
  );

  console.log(styleText('blue', 'Getting statistics...'));
  const stats = await getStats(lastVersion, thisVersion, lastVersionDate);

  console.log(styleText('blue', 'Getting lists of added/removed features...'));
  const changes = await getChanges(lastVersionDate);

  console.log(styleText('blue', 'Updating release notes...'));
  const notes = getNotes(thisVersion, changes, stats, versionBump);
  await addNotes(notes, versionBump, lastVersion);

  if (!dryRun) {
    const title = `Release ${thisVersion}`;
    const body = `(This release was generated by the project's release script.)\n\n${notes}`;
    await commitAndPR(
      title,
      !process.env.GITHUB_ACTIONS && versionBump !== 'patch',
      {
        branch: 'release',
        pr: { title, body },
      },
    );
  }

  console.log(styleText('blue', styleText('bold', 'Done!')));
};

if (esMain(import.meta)) {
  const argv = yargs(hideBin(process.argv))
    .command(
      '$0',
      'Prepares a release by determining changes since the last release, and creating/updating a release PR',
    )
    .option('dry-run', {
      alias: 'n',
      describe: "Don't commit, push or PR",
      type: 'boolean',
      default: false,
    })
    .parseSync();

  await main({ dryRun: argv.dryRun });
}
