// @ts-check

/**
 * The CMS data returned for the routes API
 *
 * @typedef {Object} CmsStructureResponse
 * @property {RoutesAsyncI18n} i18n
 * @property {RouteAsyncData[]} routes
 */

/**
 * The routes meta data retrieved asynchronously.
 *
 * @typedef {Object} RoutesAsyncI18n
 * @property {string[]} locales Locales currently active in the project
 * @property {string} default_locale Defalt locale for the project
 */

/**
 * Just the route data portion retrieved asynchronously.
 *
 * @typedef {Object} RouteAsyncData
 * @property {string} id Route/Page ID, saame as its `name`
 * @property {Object} slug Route/Page slug map by locale, the key is the locale the value is the localised slug
 */

/**
 * Just the route data portion determined synchronously.
 *
 * @typedef {Object} RouteSyncData
 */

/**
 * All route data.
 * Obtained at two levels, synchronously by using folder names, parsing the
 * template content and using strict conventions and asynchronously by fetching
 * data from the CMS backend API.
 *
 * @typedef {RouteSyncData & RouteAsyncData & { i18n: RoutesAsyncI18n}} RouteData
 */

/**
 * Get route file name
 *
 * @param {string} routeName
 * @returns {string}
 */
function getRouteFileName(routeName) {
  const { pascalCase } = require("change-case");
  return `Route${pascalCase(routeName)}`;
}

/**
 * Get remote route names as a flat array of strings, route IDs correspond to
 * names.
 *
 * @param {RouteAsyncData[]} routes
 * @returns {string[]}
 */
function getRoutesNamesFromRemote(routes) {
  const output = [];

  for (let i = 0; i < routes.length; i++) {
    const route = routes[i];
    output.push(route.id);
  }

  return output;
}

/**
 * Get locally defined routes IDs based on the source code folder structure,
 * each route name corresponds to its folder name
 *
 * @returns {string[]}
 */
function getRoutesNamesFromSource() {
  const fs = require("fs");
  const paths = require("../paths");
  const routesSrc = paths.frontend.src.routes;

  // at the very begininng routes folder might not even be there
  if (!fs.existsSync(routesSrc)) {
    return [];
  }

  return fs
    .readdirSync(routesSrc, { withFileTypes: true })
    .filter((folder) => folder.isDirectory())
    .map((folder) => folder.name);
}

/**
 * Check routes consistency and reports a summary in the terminal. The check is
 * performed among two levels:
 *
 * 1) routes determined by the source code folder structure
 * 2) routes defined in the CMS
 *
 * @param {CmsStructureResponse} asyncData
 */
function checkRoutesConsistency(asyncData) {
  const log = require("@acanto/workflow-utils/log");
  const chalk = require("@acanto/workflow-utils/chalk");
  const source = getRoutesNamesFromSource();
  const remote = getRoutesNamesFromRemote(asyncData.routes);
  const missingRemotes = source.filter((route) => remote.indexOf(route) === -1);
  const missingSources = remote.filter((route) => source.indexOf(route) === -1);

  if (missingRemotes.length) {
    log(
      chalk.ansi256(220)(
        `The following routes are defined locally but ${chalk.bold(
          "missing in the CMS"
        )}:`
      )
    );

    for (let i = 0; i < missingRemotes.length; i++) {
      log(chalk.ansi256(222)("• " + missingRemotes[i]));
    }

    log(
      chalk.ansi256(220)("You might want to add them through the CMS pages.")
    );
  }

  if (missingSources.length) {
    log(
      chalk.yellowBright(
        `The following routes defined in the CMS were ${chalk.bold(
          "missing locally"
        )}:`
      )
    );

    for (let i = 0; i < missingSources.length; i++) {
      log(chalk.yellow("• " + missingSources[i]));
      generateRouteSource(missingSources[i]);
    }

    log(
      chalk.yellowBright(
        "Now they have been autogenerated. It is better to immediately add and push them on Git."
      )
    );
  }

  if (!missingRemotes.length && !missingSources.length) {
    log(
      chalk.green(
        chalk.bold("Routes are consistent") +
          " between the CMS and the local repository folder structure."
      )
    );
  }
}

/**
 * Generate route source files and folder
 *
 * @param {string} name
 */
function generateRouteSource(name) {
  const paths = require("../paths");
  const { generate } = require("./generator");
  const { enrichName } = require("./enricher");
  const { getCliOptionsMap } = require("./cli");
  const data = {
    route: enrichName(name),
  };
  const { barba } = getCliOptionsMap();

  generate({
    name,
    data,
    srcFolder: "route" + (barba ? "-barba" : ""),
    destFolder: paths.frontend.src.routes,
  });
}

/**
 * Merge routes coming from the CMS with system routes, like 404 and 500 error
 * pages
 *
 * @param {CmsStructureResponse} data
 * @return {CmsStructureResponse}
 */
function mergeWithSystemRoutes({ routes, ...data }) {
  const systemRoutes = {
    404: {
      id: "404",
      slug: {},
    },
    500: {
      id: "500",
      slug: {},
    },
  };

  for (let i = 0; i < routes.length; i++) {
    const route = routes[i];
    if (systemRoutes[route.id]) {
      delete systemRoutes[route.id];
    }
  }

  for (const key in systemRoutes) {
    const route = systemRoutes[key];
    for (let k = 0; k < data.i18n.locales.length; k++) {
      const locale = data.i18n.locales[k];
      route.slug[locale] = `/${locale}/${route.id}`;
    }
    routes.unshift(route);
  }

  return {
    ...data,
    routes,
  };
}

/**
 * Get routes async data from CMS exposed public API
 *
 * @returns {Promise<CmsStructureResponse, string>}
 */
function getCmsRoutes(options) {
  const { getApiData } = require("./cms");

  return new Promise((resolve, reject) => {
    const request = getApiData("/structure", {
      useCache: true,
      subject: "structure",
    });

    request.then(
      (response) => {
        resolve(mergeWithSystemRoutes(response.data));
      },
      (error) => {
        reject(error);
      }
    );
  });
}

/**
 * Check wether a rote is using 
 * 
 * It searches for a `$useBarba` property on the route's controller php class.
 * We simply do a regex, we might be more sophisticated and parse the actual
 * php code but it does not seem to be needed.
 *
 * @param {string} routeName
 * @returns {boolean}
 */
function isRouteUsingBarba(routeName) {
  const fs = require("fs");
  const { join, frontend } = require("../paths");
  const routeController = join(frontend.src.routes, routeName, "index.php");
  const regex = /\$useBarba.*=.*true/gm;
  
  if (fs.existsSync(routeController)) {

    const cnt = fs.readFileSync(routeController, { encoding: "utf8" });
    
    return regex.test(cnt);
  }
  return false;
}

/**
 * Get all routes that use barba
 *
 */
function getBarbaRoutes() {
  const allRoutes = getRoutesNamesFromSource();
  const filtered = allRoutes.filter(isRouteUsingBarba);
  return filtered;
}

module.exports = {
  getCmsRoutes,
  getBarbaRoutes,
  checkRoutesConsistency,
  generateRouteSource,
  getRouteFileName,
};
