"use strict";

const log = require("npmlog");
const npa = require("npm-package-arg");
const pify = require("pify");
const { ValidationError } = require("@lerna/validation-error");

module.exports.getChangelogConfig = getChangelogConfig;

const cfgCache = new Map();

function isFunction(config) {
  return Object.prototype.toString.call(config) === "[object Function]";
}

function resolveConfigPromise(presetPackageName, presetConfig) {
  log.verbose("getChangelogConfig", "Attempting to resolve preset %j", presetPackageName);

  // eslint-disable-next-line global-require, import/no-dynamic-require
  let config = require(presetPackageName);

  log.info("getChangelogConfig", "Successfully resolved preset %j", presetPackageName);

  if (isFunction(config)) {
    try {
      // try assuming config builder function first
      config = config(presetConfig);
    } catch (_) {
      // legacy presets export an errback function instead of Q.all()
      config = pify(config)();
    }
  }

  return config;
}

/**
 * @param {import("..").ChangelogPresetConfig} [changelogPreset]
 * @param {string} [rootPath]
 */
function getChangelogConfig(changelogPreset = "conventional-changelog-angular", rootPath) {
  const presetName = typeof changelogPreset === "string" ? changelogPreset : changelogPreset.name;
  const presetConfig = typeof changelogPreset === "object" ? changelogPreset : {};

  const cacheKey = `${presetName}${presetConfig ? JSON.stringify(presetConfig) : ""}`;

  let config = cfgCache.get(cacheKey);

  if (!config) {
    let presetPackageName = presetName;

    // https://github.com/npm/npm-package-arg#result-object
    const parsed = npa(presetPackageName, rootPath);

    log.verbose("getChangelogConfig", "using preset %j", presetPackageName);
    log.silly("npa", parsed);

    if (parsed.type === "directory") {
      if (parsed.raw[0] === "@") {
        // npa parses scoped subpath reference as a directory
        parsed.name = parsed.raw;
        parsed.scope = parsed.raw.substring(0, parsed.raw.indexOf("/"));
        // un-scoped subpath shorthand handled in first catch block
      } else {
        presetPackageName = parsed.fetchSpec;
      }
    } else if (parsed.type === "git" && parsed.hosted && parsed.hosted.default === "shortcut") {
      // probably a shorthand subpath, e.g. "foo/bar"
      parsed.name = parsed.raw;
    }

    // Maybe it doesn't need an implicit 'conventional-changelog-' prefix?
    try {
      config = resolveConfigPromise(presetPackageName, presetConfig);

      cfgCache.set(cacheKey, config);

      // early exit, yay
      return Promise.resolve(config);
    } catch (err) {
      log.verbose("getChangelogConfig", err.message);
      log.info("getChangelogConfig", "Auto-prefixing conventional-changelog preset %j", presetName);

      // probably a deep shorthand subpath :P
      parsed.name = parsed.raw;
    }

    if (parsed.name.indexOf("conventional-changelog-") < 0) {
      // implicit 'conventional-changelog-' prefix
      const parts = parsed.name.split("/");
      const start = parsed.scope ? 1 : 0;

      //        foo =>        conventional-changelog-foo
      // @scope/foo => @scope/conventional-changelog-foo
      parts.splice(start, 1, `conventional-changelog-${parts[start]}`);

      // _technically_ supports 'foo/lib/bar.js', but that's gross
      presetPackageName = parts.join("/");
    }

    try {
      config = resolveConfigPromise(presetPackageName, presetConfig);

      cfgCache.set(cacheKey, config);
    } catch (err) {
      log.warn("getChangelogConfig", err.message);

      throw new ValidationError(
        "EPRESET",
        `Unable to load conventional-changelog preset '${presetName}'${
          presetName !== presetPackageName ? ` (${presetPackageName})` : ""
        }`
      );
    }
  }

  // the core presets are bloody Q.all() spreads
  return Promise.resolve(config);
}
