/**
 * 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 {ModuleObject} from 'yargs';
import typeof Yargs from 'yargs';

const {makeAsyncCommand, watchFile} = require('../cli-utils');
const {loadConfig, resolveConfig} = require('metro-config');
const {promisify} = require('util');

type Args = $ReadOnly<{
  projectRoots?: $ReadOnlyArray<string>,
  host: string,
  port: number,
  maxWorkers?: number,
  secure?: boolean,
  secureKey?: string,
  secureCert?: string,
  secureServerOptions?: string,
  hmrEnabled?: boolean,
  config?: string,
  resetCache?: boolean,
}>;

module.exports = (): {
  ...ModuleObject,
  handler: Function,
} => ({
  command: 'serve',
  aliases: ['start'],
  desc: 'Starts Metro on the given port, building bundles on the fly',

  builder: (yargs: Yargs): void => {
    yargs.option('project-roots', {
      alias: 'P',
      type: 'string',
      array: true,
    });

    yargs.option('host', {alias: 'h', type: 'string', default: 'localhost'});
    yargs.option('port', {alias: 'p', type: 'number', default: 8081});

    yargs.option('max-workers', {alias: 'j', type: 'number'});

    yargs.option('secure', {type: 'boolean', describe: '(deprecated)'});
    yargs.option('secure-key', {type: 'string', describe: '(deprecated)'});
    yargs.option('secure-cert', {type: 'string', describe: '(deprecated)'});
    yargs.option('secure-server-options', {
      alias: 's',
      type: 'string',
      describe: 'Use dot notation for object path',
    });

    yargs.option('hmr-enabled', {alias: 'hmr', type: 'boolean'});

    yargs.option('config', {alias: 'c', type: 'string'});

    // Deprecated
    yargs.option('reset-cache', {type: 'boolean'});

    // Examples
    yargs.example(
      'secure-server-options',
      '-s.cert="$(cat path/to/cert)" -s.key="$(cat path/to/key")',
    );
  },

  handler: makeAsyncCommand(async (argv: Args) => {
    let server = null;
    let restarting = false;

    async function restart(): Promise<void> {
      if (restarting) {
        return;
      } else {
        restarting = true;
      }

      if (server) {
        // eslint-disable-next-line no-console
        console.log('Configuration changed. Restarting the server...');
        // $FlowFixMe[method-unbinding] added when improving typing for this parameters
        await promisify(server.close).call(server);
      }

      const config = await loadConfig(argv);

      // Inline require() to avoid circular dependency with ../index
      const MetroApi = require('../index');

      const {
        config: _config,
        hmrEnabled: _hmrEnabled,
        maxWorkers: _maxWorkers,
        port: _port,
        projectRoots: _projectRoots,
        resetCache: _resetCache,
        ...runServerOptions
      } = argv;
      server = await MetroApi.runServer(config, runServerOptions);

      restarting = false;
    }

    const foundConfig = await resolveConfig(argv.config);

    if (foundConfig) {
      await watchFile(foundConfig.filepath, restart);
    } else {
      await restart();
    }
  }),
});