/**
 * Get clean project name from the given string falling back to the entry `"name"`
 * in the `package.json` stripping out organization name, if any
 *
 * @param {string} [input]
 * @returns {string}
 */
function getProjectName(input) {
  const { pkg } = require("../config");
  input = input || pkg.name;
  return input.replace(/^@.+\//, "");
}

/**
 * Get project slug normalizing the @org syntax and slashes
 *
 * @param {string} [input]
 * @returns {string}
 */
function getProjectSlug(input) {
  const { pkg } = require("../config");
  input = input || pkg.name;
  return input.replace(/@/g, "").replace(/\//g, "-");
}

/**
 * Transform a kebab case project name, as defined in the `package.json` entry
 * `"name"` (e.g. `@acanto/my-project`) to a nice title: `My Project`
 *
 * @param {string} [input]
 * @returns {string}
 */
function getProjectTitle(input) {
  const { sentenceCase } = require("change-case");
  const name = getProjectName(input);
  return sentenceCase(name);
}

/**
 * Returns the project's base URL (environment sensible)
 *
 * @param {boolean} [bypassWds]
 * @returns {string}
 */
function getBaseUrl(bypassWds) {
  const { getServerHost, getServerPort } = require("../webpack/helpers");
  const { isProduction, isHttps } = require("../config");
  if (!bypassWds && !isProduction) {
    const protocol = isHttps ? "https://" : "http://";
    return normaliseUrl(`${protocol}${getServerHost()}:${getServerPort()}/`);
  }
  return normaliseUrl(`${process.env.APP_URL}/`);
}

/**
 * Get public URLs, env dependent and ensure initial/ending slashes
 */
function getPublicUrls() {
  const { frontend } = require("../paths");
  const { relativeUrls } = frontend.dest;
  const { env } = process;

  return {
    root: env.APP_URL ? normaliseUrl(getBaseUrl()) : "undefined",
    api: env.CMS_API_URL ? normaliseUrl(env.CMS_API_URL) : "undefined",
    base: normaliseUrl(`${getBaseUrl()}/${relativeUrls.base}`),
    assets: normaliseUrl(`${getBaseUrl()}/${relativeUrls.assets}`),
    entries: normaliseUrl(`${getBaseUrl()}/${relativeUrls.entries}`),
    images: normaliseUrl(`${getBaseUrl()}/${relativeUrls.images}`),
    fonts: normaliseUrl(`${getBaseUrl()}/${relativeUrls.fonts}`),
    favicons: normaliseUrl(`${getBaseUrl(true)}/${relativeUrls.favicons}`),
  };
}

/**
 * Get options deep merging the defaults with the `package.json`'s data, usually
 * set on the `config` key.
 *
 * @returns {Object}
 */
function getOptions(key, defaults) {
  const merge = require("deepmerge");
  const _get = require("lodash.get");
  const { pkg } = require("../config");
  const custom = _get(pkg, key);

  return merge(defaults, custom);
}

/**
 * Get now time information text
 *
 * @returns {string}
 */
function getNow() {
  const pad = (n) => (n < 10 ? "0" + n : n);
  const date = new Date();
  const day = date.getDate();
  const month = date.getMonth();
  const year = date.getFullYear();
  const hour = date.getHours();
  const minutes = date.getMinutes();
  const seconds = date.getSeconds();
  const ddmmyyyy = pad(day) + "/" + pad(month + 1) + "/" + year;
  const hhmmss = pad(hour) + ":" + pad(minutes) + ":" + pad(seconds);

  return ddmmyyyy + " " + hhmmss;
}

/**
 * Get header autogeneration banner
 *
 * @returns {string}
 */
function getHeaderAutogeneration() {
  return `DO NOT EDIT, THIS FILE IS AUTO-GENERATED. LAST UPDATE ${getNow()}`;
}

/**
 * CSS rules to visually hide a piece of HTML
 *
 * @see https://css-tricks.com/places-its-tempting-to-use-display-none-but-dont/
 * @returns {string}
 */
function getHiddenCss() {
  return [
    "position: absolute; ",
    "overflow: hidden;",
    "clip: rect(0 0 0 0);",
    "height: 1px; width: 1px;",
    "margin: -1px; padding: 0; border: 0;",
  ].join("");
}

/**
 * Get watch path safe for Windows OS
 *
 * @see https://github.com/paulmillr/chokidar/issues/777
 * @param {string} fullPath
 * @returns {string}
 */
function getWatchPath(fullPath) {
  return fullPath.replace(/\\/g, "/");
}

/**
 * Normalize URL
 *
 * @param {string} url
 * @returns {string}
 */
function normaliseUrl(url) {
  // remove double slashes but not after protocol, e.g. keep `http://`
  return url.replace(/(?<!:)\/{2,}/g, "/");
}

/**
 * Remove trailing slashes
 *
 * @param {string} url
 * @returns {string}
 */
function removeTrailingSlashes(url) {
  return url.replace(/\/+$/g, "");
}

/**
 * Extract domain from url
 *
 * @param {string} url
 * @returns {string}
 */
function extractDomainFromUrl(url) {
  return removeTrailingSlashes(
    url.replace("https://", "").replace("http://", "")
  );
}

/**
 * Run task if env is development and if the given file does not already exists
 *
 * @param {string} filePath
 * @param {Function} callback
 * @param {Function} task
 * @returns {?Function}
 */
function taskIfDevAndMissingFile(filePath, callback, task) {
  const fs = require("fs");
  const { isProduction } = require("../config");

  // don't autogenerate it on production
  if (isProduction) {
    callback();
  }

  let alreadyExists = false;

  try {
    if (fs.existsSync(filePath)) {
      alreadyExists = true;
    }
  } catch (err) {}

  if (alreadyExists) {
    callback();
  } else {
    return task();
  }
}

/**
 * Get JS globally avialable variables
 *
 */
function getProjectJsGlobals() {
  const publicUrls = getPublicUrls();

  return [
    {
      name: "__DEV__",
      desc:
        "Flag for development environment. Code put inside a condition based on this variable will be stripped out on production.",
      type: "boolean",
      value: process.env.NODE_ENV !== "production",
    },
    {
      name: "__ENV__",
      desc: "Current environment",
      type: "'development' | 'production'",
      value: `"${process.env.APP_ENV}"`,
    },
    {
      name: "__URL__",
      desc:
        "The website base URL, in other words its domain (environment sensible).",
      type: "string",
      value: `"${publicUrls.root}"`,
    },
    {
      name: "__API__",
      desc: "The website api URL (environment sensible).",
      type: "string",
      value: `"${publicUrls.api}"`,
    },
    {
      name: "__ASSETS__",
      desc: "The website static assets URL (environment sensible).",
      type: "string",
      value: `"${publicUrls.assets}"`,
    },
    {
      name: "__IMAGES__",
      desc: "The website static images URL (environment sensible).",
      type: "string",
      value: `"${publicUrls.images}"`,
    },
    {
      name: "__FONTS__",
      desc: "The website static fonts URL (environment sensible).",
      type: "string",
      value: `"${publicUrls.fonts}"`,
    },
  ];
}

/**
 * Is using a locally linked npm module
 *
 * When developing node_modules is useful to have this information, whether
 * you are using the version from the npm registry or from your local machine
 * linked with `npm link`
 *
 * @param {string} path The npm module to check
 * @returns {boolean}
 */
function isUsingLocalLinkedNodeModule(path) {
  const fs = require("fs");
  const stat = fs.lstatSync(path);
  // const dir = fs.readdirSync(path, { withFileTypes: true })[0];
  return stat.isSymbolicLink();
}

module.exports = {
  getProjectName,
  getProjectSlug,
  getProjectTitle,
  getBaseUrl,
  getPublicUrls,
  getOptions,
  getNow,
  getHeaderAutogeneration,
  getHiddenCss,
  getWatchPath,
  normaliseUrl,
  removeTrailingSlashes,
  extractDomainFromUrl,
  taskIfDevAndMissingFile,
  getProjectJsGlobals,
  isUsingLocalLinkedNodeModule,
};
