const { join } = require('path');
const webpack = require('../compiled/webpack');
const { DEFAULT_BROWSER_TARGETS, DEFAULT_DEVTOOL, DEFAULT_OUTPUT_PATH } = require('../constants');
const Config = require('../compiled/webpack-5-chain');
// eslint-disable-next-line import/extensions
const { Env } = require('../types');
// eslint-disable-next-line import/extensions
const { RuntimePublicPathPlugin } = require('../plugins/RuntimePublicPathPlugin');
const { getBrowsersList } = require('../utils/browsersList');
const addAssetRules = require('./assetRules');
const addBundleAnalyzerPlugin = require('./bundleAnalyzerPlugin');
const addCompressPlugin = require('./compressPlugin');
const addCopyPlugin = require('./copyPlugin');
const addCSSRules = require('./cssRules');
const addDefinePlugin = require('./definePlugin');
const addDetectDeadCodePlugin = require('./detectDeadCodePlugin');
const addFastRefreshPlugin = require('./fastRefreshPlugin');
const addForkTSCheckerPlugin = require('./forkTSCheckerPlugin');
const addHarmonyLinkingErrorPlugin = require('./harmonyLinkingErrorPlugin');
const addIgnorePlugin = require('./ignorePlugin');
const addJavaScriptRules = require('./javaScriptRules');
const addManifestPlugin = require('./manifestPlugin');
const addMiniCSSExtractPlugin = require('./miniCSSExtractPlugin');
const addNodePolyfill = require('./nodePolyfill');
const addNodePrefixPlugin = require('./nodePrefixPlugin');
const addProgressPlugin = require('./progressPlugin');
const addSpeedMeasureWebpackPlugin = require('./speedMeasureWebpackPlugin');
const addSVGRules = require('./svgRules');
async function getConfig(opts) {
  const { userConfig } = opts;
  const isDev = opts.env === Env.development;
  const config = new Config();
  userConfig.targets ||= DEFAULT_BROWSER_TARGETS;
  const useHash = !!(opts.hash || (userConfig.hash && !isDev));
  const applyOpts = {
    name: opts.name,
    config,
    userConfig,
    cwd: opts.cwd,
    env: opts.env,
    babelPreset: opts.babelPreset,
    extraBabelPlugins: opts.extraBabelPlugins || [],
    extraBabelPresets: opts.extraBabelPresets || [],
    extraEsbuildLoaderHandler: opts.extraEsbuildLoaderHandler || [],
    browsers: getBrowsersList({
      targets: userConfig.targets,
    }),
    useHash,
    staticPathPrefix: opts.staticPathPrefix !== undefined ? opts.staticPathPrefix : 'static/',
  };

  // mode
  config.mode(opts.env);
  config.stats('none');

  // entry
  Object.keys(opts.entry).forEach(key => {
    const entry = config.entry(key);
    if (isDev && opts.hmr) {
      entry.add(require.resolve('../../client/client/client'));
    }
    entry.add(opts.entry[key]);
  });

  // devtool
  config.devtool(
    isDev ? (userConfig.devtool === false ? false : userConfig.devtool || DEFAULT_DEVTOOL) : userConfig.devtool,
  );

  // output
  const absOutputPath = join(opts.cwd, userConfig.outputPath || DEFAULT_OUTPUT_PATH);
  const disableCompress = process.env.COMPRESS === 'none';
  config.output
    .path(absOutputPath)
    .filename(useHash ? `[name].[contenthash:8].js` : `[name].js`)
    .chunkFilename(useHash ? `[name].[contenthash:8].async.js` : `[name].js`)
    .publicPath(userConfig.publicPath || 'auto')
    .pathinfo(isDev || disableCompress);

  // resolve
  // prettier-ignore
  config.resolve
    .set('symlinks', true)
    .modules
    .add('node_modules')
    .end()
    .alias
    .merge(userConfig.alias || {})
    .end()
    .extensions
    .merge([
      '.wasm',
      '.mjs',
      '.js',
      '.jsx',
      '.ts',
      '.tsx',
      '.json'
    ])
    .end();

  // externals
  config.externals(userConfig.externals || []);

  // target
  config.target(['web', 'es5']);

  // experiments
  config.experiments({
    topLevelAwait: true,
    outputModule: !!userConfig.esm,
  });

  // node polyfill
  await addNodePolyfill(applyOpts);

  // rules
  await addJavaScriptRules(applyOpts);
  await addCSSRules(applyOpts);
  await addAssetRules(applyOpts);
  await addSVGRules(applyOpts);

  // plugins
  // mini-css-extract-plugin
  await addMiniCSSExtractPlugin(applyOpts);
  // ignoreMomentLocale
  await addIgnorePlugin(applyOpts);
  // define
  await addDefinePlugin(applyOpts);
  // fast refresh
  await addFastRefreshPlugin(applyOpts);
  // progress
  await addProgressPlugin(applyOpts);
  // detect-dead-code-plugin
  await addDetectDeadCodePlugin(applyOpts);
  // fork-ts-checker
  await addForkTSCheckerPlugin(applyOpts);
  // copy
  await addCopyPlugin(applyOpts);
  // manifest
  await addManifestPlugin(applyOpts);
  // hmr
  if (isDev && opts.hmr) {
    config.plugin('hmr').use(webpack.HotModuleReplacementPlugin);
  }
  // compress
  await addCompressPlugin(applyOpts);
  // purgecss
  // await applyPurgeCSSWebpackPlugin(applyOpts);
  // handle HarmonyLinkingError
  await addHarmonyLinkingErrorPlugin(applyOpts);
  // remove node: prefix
  await addNodePrefixPlugin(applyOpts);
  // runtimePublicPath
  if (userConfig.runtimePublicPath) {
    config.plugin('runtimePublicPath').use(RuntimePublicPathPlugin);
  }

  // cache
  if (opts.cache) {
    config.cache({
      type: 'filesystem',
      // eslint-disable-next-line global-require
      version: require('../../../package.json').version,
      buildDependencies: {
        config: opts.cache.buildDependencies || [],
      },
      cacheDirectory: opts.cache.cacheDirectory || join(opts.cwd, 'node_modules', '.cache', 'bundler-webpack'),
    });

    // tnpm 安装依赖的情况 webpack 默认的 managedPaths 不生效
    // 使用 immutablePaths 避免 node_modules 的内容被写入缓存
    // tnpm 安装的依赖路径中同时包含包名和版本号,满足 immutablePaths 使用的条件
    // ref: smallfish
    // eslint-disable-next-line global-require
    if (/* isTnpm */ require('@umijs/utils/package').__npminstall_done) {
      config.snapshot({
        immutablePaths: [opts.cache.absNodeModulesPath || join(opts.cwd, 'node_modules')],
      });
    }

    config.infrastructureLogging({
      level: 'error',
      ...(process.env.WEBPACK_FS_CACHE_DEBUG
        ? {
            debug: /webpack\.cache/,
          }
        : {}),
    });
  }

  // analyzer
  if (opts.analyze) {
    await addBundleAnalyzerPlugin(applyOpts);
  }

  // chain webpack
  if (opts.chainWebpack) {
    await opts.chainWebpack(config, {
      env: opts.env,
      webpack,
    });
  }
  if (userConfig.chainWebpack) {
    await userConfig.chainWebpack(config, {
      env: opts.env,
      webpack,
    });
  }

  let webpackConfig = config.toConfig();

  // speed measure
  // TODO: mini-css-extract-plugin 报错
  webpackConfig = await addSpeedMeasureWebpackPlugin({
    webpackConfig,
  });

  if (opts.modifyWebpackConfig) {
    webpackConfig = await opts.modifyWebpackConfig(webpackConfig, {
      env: opts.env,
      webpack,
    });
  }

  return webpackConfig;
}
module.exports = getConfig;