import resolve from "resolve";
import * as path from "path";
import * as Dawn from "@dawnjs/types";
import { DefinePlugin, HotModuleReplacementPlugin } from "webpack";
import type { WebpackPluginInstance } from "webpack/types.d";
// import InterpolateHtmlPlugin from "react-dev-utils/InterpolateHtmlPlugin";
import ModuleNotFoundPlugin from "react-dev-utils/ModuleNotFoundPlugin";
// import WatchMissingNodeModulesPlugin from "react-dev-utils/WatchMissingNodeModulesPlugin";
import ForkTsCheckerWebpackPlugin from "react-dev-utils/ForkTsCheckerWebpackPlugin";
import typescriptFormatter from "react-dev-utils/typescriptFormatter";
import { ESBuildPlugin } from "esbuild-loader";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
import Webpackbar from "webpackbar";

const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const VModulePlugin = require("vmodule-webpack-plugin");

// import InlineChunkHtmlPlugin from "react-dev-utils/InlineChunkHtmlPlugin";
// import getCacheIdentifier from "react-dev-utils/getCacheIdentifier";

import { FileInfo, IGetWebpackConfigOpts } from "../types";

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");

// Generate webpack plugins
const getPlugins = (options: IGetWebpackConfigOpts, ctx: Dawn.Context) => {
  const plugins: WebpackPluginInstance[] = [];

  // false or empty array
  // if (options?.template?.length) {
  // HTMLWebpackPlugin
  // Generates an `index.html` file with the <script> injected.
  (options.entry as FileInfo[]).forEach(({ name }) => {
    const template = options.template?.find?.(temp => temp.name === name) ?? options.template?.[0];
    if (!template) return;
    const minifyOption = ctx.isEnvProduction // auto minify when production mode
      ? {
          // https://github.com/DanielRuf/html-minifier-terser
          // Strip HTML comments
          removeComments: true,
          // Collapse white space that contributes to text nodes in a document tree
          collapseWhitespace: true,
          // Remove attributes when value matches default.
          removeRedundantAttributes: true,
          // Replaces the doctype with the short (HTML5) doctype
          useShortDoctype: true,
          // Remove all attributes with whitespace-only values
          removeEmptyAttributes: true,
          // Remove type="text/css" from style and link tags. Other type attribute values are left intact
          removeStyleLinkTypeAttributes: true,
          // Keep the trailing slash on singleton elements
          keepClosingSlash: true,
          // Minify JavaScript in script elements and event attributes (uses Terser)
          minifyJS: true,
          // Minify CSS in style elements and style attributes (uses clean-css)
          minifyCSS: true,
          // Minify URLs in various attributes (uses relateurl)
          minifyURLs: true,
        }
      : false;
    plugins.push(
      new HtmlWebpackPlugin({
        inject: true,
        filename: path.join(options?.folders?.html ?? "", `${name}.html`),
        template: template.file,
        chunks: [options?.common?.name, name].filter(Boolean),
        minify: minifyOption,
      }),
    );
  });
  // }

  // InlineChunkHtmlPlugin
  // Inlines the webpack runtime script. This script is too small to warrant a network request.
  // https://github.com/facebook/create-react-app/tree/master/packages/react-dev-utils#new-inlinechunkhtmlpluginhtmlwebpackplugin-htmlwebpackplugin-tests-regex
  // ctx.isEnvProduction && plugins.push(new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]) as any);

  // InterpolateHtmlPlugin
  // Makes some environment variables available in index.html.
  // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
  // <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
  // It will be an empty string unless you specify "homepage" in `package.json`, in which case it will be the pathname of that URL.
  // https://github.com/facebook/create-react-app/tree/master/packages/react-dev-utils#new-interpolatehtmlpluginhtmlwebpackplugin-htmlwebpackplugin-replacements-keystring-string
  // plugins.push(
  // new InterpolateHtmlPlugin(HtmlWebpackPlugin, {
  // TODO: add some envs
  // }) as any,
  // );

  // ModuleNotFoundPlugin
  // This gives some necessary context to module not found errors, such as the requesting resource.
  plugins.push(new ModuleNotFoundPlugin(options.cwd));

  // DefinePlugin
  // Makes some environment variables available to the JS code, for example:
  // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
  // It is absolutely essential that NODE_ENV is set to production during a production build.
  // Otherwise React will be compiled in the very slow development mode.
  plugins.push(
    new DefinePlugin({
      "process.env": {
        NODE_ENV: JSON.stringify(options.env), // production
      },
    }),
  );

  // CaseSensitivePathsPlugin
  // Watcher doesn't work well if you mistype casing in a path so we use a plugin that prints an error when you attempt to do this.
  // See https://github.com/facebook/create-react-app/issues/240
  // https://github.com/Urthen/case-sensitive-paths-webpack-plugin
  ctx.isEnvDevelopment && plugins.push(new CaseSensitivePathsPlugin());

  // MiniCssExtractPlugin
  // Options similar to the same options in webpackOptions.output both options are optional
  !ctx.injectCSS &&
    plugins.push(
      new MiniCssExtractPlugin({
        filename: path.join(options?.folders?.style ?? "", "[name].css"),
        chunkFilename: path.join(options?.folders?.style ?? "", "[name].[contenthash:8].chunk.css"),
      }) as any,
    );

  if (options.hot) {
    // This is necessary to emit hot updates (CSS and Fast Refresh):
    // https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/232
    plugins.push(
      new ReactRefreshWebpackPlugin({
        // include: /\.(js|jsx|ts|tsx|flow)$/i,
        overlay: {
          // entry: require.resolve("react-dev-utils/webpackHotDevClient"),
          sockIntegration: "whm",
          // entry: require.resolve('@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry'),
          // module: require.resolve('@pmmmwh/react-refresh-webpack-plugin/overlay'),
          // TODO: This is just a stub module. Clean this up if possible.
          // module: require.resolve('./hotRefreshOverlayModuleStub'),
        },
      }),
    );
    plugins.push(new HotModuleReplacementPlugin());
  }

  if (options?.esbuild?.minify || options?.esbuild?.loader) {
    plugins.push(new ESBuildPlugin());
  }

  const clearConsoleReporter: any = {
    start() {
      const allowScrollBack = true;
      process.stdout.write(allowScrollBack ? "\x1B[H\x1B[2J" : "\x1B[2J\x1B[3J\x1B[H\x1Bc");
    },
  };

  plugins.push(
    new Webpackbar({
      reporters: [
        options.watch ? clearConsoleReporter : null,
        "fancy",
        options.analysis || ctx.isEnvProduction ? "stats" : null,
      ].filter(Boolean),
    }),
  );

  // webpack.IgnorePlugin
  // Moment.js is an extremely popular library that bundles large locale files
  // by default due to how webpack interprets its code. This is a practical
  // solution that requires the user to opt into importing specific locales.
  // checkContext was removed from IgnorePlugin in webpack5.
  // now IgnorePlugin must now be passed only one argument that can be an object, string or function.
  // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
  // You can remove this if you don't use Moment.js:
  // new IgnorePlugin({
  //   resourceRegExp: /^\.\/locale$/,
  //   contextRegExp: /moment$/
  // });

  // BundleAnalyzerPlugin
  // https://github.com/webpack-contrib/webpack-bundle-analyzer
  const analysisOptions = options.analysis === true ? {} : options.analysis;
  options.analysis && plugins.push(new BundleAnalyzerPlugin(analysisOptions as BundleAnalyzerPlugin.Options));

  // ForkTsCheckerWebpackPlugin
  // TypeScript type checking
  ctx.useTypeScript &&
    plugins.push(
      new ForkTsCheckerWebpackPlugin({
        typescript: resolve.sync("typescript", {
          basedir: path.join(options.cwd, "node_modules"),
        }),
        async: ctx.isEnvDevelopment,
        checkSyntacticErrors: true,
        tsconfig: path.join(options.cwd, "tsconfig.json"),
        reportFiles: [
          // This one is specifically to match during tests, as micromatch doesn't match
          "../**/src/**/*.{ts,tsx}",
          "**/src/**/*.{ts,tsx}",
          "!**/src/**/__tests__/**",
          "!**/src/**/?(*.)(spec|test).*",
          "!**/src/setupProxy.*",
          "!**/src/setupTests.*",
        ],
        silent: true,
        // The formatter is invoked directly in WebpackDevServerUtils during development
        formatter: ctx.isEnvProduction ? typescriptFormatter : undefined,
      }),
    );

  // https://webpack.docschina.org/concepts/module-federation/
  options.moduleFederation &&
    plugins.push(
      new ModuleFederationPlugin({
        // name: "app_one_remote",
        // remotes: {
        //   app_two: "app_two_remote",
        //   app_three: "app_three_remote"
        // },
        // exposes: {
        //   AppContainer: "./src/App"
        // },
        shared: ["react", "react-dom"],
        ...options.moduleFederation,
      }),
    );

  // $config Plugin
  const configPath = path.resolve(options.cwd, options?.config?.path);
  // @ts-ignore
  ctx.utils.confman.env = options?.config?.env;
  ctx.$config = ctx.utils.confman.load(configPath) || options?.config?.content;
  ctx.$config &&
    plugins.push(
      new VModulePlugin.VModulePlugin({
        env: options?.config?.env,
        name: options?.config?.name,
        content: ctx.$config,
        watch: [configPath, `${configPath}.*`, `${configPath}/**/*.*`],
      }),
    );
  return plugins;
};

export default getPlugins;
