"use strict";

/**
 * Definition for Mocha's default ("run tests") command
 *
 * @module
 * @private
 */

const Mocha = require("../mocha.cjs");
const {
  createUnsupportedError,
  createInvalidArgumentValueError,
  createMissingArgumentError,
} = require("../errors.js");

const {
  list,
  handleRequires,
  validateLegacyPlugin,
  runMocha,
} = require("./run-helpers.cjs");
const { ONE_AND_DONES } = require("./one-and-dones.js");
const debug = require("debug")("mocha:cli:run");

exports.command = ["$0 [spec..]", "inspect"];

exports.describe = "Run tests with Mocha";

const camelCase = (name) =>
  name.replace(/-([a-z])/g, (_, char) => char.toUpperCase());

const coerceReporterOption = (opts) =>
  list(opts).reduce((acc, opt) => {
    const pair = opt.split("=");

    if (pair.length > 2 || !pair.length) {
      throw createInvalidArgumentValueError(
        `invalid reporter option '${opt}'`,
        "--reporter-option",
        opt,
        'expected "key=value" format',
      );
    }

    acc[pair[0]] = pair.length === 2 ? pair[1] : true;
    return acc;
  }, {});

const normalizeRunOptions = (argv) => {
  Object.keys(argv).forEach((name) => {
    const reporterOptionMatch = name.match(/^reporter-options?\.(.+)$/);
    if (reporterOptionMatch) {
      argv["reporter-option"] = Object.assign({}, argv["reporter-option"], {
        [reporterOptionMatch[1]]: argv[name],
      });
    }

    if (name.includes("-")) {
      const normalizedName = camelCase(name);
      if (!(normalizedName in argv)) {
        argv[normalizedName] = argv[name];
      }
    }
  });

  if (Array.isArray(argv["reporter-option"])) {
    argv["reporter-option"] = coerceReporterOption(argv["reporter-option"]);
  }
};

const validateRunOptions = (argv) => {
  // "one-and-dones"; help and version are handled by the top-level CLI.
  const oneAndDoneOptions = Object.keys(ONE_AND_DONES).filter((opt) =>
    Boolean(argv[opt]),
  );
  if (oneAndDoneOptions.length > 1) {
    throw createUnsupportedError(
      `Arguments ${oneAndDoneOptions.join(" and ")} are mutually exclusive`,
    );
  }

  Object.keys(ONE_AND_DONES).forEach((opt) => {
    if (argv[opt]) {
      ONE_AND_DONES[opt].call(null);
      process.exit(0);
    }
  });

  if (argv.invert && !("fgrep" in argv || "grep" in argv)) {
    throw createMissingArgumentError(
      '"--invert" requires one of "--fgrep <str>" or "--grep <regexp>"',
      "--fgrep|--grep",
      "string|regexp",
    );
  }

  if (argv.fgrep && argv.grep) {
    throw createUnsupportedError(
      "Arguments fgrep and grep are mutually exclusive",
    );
  }

  if (argv.parallel) {
    if (argv.file) {
      throw createUnsupportedError(
        "--parallel runs test files in a non-deterministic order, and is mutually exclusive with --file",
      );
    }

    if (argv.sort) {
      throw createUnsupportedError(
        "--parallel runs test files in a non-deterministic order, and is mutually exclusive with --sort",
      );
    }

    if (["progress", "markdown", "json-stream"].includes(argv.reporter)) {
      throw createUnsupportedError(
        `--reporter=${argv.reporter} is mutually exclusive with --parallel`,
      );
    }
  }
};

const prepareRunOptions = async (argv) => {
  const plugins = await handleRequires(argv.require);
  validateLegacyPlugin(argv, "reporter", Mocha.reporters);
  validateLegacyPlugin(argv, "ui", Mocha.interfaces);
  Object.assign(argv, plugins);
  return argv;
};

exports.validateRunOptions = validateRunOptions;
exports.prepareRunOptions = prepareRunOptions;
exports.normalizeRunOptions = normalizeRunOptions;

exports.handler = async function (argv) {
  normalizeRunOptions(argv);
  debug("running with config", argv);
  const mocha = new Mocha(argv);

  try {
    await runMocha(mocha, argv);
  } catch (err) {
    console.error("\n Exception during run:", err);
    process.exit(1);
  }
};
