/**
 * Copyright (c) 2017-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');
const chalk = require('chalk');
const fs = require('fs-extra');
const globby = require('globby');
const load = require('../load');
const createSitemap = require('../core/sitemap');
const createServerConfig = require('../webpack/server');
const createClientConfig = require('../webpack/client');
const {applyConfigureWebpack} = require('../webpack/utils');

function compile(config) {
  return new Promise((resolve, reject) => {
    webpack(config, (err, stats) => {
      if (err) {
        reject(err);
      }
      if (stats.hasErrors()) {
        stats.toJson().errors.forEach(e => {
          console.error(e);
        });
        reject(new Error('Failed to compile with errors.'));
      }
      if (stats.hasWarnings()) {
        stats.toJson().warnings.forEach(warning => {
          console.warn(warning);
        });
      }
      resolve(stats.toJson({modules: false}));
    });
  });
}

module.exports = async function build(siteDir) {
  process.env.NODE_ENV = 'production';
  console.log('Build command invoked ...');

  const props = await load(siteDir);

  // Apply user webpack config.
  const {
    outDir,
    siteConfig: {configureWebpack},
  } = props;

  const clientConfigObj = createClientConfig(props);
  // Remove/clean build folders before building bundles.
  clientConfigObj
    .plugin('clean')
    .use(CleanWebpackPlugin, [outDir, {verbose: false, allowExternal: true}]);
  const serverConfigObj = createServerConfig(props);
  const clientConfig = applyConfigureWebpack(
    configureWebpack,
    clientConfigObj.toConfig(),
    false,
  );
  const serverConfig = applyConfigureWebpack(
    configureWebpack,
    serverConfigObj.toConfig(),
    true,
  );

  // Build the client bundles first.
  // We cannot run them in parallel because the server needs to know
  // the correct client bundle name.
  await compile(clientConfig);

  // Build the server bundles (render the static HTML and pick client bundle),
  await compile(serverConfig);

  // Copy static files.
  const staticDir = path.resolve(siteDir, 'static');
  const staticFiles = await globby(['**'], {
    cwd: staticDir,
  });
  await Promise.all(
    staticFiles.map(async source => {
      const fromPath = path.resolve(staticDir, source);
      const toPath = path.resolve(outDir, source);
      return fs.copy(fromPath, toPath);
    }),
  );

  // Generate sitemap.
  const sitemap = await createSitemap(props);
  const sitemapPath = path.join(outDir, 'sitemap.xml');
  await fs.writeFile(sitemapPath, sitemap);

  const relativeDir = path.relative(process.cwd(), outDir);
  console.log(
    `\n${chalk.green('Success!')} Generated static files in ${chalk.cyan(
      relativeDir,
    )}.\n`,
  );
};