text: 'use strict';
  
  const path = require('path');
  const _ = require('lodash');
  const fs = require('fs-extra');
  const webpack = require('webpack');
  const WebpackDevServer = require('webpack-dev-server');
  const chalk = require('chalk');
  const chokidar = require('chokidar');
  const getWebpackConfig = require('./webpack.config');
  
  const getPkgPath = name => path.dirname(require.resolve(`${name}/package.json`));
  
  const DEFAULT_PLUGINS = [
    'content-type-builder',
    'content-manager',
    'upload',
    'email',
    'i18n',
    'users-permissions',
  ];
  
  function getCustomWebpackConfig(dir, config) {
    const adminConfigPath = path.join(dir, 'src', 'admin', 'webpack.config.js');
  
    let webpackConfig = getWebpackConfig(config);
  
    if (fs.existsSync(adminConfigPath)) {
      const webpackAdminConfig = require(path.resolve(adminConfigPath));
  
      if (_.isFunction(webpackAdminConfig)) {
        // Expose the devServer configuration
        if (config.devServer) {
          webpackConfig.devServer = config.devServer;
        }
  
        webpackConfig = webpackAdminConfig(webpackConfig, webpack);
  
        if (!webpackConfig) {
          console.error(
            `${chalk.red('Error:')} Nothing was returned from your custom webpack configuration`
          );
          process.exit(1);
        }
      }
    }
  
    return webpackConfig;
  }
  
  async function build({ plugins, dir, env, options, optimize, forceBuild }) {
    const buildAdmin = await shouldBuildAdmin({ dir, plugins });
  
    if (!buildAdmin && !forceBuild) {
      return;
    }
  
    // Create the cache dir containing the front-end files.
    await createCacheDir({ dir, plugins });
  
    const cacheDir = path.resolve(dir, '.cache');
    const entry = path.resolve(cacheDir, 'admin', 'src');
    const dest = path.resolve(dir, 'build');
  
    // Roots for the @strapi/babel-plugin-switch-ee-ce
    const roots = {
      eeRoot: path.resolve(cacheDir, 'ee', 'admin'),
      ceRoot: path.resolve(cacheDir, 'admin', 'src'),
    };
  
    const pluginsPath = Object.keys(plugins).map(pluginName => plugins[pluginName].pathToPlugin);
  
    const config = getCustomWebpackConfig(dir, {
      entry,
      pluginsPath,
      cacheDir,
      dest,
      env,
      options,
      optimize,
      roots,
    });
  
    const compiler = webpack(config);
  
    return new Promise((resolve, reject) => {
      compiler.run((err, stats) => {
        if (err) {
          console.error(err.stack || err);
  
          if (err.details) {
            console.error(err.details);
          }
          return reject(err);
        }
  
        const info = stats.toJson();
  
        if (stats.hasErrors()) {
          console.error(info.errors);
        }
  
        return resolve({
          stats,
  
          warnings: info.warnings,
        });
      });
    });
  }
  
  async function createPluginsJs(plugins, dest) {
    const pluginsArray = plugins.map(({ pathToPlugin, name }) => {
      const shortName = _.camelCase(name);
  
      /**
       * path.join, on windows, it uses backslashes to resolve path.
       * The problem is that Webpack does not windows paths
       * With this tool, we need to rely on "/" and not "\".
       * This is the reason why '..\\..\\..\\node_modules\\@strapi\\plugin-content-type-builder/strapi-admin.js' was not working.
       * The regexp at line 105 aims to replace the windows backslashes by standard slash so that webpack can deal with them.
       * Backslash looks to work only for absolute paths with webpack => https://webpack.js.org/concepts/module-resolution/#absolute-paths
       */
      const realPath = path
        .join(path.relative(path.resolve(dest, 'admin', 'src'), pathToPlugin), 'strapi-admin.js')
        .replace(/\\/g, '/');
  
      return {
        name,
        pathToPlugin: realPath,
        shortName,
      };
    });
  
    const content = `
  ${pluginsArray
    .map(({ pathToPlugin, shortName }) => {
      const req = `'${pathToPlugin}'`;
  
      return `import ${shortName} from ${req};`;
    })
    .join('\n')}
  
  
  const plugins = {
  ${[...pluginsArray]
    .map(({ name, shortName }) => {
      return `  '${name}': ${shortName},`;
    })
    .join('\n')}
  };
  
  export default plugins;
  `;
  
    return fs.writeFile(path.resolve(dest, 'admin', 'src', 'plugins.js'), content);
  }
  
  async function clean({ dir }) {
    const buildDir = path.join(dir, 'build');
    const cacheDir = path.join(dir, '.cache');
  
    fs.removeSync(buildDir);
    fs.removeSync(cacheDir);
  }
  
  async function copyAdmin(dest) {
    const adminPath = getPkgPath('@strapi/admin');
  
    // TODO copy ee folders for plugins
    await fs.copy(path.resolve(adminPath, 'ee', 'admin'), path.resolve(dest, 'ee', 'admin'));
  
    await fs.ensureDir(path.resolve(dest, 'config'));
    await fs.copy(path.resolve(adminPath, 'admin'), path.resolve(dest, 'admin'));
  
    // Copy package.json
    await fs.copy(path.resolve(adminPath, 'package.json'), path.resolve(dest, 'package.json'));
  }
  
  async function createCacheDir({ dir, plugins }) {
    const cacheDir = path.resolve(dir, '.cache');
  
    const pluginsWithFront = Object.keys(plugins)
      .filter(pluginName => {
        const pluginInfo = plugins[pluginName];
        return fs.existsSync(path.resolve(pluginInfo.pathToPlugin, 'strapi-admin.js'));
      })
      .map(name => ({ name, ...plugins[name] }));
  
    // create .cache dir
    await fs.emptyDir(cacheDir);
  
    // copy admin core code
    await copyAdmin(cacheDir);
  
    // Copy app.js
    const customAdminConfigFilePath = path.join(dir, 'src', 'admin', 'app.js');
  
    if (fs.existsSync(customAdminConfigFilePath)) {
      await fs.copy(customAdminConfigFilePath, path.resolve(cacheDir, 'admin', 'src', 'app.js'));
    }
  
    // Copy admin extensions folder
    const adminExtensionFolder = path.join(dir, 'src', 'admin', 'extensions');
  
    if (fs.existsSync(adminExtensionFolder)) {
      await fs.copy(adminExtensionFolder, path.resolve(cacheDir, 'admin', 'src', 'extensions'));
    }
  
    // create plugins.js with plugins requires
    await createPluginsJs(pluginsWithFront, cacheDir);
  }
  
  async function watchAdmin({ plugins, dir, host, port, browser, options }) {
    // Create the cache dir containing the front-end files.
    const cacheDir = path.join(dir, '.cache');
    await createCacheDir({ dir, plugins });
  
    const entry = path.join(cacheDir, 'admin', 'src');
    const dest = path.join(dir, 'build');
    const env = 'development';
  
    // Roots for the @strapi/babel-plugin-switch-ee-ce
    const roots = {
      eeRoot: path.resolve(cacheDir, 'ee', 'admin'),
      ceRoot: path.resolve(cacheDir, 'admin', 'src'),
    };
  
    const pluginsPath = Object.keys(plugins).map(pluginName => plugins[pluginName].pathToPlugin);
  
    const args = {
      entry,
      cacheDir,
      pluginsPath,
      dest,
      env,
      port,
      options,
      roots,
      devServer: {
        port,
        client: {
          logging: 'none',
          overlay: {
            errors: true,
            warnings: false,
          },
        },
  
        open: browser === 'true' ? true : browser,
        devMiddleware: {
          publicPath: options.adminPath,
        },
        historyApiFallback: {
          index: options.adminPath,
          disableDotRule: true,
        },
      },
    };
  
    const webpackConfig = getCustomWebpackConfig(dir, args);
  
    const compiler = webpack(webpackConfig);
  
    const devServerArgs = {
      ...args.devServer,
      ...webpackConfig.devServer,
    };
  
    const server = new WebpackDevServer(devServerArgs, compiler);
  
    const runServer = async () => {
      console.log(chalk.green('Starting the development server...'));
      console.log();
      console.log(chalk.green(`Admin development at http://${host}:${port}${options.adminPath}`));
  
      await server.start();
    };
  
    runServer();
  
    watchFiles(dir);
  }
  
  /**
   * Listen to files change and copy the changed files in the .cache/admin folder
   * when using the dev mode
   * @param {string} dir
   */
  async function watchFiles(dir) {
    const cacheDir = path.join(dir, '.cache');
    const appExtensionFile = path.join(dir, 'src', 'admin', 'app.js');
    const extensionsPath = path.join(dir, 'src', 'admin', 'extensions');
  
    // Only watch the admin/app.js file and the files that are in the ./admin/extensions/folder
    const filesToWatch = [appExtensionFile, extensionsPath];
  
    const watcher = chokidar.watch(filesToWatch, {
      ignoreInitial: true,
      ignorePermissionErrors: true,
    });
  
    watcher.on('all', async (event, filePath) => {
      const isAppFile = filePath.includes(appExtensionFile);
  
      // The app.js file needs to be copied in the .cache/admin/src/app.js and the other ones needs to
      // be copied in the .cache/admin/src/extensions folder
      const targetPath = isAppFile
        ? path.join(path.normalize(filePath.split(appExtensionFile)[1]), 'app.js')
        : path.join('extensions', path.normalize(filePath.split(extensionsPath)[1]));
  
      const destFolder = path.join(cacheDir, 'admin', 'src');
  
      if (event === 'unlink' || event === 'unlinkDir') {
        // Remove the file or folder
        // We need to copy the original files when deleting an override one
        try {
          fs.removeSync(path.join(destFolder, targetPath));
        } catch (err) {
          console.log('An error occured while deleting the file', err);
        }
      } else {
        // In any other case just copy the file into the .cache/admin/src folder
        try {
          await fs.copy(filePath, path.join(destFolder, targetPath));
        } catch (err) {
          console.log(err);
        }
      }
    });
  }
  
  const hasCustomAdminCode = async dir => {
    const customAdminPath = path.join(dir, 'src', 'admin');
    const customAdminConfigFile = path.join(customAdminPath, 'app.js');
    const customAdminWebpackFile = path.join(customAdminPath, 'webpack.config.js');
  
    const hasCustomConfigFile = await fs.pathExists(customAdminConfigFile);
    const hasCustomWebpackFile = await fs.pathExists(customAdminWebpackFile);
  
    return hasCustomConfigFile || hasCustomWebpackFile;
  };
  
  /**
   * Checks if the project's installed plugins are not the same as a default one.
   * @param {Object} plugins
   * @returns {boolean}
   */
  const hasNonDefaultPlugins = plugins => {
    // List of plugins that are not the ones installed in a generated app
    const installedPlugins = Object.keys(plugins).filter(x => !DEFAULT_PLUGINS.includes(x));
  
    // List of default plugins uninstalled from a generated app
    const missingPlugins = DEFAULT_PLUGINS.filter(x => !Object.keys(plugins).includes(x));
  
    const diff = [...installedPlugins, ...missingPlugins];
  
    return diff.length > 0;
  };
  
  async function shouldBuildAdmin({ dir, plugins }) {
    const appHasCustomAdminCode = await hasCustomAdminCode(dir);
    const appHasNonDefaultPlugins = hasNonDefaultPlugins(plugins);
  
    return appHasCustomAdminCode || appHasNonDefaultPlugins;
  }
  
  module.exports = {
    clean,
    build,
    watchAdmin,
  };
  