#!/usr/bin/env node

'use strict';

/**
 * This wrapper executable checks for known node flags and appends them when found, before invoking the "real" _mocha(1) executable.
 *
 * @module bin/mocha
 * @private
 */

const {deprecate, warn} = require('../lib/utils');
const {spawn} = require('child_process');
const {loadOptions} = require('../lib/cli/options');
const {
  unparseNodeFlags,
  isNodeFlag,
  impliesNoTimeouts
} = require('../lib/cli/node-flags');
const unparse = require('yargs-unparser');
const debug = require('debug')('mocha:cli:mocha');
const {aliases} = require('../lib/cli/run-option-metadata');
const nodeEnv = require('node-environment-flags');

const mochaPath = require.resolve('./_mocha');
const mochaArgs = {};
const nodeArgs = {};

const opts = loadOptions(process.argv.slice(2));
debug('loaded opts', opts);

/**
 * Given option/command `value`, disable timeouts if applicable
 * @param {string} [value] - Value to check
 * @ignore
 */
const disableTimeouts = value => {
  if (impliesNoTimeouts(value)) {
    debug(`option "${value}" disabled timeouts`);
    mochaArgs.timeout = 0;
    delete mochaArgs.timeouts;
    delete mochaArgs.t;
  }
};

/**
 * If `value` begins with `v8-` and is not explicitly `v8-options`, remove prefix
 * @param {string} [value] - Value to check
 * @returns {string} `value` with prefix (maybe) removed
 * @ignore
 */
const trimV8Option = value =>
  value !== 'v8-options' && /^v8-/.test(value) ? value.slice(3) : value;

// sort options into "node" and "mocha" buckets
Object.keys(opts).forEach(opt => {
  if (isNodeFlag(opt)) {
    nodeArgs[trimV8Option(opt)] = opts[opt];
    disableTimeouts(opt);
  } else {
    mochaArgs[opt] = opts[opt];
  }
});

// Native debugger handling
// see https://nodejs.org/api/debugger.html#debugger_debugger
// look for 'debug' or 'inspect' that would launch this debugger,
// remove it from Mocha's opts and prepend it to Node's opts.
// also coerce depending on Node.js version.
// A deprecation warning will be printed by node, if applicable.
// (mochaArgs._ are "positional" arguments, not prefixed with - or --)
if (/^(debug|inspect)$/.test(mochaArgs._[0])) {
  const command = mochaArgs._.shift();
  disableTimeouts(command);
  // don't conflict with inspector
  ['debug', 'inspect', 'debug-brk', 'inspect-brk']
    .filter(opt => opt in nodeArgs || opt in mochaArgs)
    .forEach(opt => {
      warn(`command "${command}" provided; --${opt} ignored`);
      delete nodeArgs[opt];
      delete mochaArgs[opt];
    });
  nodeArgs._ = [
    parseInt(
      process.version
        .slice(1)
        .split('.')
        .shift(),
      10
    ) >= 8
      ? 'inspect'
      : 'debug'
  ];
}

// allow --debug to invoke --inspect on Node.js v8 or newer.
['debug', 'debug-brk']
  .filter(opt => opt in nodeArgs && !nodeEnv.has(opt))
  .forEach(opt => {
    const newOpt = opt === 'debug' ? 'inspect' : 'inspect-brk';
    warn(
      `"--${opt}" is not available in Node.js ${
        process.version
      }; use "--${newOpt}" instead.`
    );
    nodeArgs[newOpt] = nodeArgs[opt];
    mochaArgs.timeout = false;
    debug(`--${opt} -> ${newOpt}`);
    delete nodeArgs[opt];
  });

// historical
if (nodeArgs.gc) {
  deprecate(
    '"-gc" is deprecated and will be removed from a future version of Mocha.  Use "--gc-global" instead.'
  );
  nodeArgs['gc-global'] = nodeArgs.gc;
  delete nodeArgs.gc;
}

debug('final node args', nodeArgs);

const args = [].concat(
  unparseNodeFlags(nodeArgs),
  mochaPath,
  unparse(mochaArgs, {alias: aliases})
);

debug(`exec ${process.execPath} w/ args:`, args);

const proc = spawn(process.execPath, args, {
  stdio: 'inherit'
});

proc.on('exit', (code, signal) => {
  process.on('exit', () => {
    if (signal) {
      process.kill(process.pid, signal);
    } else {
      process.exit(code);
    }
  });
});

// terminate children.
process.on('SIGINT', () => {
  proc.kill('SIGINT'); // calls runner.abort()
  proc.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die.
});
