'use strict';

/**
 * Responsible for loading / finding Mocha's "rc" files.
 * This doesn't have anything to do with `mocha.opts`.
 *
 * @private
 * @module
 */

const fs = require('fs');
const path = require('path');
const debug = require('debug')('mocha:cli:config');
const findUp = require('find-up');

/**
 * These are the valid config files, in order of precedence;
 * e.g., if `.mocharc.js` is present, then `.mocharc.yaml` and the rest
 * will be ignored.
 * The user should still be able to explicitly specify a file.
 * @private
 */
exports.CONFIG_FILES = [
  '.mocharc.js',
  '.mocharc.yaml',
  '.mocharc.yml',
  '.mocharc.jsonc',
  '.mocharc.json'
];

const isModuleNotFoundError = err =>
  err.code !== 'MODULE_NOT_FOUND' ||
  err.message.indexOf('Cannot find module') !== -1;

/**
 * Parsers for various config filetypes. Each accepts a filepath and
 * returns an object (but could throw)
 */
const parsers = (exports.parsers = {
  yaml: filepath =>
    require('js-yaml').safeLoad(fs.readFileSync(filepath, 'utf8')),
  js: filepath => {
    const cwdFilepath = path.resolve(filepath);
    try {
      debug(`parsers: load using cwd-relative path: "${cwdFilepath}"`);
      return require(cwdFilepath);
    } catch (err) {
      if (isModuleNotFoundError(err)) {
        debug(`parsers: retry load as module-relative path: "${filepath}"`);
        return require(filepath);
      } else {
        throw err; // rethrow
      }
    }
  },
  json: filepath =>
    JSON.parse(
      require('strip-json-comments')(fs.readFileSync(filepath, 'utf8'))
    )
});

/**
 * Loads and parses, based on file extension, a config file.
 * "JSON" files may have comments.
 *
 * @private
 * @param {string} filepath - Config file path to load
 * @returns {Object} Parsed config object
 */
exports.loadConfig = filepath => {
  let config = {};
  debug(`loadConfig: "${filepath}"`);

  const ext = path.extname(filepath);
  try {
    if (ext === '.yml' || ext === '.yaml') {
      config = parsers.yaml(filepath);
    } else if (ext === '.js') {
      config = parsers.js(filepath);
    } else {
      config = parsers.json(filepath);
    }
  } catch (err) {
    throw new Error(`failed to parse config "${filepath}": ${err}`);
  }
  return config;
};

/**
 * Find ("find up") config file starting at `cwd`
 *
 * @param {string} [cwd] - Current working directory
 * @returns {string|null} Filepath to config, if found
 */
exports.findConfig = (cwd = process.cwd()) => {
  const filepath = findUp.sync(exports.CONFIG_FILES, {cwd});
  if (filepath) {
    debug(`findConfig: found "${filepath}"`);
  }
  return filepath;
};
