/* eslint-disable n/no-sync */
import DuplicatePackageCheckerPlugin from "@cerner/duplicate-package-checker-webpack-plugin";
import CompressWebpackPlugin from "compression-webpack-plugin";
import CopyWebpackPlugin from "copy-webpack-plugin";
import CssMinimizerPlugin from "css-minimizer-webpack-plugin";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin";
import { spawn, spawnSync } from "node:child_process";
import { globSync, readFileSync } from "node:fs";
import { basename, join, relative, resolve } from "node:path";
import * as sass from "sass";
import { TimeAnalyticsPlugin } from "time-analytics-webpack-plugin";
import webpack from "webpack";
import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
import { StatsWriterPlugin } from "webpack-stats-plugin";
import WebpackBar from "webpackbar";

import { findWorkspaceRootSync, getPackagesArray } from "@devowl-wp/monorepo-utils";
import { Pot, WebpackPluginRegexpTranslationExtractor } from "@devowl-wp/regexp-translation-extractor";

import { AvoidStandaloneProcessEnvMemberExpression } from "./plugins/avoidStandaloneProcessEnvMemberExpression.js";
import { getWordPressPackages } from "./utils/getWordPressPackages.js";
import { getWordPressPlugins } from "./utils/getWordPressPlugins.js";
import { slugCamelCase } from "./utils/slugCamelCase.js";

import type { WebpackBarOptions } from "./utils/types.js";
import type { Compilation, Compiler, Configuration, RuleSetRule, optimize } from "webpack";

const { DefinePlugin, IgnorePlugin, ProvidePlugin, SourceMapDevToolPlugin } = webpack;

const workspaceRoot = findWorkspaceRootSync();

class CachebusterPlugin {
    public apply(compiler: Compiler) {
        compiler.hooks.done.tap("ShellCommandPlugin", () => {
            try {
                spawn("dowl", ["task", "@devowl-wp/utils:plugin/cachebuster"]);
            } catch {
                // dowl binary not found, skip cachebuster task
            }
        });
    }
}

type FactoryValues = {
    pwd: string;
    mode: string;
    rootPkg: any;
    pkg: any;
    plugins: ReturnType<typeof getWordPressPlugins>;
    slug: string;
    type: "frontend" | "plugin" | "package";
    rootSlugCamelCased: string;
    slugCamelCased: string;
    outputPath: string;
    tsFolder: string;
    cacheDirectory: (name: string) => string;
};

/**
 * Create default settings for the current working directory.
 * If you want to do some customizations you can pass an override function
 * which passes the complete default settings and you can mutate it.
 */
function createDefaultSettings(
    /**
     * Always use `import.meta.filename`. Purposes filesystem caching in CI systems.
     */
    filename: string,
    type: FactoryValues["type"],
    {
        override,
        definePlugin = (processEnv) => processEnv,
        webpackBarOptions = (options) => options,
        replaceWithEmptyModule,
        htmlPluginOptions = {},
        copy = [],
        forceFrontendChunks = false,
        skipExternals = [],
        skipEntrypoints = [],
        onlyEntrypoints = [],
        bundleAnalyzerOptions = false,
        reportDuplicates = false,
        sideEffectFree = [],
        writeStats = false,
    }: {
        override?: (settings: Configuration[], factoryValues: FactoryValues) => void;
        definePlugin?: (processEnv: any) => any;
        webpackBarOptions?: (options: WebpackBarOptions) => WebpackBarOptions;
        replaceWithEmptyModule?: string[];
        htmlPluginOptions?: Partial<HtmlWebpackPlugin["options"]>;
        /**
         * Copy patterns to build folder.
         *
         * @see https://webpack.js.org/plugins/copy-webpack-plugin/
         */
        copy?: Array<any>;
        /**
         * Pass `true` or constructor options to activate the `BundleAnalyzerPlugin`.
         */
        bundleAnalyzerOptions?: boolean | BundleAnalyzerPlugin.Options;
        /**
         * Pass `true` or advanced options to activate a report for duplicate packages.
         *
         * @see https://github.com/cerner/terra-toolkit/tree/main/packages/duplicate-package-checker-webpack-plugin#configuration
         */
        reportDuplicates?:
            | boolean
            | {
                  verbose?: boolean;
                  emitError?: boolean;
                  showHelp?: boolean;
                  strict?: boolean;
                  exclude?: (instance: { name: string; version: string; path: string; issuer?: string }) => boolean;
                  alwaysEmitErrorsFor?: string[];
              };
        /*
         * Force to use chunk groups like we do for frontends (ignoring the passed type).
         */
        forceFrontendChunks?: false | string;
        /**
         * Allows to skip externals in `config.externals`. This can be useful to
         * force-bundle a package.
         */
        skipExternals?: string[];
        /**
         * Allows to skip found entrypoints in `config.entry`.
         */
        skipEntrypoints?: string[];
        /**
         * Allows e. g. to only allow one entrypoint.
         */
        onlyEntrypoints?: string[];
        /**
         * Mark some node modules as side effect free.
         *
         * @see https://git.io/Jqc5e
         */
        sideEffectFree?: RuleSetRule["include"][];
        /**
         * Write stats to `webpack-json-stats/head` so you can e.g. use `webpack-bundle-delta`.
         */
        writeStats?: boolean;
    } = {},
) {
    const pwd = process.env.DOCKER_START_PWD || process.env.PWD;
    const NODE_ENV = (process.env.NODE_ENV as Configuration["mode"]) || "development";
    const CI = !!process.env.CI;
    const nodeEnvFolder = NODE_ENV === "production" ? "dist" : "dev";
    const rootPkg = JSON.parse(readFileSync(resolve(pwd, workspaceRoot, "package.json"), { encoding: "utf-8" }));
    const pkg = JSON.parse(readFileSync(resolve(pwd, "package.json"), { encoding: "utf-8" }));
    let settings: Configuration[] = [];
    const plugins = getWordPressPlugins(pwd);
    const slug = pkg.slug ? pkg.slug : pkg.name.split("/")[1];
    const NoopLoader = import.meta.resolve("./loader/noop.js").slice(7); // without file:// protocol

    const sourceMapModuleNameTemplateMonorepoRemoveStartsWith = ["/src/public/ts", "/src", "/lib"];
    const sourceMapModuleNameTemplateMonorepoMap = getPackagesArray().reduce(
        (previous, next) => {
            previous[next.location.substring(workspaceRoot.length)] = `${next.name.split("/")[1]}@${next.version}`;
            return previous;
        },
        {} as Record<string, string>,
    );

    const typeValue = <T>(returnValue: { [P in FactoryValues["type"]]?: T } & { other: T }) =>
        returnValue[type] || returnValue["other"];

    const rootSlugCamelCased = slugCamelCase(rootPkg.name);
    const packages = getWordPressPackages(pwd, rootSlugCamelCased);
    const slugCamelCased = slugCamelCase(slug);
    const outputPath = typeValue<string>({
        plugin: join(pwd, "src/public", nodeEnvFolder),
        other: join(pwd, nodeEnvFolder),
    });
    const tsFolder = resolve(pwd, typeValue<string>({ package: "lib", plugin: "src/public/ts", other: "src" }));

    // See also: https://www.npmjs.com/package/@wordpress/dependency-extraction-webpack-plugin
    const wordPressExternals = {
        react: "React",
        "react-dom": "ReactDOM",
        "react/jsx-runtime": "ReactJSXRuntime",
        "react/jsx-dev-runtime": "ReactJSXRuntime",
        jquery: "jQuery",
        mobx: "mobx",
        wp: "wp",
        wpApiSettings: "wpApiSettings",
        "@wordpress/i18n": "wp['i18n']",
        // Get dynamically a map of externals for add-on development
        ...plugins.reduce((map: { [key: string]: string }, obj) => {
            map[obj.moduleId] = obj.externalId;
            return map;
        }, {}),
        // Get dynamically a map of external package dependencies
        ...packages.reduce((map: { [key: string]: string }, obj) => {
            map[obj.name] = obj.externalId.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
            return map;
        }, {}),
    };

    // We are using a custom webpack configuration to replace the react-dom/client dependency with our own package.
    // This ensures compatibility with the React 16 and v17 of Divi and WordPress < 6.2
    // See https://app.clickup.com/t/86959qqq1
    if (
        pkg.name !== "@devowl-wp/utils" &&
        ["plugin", "package"].indexOf(type) > -1 &&
        skipExternals.indexOf("react-dom") === -1
    ) {
        wordPressExternals["react-dom/client"] = wordPressExternals["@devowl-wp/utils"];
    }

    // We need to copy so updates to this object are not reflected to other configurations
    // And also, we need to copy the targets from `browserslist` to SWC `env.targets`
    // Read more about this bug in https://github.com/swc-project/swc/issues/3365, https://github.com/swc-project/swc/pull/8921
    // and https://github.com/swc-project/swc-loader/issues/37#issuecomment-1233829398
    const swcOptions = JSON.parse(JSON.stringify(pkg.swc));
    swcOptions.env = swcOptions.env || {};
    swcOptions.env.targets = pkg.browserslist;

    // For WordPress development: We cannot currently use `jsxDEV` as WordPress does not provide a `react-jsx-dev-runtime.js` file.
    // See: https://github.com/WordPress/WordPress/blob/master/wp-includes/js/dist/vendor/react-jsx-runtime.js
    // And: https://github.com/WordPress/gutenberg/blob/ceb89495d22d0907e37faf170f052679e36f90aa/packages/dependency-extraction-webpack-plugin/lib/util.js#L46-L48
    if (type === "plugin" || type === "package") {
        swcOptions.jsc.transform.react.development = false;
    }

    // Prepare a predefined usable cache directory and identifier
    const cacheDirectory = (name: string) => resolve(pwd, CI ? "node_modules_cache" : "node_modules/.cache", name);

    // Prepare `process.env.VAR` variables to avoid `process.env` direct access
    let definePluginProcessEnv = definePlugin({
        NODE_ENV: JSON.stringify(NODE_ENV),
        env: JSON.stringify(NODE_ENV),
        rootSlug: JSON.stringify(rootPkg.name),
        slug: JSON.stringify(slug),
    });

    // For frontends, assets are localized via i18next
    if (type === "frontend") {
        copy.push({ from: "src/locale", to: "locale" });

        definePluginProcessEnv = {
            ...definePluginProcessEnv,
            // This variable is exposed via NGINX directly into `index.html`
            // FRONTEND_APP_ENVIRONMENT: JSON.stringify(FRONTEND_APP_ENVIRONMENT),
            // ENVIRONMENT_BASE_HOSTNAME: JSON.stringify(ENVIRONMENT_BASE_HOSTNAME),
            SUPPORTED_LANGS: JSON.stringify(globSync(`src/locale/*`).map((p: string) => basename(p))),
        };
    }

    const cssLoaders = [
        MiniCssExtractPlugin.loader,
        {
            loader: "css-loader",
            options: {
                url: false,
            },
        },
        {
            loader: "postcss-loader",
            options: {
                postcssOptions: {
                    plugins: ["postcss-preset-env"],
                },
            },
        },
    ];

    const pluginSettings: Configuration = {
        context: pwd,
        mode: NODE_ENV,
        cache: {
            type: "filesystem",
            cacheDirectory: cacheDirectory("webpack"),
            buildDependencies: {
                config: [filename, import.meta.filename],
            },
        },
        infrastructureLogging: {
            debug: [].concat(CI ? [/PackFileCache/] : []),
        },
        performance: {
            assetFilter: (assetFilename: string) =>
                !/\.map$/.test(assetFilename) && !/\/webpack-json-stats\//.test(assetFilename),
        },
        optimization: {
            usedExports: true,
            sideEffects: true,
            moduleIds: "deterministic",
            // As we can not use HtmlWebpackPlugin in WordPress and rely on static translation files we need to make
            // sure that they are always the same in local development and CI system. The most difficult part for this
            // is the `.po` generation and `wp make-json`.
            chunkIds: "deterministic",
            // runtimeChunk: "single", // TODO: perhaps we can use this in the near future?
            minimizer: [
                new CssMinimizerPlugin({
                    minify: CssMinimizerPlugin.cleanCssMinify,
                }),
                "...",
            ],
            splitChunks: {
                cacheGroups:
                    forceFrontendChunks || type === "frontend"
                        ? // Create only one vendor file for initial chunk
                          {
                              vendor: {
                                  test: /node_modules.*(?<!\.css)(?<!\.scss)(?<!\.less)$/,
                                  name: forceFrontendChunks ? `vendor-${forceFrontendChunks}` : "vendor",
                                  chunks: "initial",
                                  priority: 25,
                                  enforce: true,
                                  reuseExistingChunk: true,
                              },
                          }
                        : type === "plugin"
                          ? // Dynamically get the entries from first-level files so every entrypoint get's an own vendor file (https://git.io/Jv2XY)
                            {
                                ...plugins
                                    .filter(({ location }) => location === pwd)
                                    .reduce(
                                        (
                                            map: {
                                                [key: string]: Extract<
                                                    ConstructorParameters<
                                                        typeof optimize.SplitChunksPlugin
                                                    >[0]["cacheGroups"][0],
                                                    {
                                                        idHint?: string;
                                                    }
                                                >;
                                            },
                                            obj,
                                        ) => {
                                            map[`vendor-${obj.entrypointName}`] = {
                                                test: /node_modules.*(?<!\.css)(?<!\.scss)(?<!\.less)$/,
                                                chunks: (chunk) => chunk.name === obj.entrypointName,
                                                name: `vendor-${obj.entrypointName}`,
                                                enforce: true,
                                            };
                                            return map;
                                        },
                                        {},
                                    ),
                            }
                          : {
                                vendor: {
                                    test: /node_modules.*(?<!\.css)(?<!\.scss)(?<!\.less)$/,
                                    name: (module, chunks, cacheGroupKey) => {
                                        const allChunksNames = chunks.map((item: any) => item.name).join("-");
                                        return `${cacheGroupKey}-${allChunksNames}`;
                                    },
                                    chunks: "initial",
                                    enforce: true,
                                },
                            },
            },
        },
        output: {
            environment: {
                arrowFunction: true,
                const: true,
                destructuring: true,
                forOf: true,
            },
            path: outputPath,
            // Default in Webpack v6, more performant and secure
            hashFunction: "xxhash64",
            filename: typeValue<string>({ frontend: "[name].[contenthash].js", other: "[name].js" }),
            clean: type === "frontend",
            // Add query argument with chunk hash as we are using `webpackChunkName` for
            // code splitting. Browsers do not invalidate the cache as the names are always
            // the same.
            chunkFilename: "[name].js?ver=[chunkhash]",
            library: typeValue<string>({
                plugin: `${slugCamelCased}_[name]`,
                package: `${rootSlugCamelCased}_${slugCamelCased}`,
                other: undefined,
            }),
        },
        entry: typeValue<Configuration["entry"]>({
            // Dynamically get the entries from first-level files
            plugin: {
                ...plugins
                    .filter(({ location }) => location === pwd)
                    .reduce((map: { [key: string]: string }, obj) => {
                        map[obj.entrypointName] = obj.modulePath;
                        return map;
                    }, {}),
            },
            // Allow only one entry point, requires additional config for more
            other: {
                index: resolve(tsFolder, "index.tsx"),
            },
        }),
        externals: typeValue<Configuration["externals"]>({
            // WordPress plugins and packages can use existing APIs
            plugin: wordPressExternals,
            package: wordPressExternals,
            frontend: {
                // Some environment variables can be dynamically exposed by the NGINX server
                nginx: "window.nginx",
            },
            other: {},
        }),
        devtool: false,
        module: {
            rules: [
                {
                    test: /\.(ts|tsx|js|jsx)$/,
                    exclude: /node_modules/,
                    use: [
                        CI ? NoopLoader : "thread-loader",
                        {
                            loader: "swc-loader",
                            options: swcOptions,
                        },
                    ],
                },
                {
                    test: /\.css$/,
                    resourceQuery: {
                        // See https://webpack.js.org/guides/asset-modules/#replacing-inline-loader-syntax
                        not: [/inline-raw/],
                    },
                    use: cssLoaders,
                },
                {
                    test: /\.scss$/,
                    resourceQuery: {
                        not: [/inline-raw/],
                    },
                    use: [
                        ...cssLoaders,
                        {
                            loader: "sass-loader",
                            options: {
                                implementation: sass,
                                additionalData: Object.entries(definePluginProcessEnv)
                                    .map(([key, value]) => `$${key}: ${value};`)
                                    .join("\n"),
                            },
                        },
                    ],
                },
                {
                    resourceQuery: /inline-raw/,
                    type: "asset/source",
                },
                type === "frontend" && {
                    test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|svg)(\?[a-z0-9=.]+)?$/,
                    // Do not outsource `new URL()` calls
                    dependency: { not: ["url"] },
                    type: "asset/resource",
                },
            ].filter(Boolean),
        },
        resolve: {
            extensions: [".js", ".jsx", ".ts", ".tsx"],
            // Allow TS(X) files to be imported as JS. This is needed for the ESM ecosystem
            // See also https://github.com/webpack/webpack/issues/13252#issuecomment-1170215575
            extensionAlias: {
                ".js": [".js", ".jsx", ".ts", ".tsx"],
            },
            alias: (replaceWithEmptyModule || []).reduce(
                (a, b) => {
                    a[b] = import.meta.resolve("./emptyModule.js").slice(7); // without file:// protocol
                    return a;
                },
                {} as Record<string, string>,
            ),
            modules: [
                "node_modules",
                resolve(process.cwd(), "node_modules"),
                resolve(import.meta.dirname, "../node_modules"),
            ],
            // In our monorepo, we first try to resolve the raw source file, then search for a compiled file
            // We should not use `source` as condition name and use a "company-specific" condition name instead as other external packages like
            // https://github.com/scroll-into-view/scroll-into-view-if-needed/blob/664207a227da4e7d6ec7df567fe29a14a922098c/package.json#L31 also
            // use the `source` condition.
            // See also: https://nodejs.org/api/packages.html#community-conditions-definitions
            // > The listing of the condition definition should provide a coordination benefit to the ecosystem that wouldn't
            // > otherwise be possible. For example, this would not necessarily be the case for company-specific or
            // > application-specific conditions.
            conditionNames: ["devowl-source", "..."],
        },
        resolveLoader: {
            modules: [
                "node_modules",
                resolve(process.cwd(), "node_modules"),
                resolve(import.meta.dirname, "../node_modules"),
            ],
        },
        snapshot: CI
            ? {
                  // see https://github.com/webpack/webpack/discussions/14041
                  buildDependencies: {
                      hash: true,
                  },
                  module: {
                      hash: true,
                  },
                  resolve: {
                      hash: true,
                  },
                  resolveBuildDependencies: {
                      hash: true,
                  },
              }
            : undefined,
        plugins: [
            new AvoidStandaloneProcessEnvMemberExpression(),
            ["plugin", "package"].indexOf(type) > -1 &&
                new WebpackPluginRegexpTranslationExtractor({
                    outputFormat: Pot,
                    functionNames: {
                        single: ["__", "i18n_"],
                        singleContext: ["_x", "i18n_x"],
                        plural: ["_n", "i18n_n"],
                        pluralContext: ["_nx", "i18n_nx"],
                    },
                    createChunkMap: {
                        filename: "i18n-dependency-map-%s.json",
                        filterChunk: (f) => f.startsWith("chunk-"),
                        filterDependency: (f, a) => /^\d+\./.test(f) || f === a,
                    },
                }),
            new DefinePlugin({
                ...Object.keys(definePluginProcessEnv).reduce(
                    (p, k) => {
                        p[`process.env.${k}`] = definePluginProcessEnv[k];
                        delete p[k];
                        return p;
                    },
                    {} as Record<string, string>,
                ),
                // so e.g. `if (process.env && process.env.NODE_ENV)` works
                "process.env": {},
            }),
            new MiniCssExtractPlugin({
                filename: typeValue({
                    // For WordPress, `contenthash` is not needed as this is done through the `$version` parameter in `wp_enqueue_style`
                    other: "[name].css",
                    frontend: "[name].[contenthash].css",
                }),
            }) as any,
            new ProvidePlugin({
                setImmediate: ["setimmediate", "setImmediate"],
                clearImmediate: ["setimmediate", "clearImmediate"],
            }),
            new SourceMapDevToolPlugin({
                // defaults: https://git.io/J9kgw
                filename: "[file].map[query]",
                exclude: "vendor-",
                // See https://github.com/webpack/webpack/blob/ae9f0e065a7734504e69d48ce9a0cd78a0b57a40/lib/ModuleFilenameHelpers.js#L110
                moduleFilenameTemplate: ({
                    absoluteResourcePath,
                    namespace,
                    resourcePath,
                }: {
                    identifier: string;
                    shortIdentifier: string;
                    resource: string;
                    resourcePath: string;
                    absoluteResourcePath: string;
                    query: string;
                    moduleId: string;
                    hash: string;
                    namespace: string;
                }) => {
                    const protocol = `webpack://devowl-local-files/${slug}/${pluginSettings.name || "default"}`;
                    if (absoluteResourcePath.startsWith(workspaceRoot)) {
                        // Example: "/wordpress-plugins/real-cookie-banner/src/public/ts/admin.tsx"
                        let path = absoluteResourcePath.substring(workspaceRoot.length);

                        // Shorten known packages from monorepo structure
                        const firstTwoFolders = path.substring(1).split("/", 2);
                        if (firstTwoFolders.length > 1) {
                            const packagePath = `/${firstTwoFolders.join("/")}`;
                            if (sourceMapModuleNameTemplateMonorepoMap[packagePath]) {
                                path = `${path.substring(packagePath.length)}`;

                                for (const removeStartsWith of sourceMapModuleNameTemplateMonorepoRemoveStartsWith) {
                                    if (path.startsWith(removeStartsWith)) {
                                        path = path.substring(removeStartsWith.length);
                                        break;
                                    }
                                }

                                if (firstTwoFolders[1] !== slug) {
                                    path = `/internal/${sourceMapModuleNameTemplateMonorepoMap[packagePath]}${path}`;
                                } else {
                                    path = `/src${path}`;
                                }
                            }
                        }

                        // Short PNPM path
                        // Example: /node_modules/.pnpm/antd@4.24.8_wcqkhtmu7mswc6yz4uyexck3ty/node_modules/antd
                        //          /node_modules/.pnpm/antd@4.24.8/node_modules/antd
                        // See https://regex101.com/r/O2dAnC/1
                        path = path.replace(
                            /node_modules\/\.pnpm\/[@]?[^@]+@([^_/]+)(?:_[^/]+)?\/node_modules\/([^@/]+|@[^/]+\/[^/]+)/,
                            `node_modules/$2@$1`,
                        );

                        return `${protocol}${path}`;
                    } else {
                        // Standard = [namespace]/[resourcePath]
                        return `${protocol}${namespace}/${resourcePath}`;
                    }
                },
            }),
            new IgnorePlugin({
                resourceRegExp: /async_hooks/,
            }),
            // No typings available, see https://github.com/cerner/terra-toolkit/tree/main/packages/duplicate-package-checker-webpack-plugin#configuration
            reportDuplicates &&
                new DuplicatePackageCheckerPlugin({
                    verbose: true,
                    emitError: false,
                    showHelp: false,
                    ...(reportDuplicates === true ? {} : reportDuplicates),
                }),
            type === "frontend" &&
                new HtmlWebpackPlugin({
                    base: "/",
                    template: resolve(tsFolder, "index.html"),
                    favicon: resolve(tsFolder, "assets/favicon.png"),
                    ...htmlPluginOptions,
                }),
            copy.length &&
                new CopyWebpackPlugin({
                    patterns: copy,
                }),
        ]
            .filter(Boolean)
            .concat(
                CI
                    ? [
                          // When writing stats, we also want to obtain the GZIP size
                          writeStats &&
                              new CompressWebpackPlugin({
                                  // webpack supports only relative pathes when exporting assets
                                  filename: relative(
                                      outputPath,
                                      resolve(pwd, "webpack-json-stats", "gzip", "[path][base].gz"),
                                  ),
                                  algorithm: "gzip",
                                  test: /\.(js|mjs|css)$/,
                              }),
                          // Everything else **first**.
                          writeStats &&
                              new StatsWriterPlugin({
                                  filename: ((compiler: Compilation) =>
                                      // webpack supports only relative pathes when exporting assets
                                      relative(
                                          outputPath,
                                          resolve(
                                              pwd,
                                              "webpack-json-stats",
                                              "head",
                                              `stats-${[...compiler.entrypoints.keys()].join("-")}.json`,
                                          ),
                                      )) as any,
                                  stats: {
                                      assets: true,
                                      entrypoints: true,
                                      chunks: true,
                                      modules: true,
                                  },
                              }),
                      ].filter(Boolean)
                    : [
                          new WebpackBar(
                              webpackBarOptions({
                                  name: slug,
                              }),
                          ) as any /* webpack typing is incompatible */,
                          // When bundling a plugin in a CI we have already ensured the type check in a previous job
                          new ForkTsCheckerWebpackPlugin(),
                          ["package", "plugin"].indexOf(type) > -1 && new CachebusterPlugin(),
                      ].filter(Boolean),
            ),
    };

    if (sideEffectFree.length) {
        pluginSettings.module.rules.push(
            ...sideEffectFree.map((module) => ({
                include: module,
                sideEffects: false,
            })),
        );
    }

    skipExternals.forEach((key) => {
        delete (pluginSettings.externals as any)[key];
    });

    skipEntrypoints.forEach((key) => {
        delete (pluginSettings.entry as any)[key];
    });

    onlyEntrypoints.length &&
        Object.keys(pluginSettings.entry).forEach((key) => {
            if (onlyEntrypoints.indexOf(key) === -1) {
                delete (pluginSettings.entry as any)[key];
            }
        });

    if (bundleAnalyzerOptions && !CI) {
        const ips = spawnSync("hostname", ["-i"], { encoding: "utf-8" }).stdout.replace("\n", "").split(" ");
        pluginSettings.plugins.push(
            new BundleAnalyzerPlugin({
                analyzerHost: ips[ips.length - 1],
                ...(bundleAnalyzerOptions === true ? {} : bundleAnalyzerOptions),
            }) as any /* webpack typing is incompatible */,
        );
    }

    settings.push(pluginSettings);

    override?.(settings, {
        pwd,
        mode: NODE_ENV,
        rootPkg,
        pkg,
        plugins,
        slug,
        type,
        rootSlugCamelCased,
        slugCamelCased,
        outputPath,
        tsFolder,
        cacheDirectory,
    });

    if (process.env.ANALYZE_BUILD_TIME) {
        settings = settings.map((c) => TimeAnalyticsPlugin.wrap(c));
    }

    return settings;
}

export { type FactoryValues, createDefaultSettings };
