/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 * @oncall react_native
 */

'use strict';

/*:: import type {ProjectInfo} from '../utils/monorepo'; */

const {retry} = require('../circleci/retry');
const {PACKAGES_DIR, REPO_ROOT} = require('../consts');
const {getPackages} = require('../utils/monorepo');
const {
  VERDACCIO_SERVER_URL,
  VERDACCIO_STORAGE_PATH,
  setupVerdaccio,
} = require('./utils/verdaccio');
const {parseArgs} = require('@pkgjs/parseargs');
const chalk = require('chalk');
const {execSync} = require('child_process');
const fs = require('fs');
const path = require('path');
const {popd, pushd} = require('shelljs');

const config = {
  options: {
    projectName: {type: 'string'},
    directory: {type: 'string'},
    currentBranch: {type: 'string'},
    pathToLocalReactNative: {type: 'string'},
    verbose: {type: 'boolean', default: false},
    useHelloWorld: {type: 'boolean', default: false},
    help: {type: 'boolean'},
  },
};

async function main() {
  const {
    values: {help, ...options},
  } = parseArgs(config);

  if (help) {
    console.log(`
  Usage: node ./scripts/e2e/init-project-e2e.js [OPTIONS]

  Bootstraps a React Native project, using the currently checked out
  repository as the source of truth for the react-native package and
  dependencies.

  - Configures and starts a local npm proxy (Verdaccio).
  - Builds and publishes all in-repo dependencies to the local npm proxy.
  - Runs either \`react-native init\` or uses packages/hello-world/ with the local
    npm proxy configured.
  - Does NOT install CocoaPods dependencies.

  This script works with either "npx @react-native-community/cli init" or
  by preparing the packages/hello-world/ app to be built.

  Note: This script will mutate the contents of some package files, which
  should not be committed.

  Options:
    --projectName             The name of the new React Native project.
    --currentBranch           The current branch to checkout.
    --directory               The absolute path to the target project directory.
    --pathToLocalReactNative  The absolute path to the local react-native package.
    --verbose                 Print additional output. Default: false.
    --useHelloWorld           Use the hello-world package instead of the init command. Default: false
    `);
    return;
  }

  await initNewProjectFromSource(options);

  // TODO(T179377112): Fix memory leak from `spawn` in `setupVerdaccio` (above
  // kill command does not wait for kill success).
  process.exit(0);
}

async function initNewProjectFromSource(
  {
    projectName,
    directory,
    currentBranch,
    pathToLocalReactNative,
    verbose = false,
    useHelloWorld = false,
  } /*: {projectName: string, directory: string, currentBranch: string, pathToLocalReactNative: string, verbose?: boolean, useHelloWorld?: boolean} */,
) {
  console.log('Starting local npm proxy (Verdaccio)');
  const verdaccioPid = setupVerdaccio();
  console.log('Done ✅');

  try {
    execSync('node ./scripts/build/build.js', {
      cwd: REPO_ROOT,
      stdio: 'inherit',
    });
    console.log('\nDone ✅');

    const packages = await getPackages({
      includeReactNative: false,
      includePrivate: false,
    });

    // packages are updated in a lockstep, let's get the version of the first one
    const version = packages[Object.keys(packages)[0]].packageJson.version;

    console.log('Publishing packages to local npm proxy\n');
    for (const {path: packagePath, packageJson} of Object.values(packages)) {
      const desc = `${packageJson.name} (${path.relative(
        REPO_ROOT,
        packagePath,
      )})`;
      process.stdout.write(
        `${desc} ${chalk.dim('.').repeat(Math.max(0, 72 - desc.length))} `,
      );
      execSync(
        `npm publish --registry ${VERDACCIO_SERVER_URL} --access public`,
        {
          cwd: packagePath,
          stdio: verbose ? 'inherit' : [process.stderr],
        },
      );
      process.stdout.write(chalk.reset.inverse.bold.green(' DONE ') + '\n');
    }
    console.log('\nDone ✅');

    if (useHelloWorld) {
      console.log('Preparing packages/helloworld/ to be built');
      _prepareHelloWorld(version, pathToLocalReactNative);
      directory = path.join(PACKAGES_DIR, 'helloworld');
    } else {
      const pathToTemplate = _prepareTemplate(
        version,
        pathToLocalReactNative,
        currentBranch,
      );

      console.log(
        'Running @react-native-community/cli@next init without install',
      );
      execSync(
        `npx @react-native-community/cli@next init ${projectName} \
          --directory ${directory} \
          --template file://${pathToTemplate} \
          --verbose \
          --pm npm \
          --skip-install`,
        {
          // Avoid loading packages/react-native/react-native.config.js
          cwd: REPO_ROOT,
          stdio: 'inherit',
        },
      );
    }
    console.log('\nDone ✅');

    console.log('Installing project dependencies');
    await installProjectUsingProxy(directory);
    console.log('Done ✅');
  } catch (e) {
    console.log('Failed ❌');
    throw e;
  } finally {
    console.log(`Cleanup: Killing Verdaccio process (PID: ${verdaccioPid})`);
    try {
      execSync(`kill ${verdaccioPid} || kill -9 ${verdaccioPid}`);
      execSync('killall verdaccio');
      console.log('Done ✅');
    } catch {
      console.warn('Failed to kill Verdaccio process');
    }
    console.log('Cleanup: Removing Verdaccio storage directory');
    execSync(`rm -rf ${VERDACCIO_STORAGE_PATH}`);
    console.log('Done ✅');
  }
}

async function installProjectUsingProxy(cwd /*: string */) {
  const execOptions = {
    cwd,
    stdio: 'inherit',
  };

  // TODO(huntie): Review pre-existing retry limit
  console.log(
    `Running 'npm install --registry ${VERDACCIO_SERVER_URL}' inside ${cwd}`,
  );
  const success = await retry('npm', execOptions, 3, 500, [
    'install',
    '--registry',
    VERDACCIO_SERVER_URL,
  ]);

  if (!success) {
    throw new Error('Failed to install project dependencies');
  }
}

function _prepareHelloWorld(
  version /*: string */,
  pathToLocalReactNative /*: ?string*/,
) {
  const helloworldDir = path.join(PACKAGES_DIR, 'helloworld');
  const helloworldPackageJson = path.join(helloworldDir, 'package.json');
  const packageJson = JSON.parse(
    fs.readFileSync(helloworldPackageJson, 'utf8'),
  );

  // and update the dependencies and devDependencies of packages scoped as @react-native
  // to the version passed as parameter
  for (const key of Object.keys(packageJson.dependencies)) {
    if (key.startsWith('@react-native/')) {
      packageJson.dependencies[key] = version;
    }
  }
  for (const key of Object.keys(packageJson.devDependencies)) {
    if (key.startsWith('@react-native/')) {
      packageJson.devDependencies[key] = version;
    }
  }
  if (pathToLocalReactNative != null) {
    packageJson.dependencies['react-native'] = `file:${pathToLocalReactNative}`;
  }

  // write the package.json to disk
  fs.writeFileSync(helloworldPackageJson, JSON.stringify(packageJson, null, 2));
}

function _prepareTemplate(
  version /*: string */,
  pathToLocalReactNative /*: ?string*/,
  currentBranch /*: string*/,
) {
  console.log('Prepare template locally');

  const templateCloneBaseFolder = '/tmp/react-native-tmp/template';
  execSync(`rm -rf ${templateCloneBaseFolder}`);

  execSync(
    `git clone https://github.com/react-native-community/template ${templateCloneBaseFolder}`,
  );

  pushd(templateCloneBaseFolder);

  execSync(`git checkout ${currentBranch}`);

  pushd('template');

  // read the package.json
  const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));

  // and update the dependencies and devDependencies of packages scoped as @react-native
  // to the version passed as parameter
  for (const key of Object.keys(packageJson.dependencies)) {
    if (key.startsWith('@react-native/')) {
      packageJson.dependencies[key] = version;
    }
  }

  for (const key of Object.keys(packageJson.devDependencies)) {
    if (key.startsWith('@react-native/')) {
      packageJson.devDependencies[key] = version;
    }
  }

  if (pathToLocalReactNative != null) {
    packageJson.dependencies['react-native'] = `file:${pathToLocalReactNative}`;
  }

  // write the package.json to disk
  fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2));

  popd();
  const templateTgz = execSync('npm pack').toString().trim();

  popd();

  console.log('Done ✅');
  return path.join(templateCloneBaseFolder, templateTgz);
}

module.exports = {
  initNewProjectFromSource,
};

if (require.main === module) {
  // eslint-disable-next-line no-void
  void main();
}
