const path = require("path");
const webpack = require("webpack");

const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const DashboardPlugin = require("webpack-dashboard/plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HappyPack = require("happypack");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");

const {getClientEnvironment} = require("../env");
const paths = require("../paths");

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.publicPath;
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = paths.publicUrl.slice(0, -1);
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === "./";
// Get environment variables to inject into our app.
const environ = getClientEnvironment(publicUrl);

// Note: defined here because it will be used more than once.
const cssFilename = "static/css/[name].[contenthash:8].css";

const postCssConfig = {
    loader: require.resolve("postcss-loader"),
    options: {
        // Necessary for external CSS imports to work
        // https://github.com/facebookincubator/create-react-app/issues/2677
        ident: "postcss",
        plugins: () => [
            require("postcss-flexbugs-fixes"),
            require("postcss-preset-env")({
                stage: 0,
            }),
        ],
    },
};

const sassConfig = {
    loader: require.resolve("sass-loader"),
    options: {},
};

const rules = (env) => {
    // "postcss" loader applies autoprefixer to our CSS.
    // "css" loader resolves paths in CSS and adds assets as dependencies.
    // "style" loader turns CSS into JS modules that inject <style> tags.
    // In production, we use a plugin to extract that CSS to a file, but
    // in development "style" loader enables hot editing of CSS.
    const styleLoader =
        (env === "prod") ? {
            loader: MiniCssExtractPlugin.loader
        } : require.resolve("style-loader");
    const cssRule = {
        test: /\.css$/,
        use: [
            styleLoader,
            {
                loader: require.resolve("css-loader"),
                options: {
                    importLoaders: 1,
                },
            },
            postCssConfig,
        ]
    };
    const sassRule = {
        test: /\.scss$/,
        use: [
            styleLoader,
            {
                loader: require.resolve("css-loader"),
                options: {
                    importLoaders: 1,
                },
            },
            postCssConfig,
            sassConfig,
        ],
    };
    return [
        {
            // "oneOf" will traverse all following loaders until one will
            // match the requirements. when no loader matches it will fall
            // back to the "file" loader at the end of the loader list.
            oneOf: [
                // "url" loader works like "file" loader except that it embeds assets
                // smaller than specified limit in bytes as data urls to avoid requests.
                // a missing `test` is equivalent to a match.
                {
                    test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
                    loader: require.resolve("url-loader"),
                    options: {
                        limit: (env === "prod") ? 10000 : 0,
                        name: "static/media/[name].[hash:8].[ext]",
                    },
                },
                cssRule,
                sassRule,
                // Process TypeScript with TSC through HappyPack.
                {
                    test: /\.tsx?$/, use: "happypack/loader?id=ts",
                    include: [ paths.appDir, paths.commonDir ],
                },
                // "file" loader makes sure those assets get served by WebpackDevServer.
                // When you `import` an asset, you get its (virtual) filename.
                // In production, they would get copied to the `build` folder.
                // This loader doesn"t use a "test" so it will catch all modules
                // that fall through the other loaders.
                {
                    // Exclude `js` files to keep "css" loader working as it injects
                    // it"s runtime that would otherwise processed through "file" loader.
                    // Also exclude `html` and `json` extensions so they get processed
                    // by webpacks internal loaders.
                    exclude: [/\.js$/, /\.html$/, /\.json$/],
                    loader: require.resolve("file-loader"),
                    options: {
                        name: "static/media/[name].[hash:8].[ext]",
                    },
                },
            ],
        },
    ];
}


const getConfig = module.exports = (env) => {
    const isProd = (env === "prod");
    const isDev = (env === "dev");
    // Assert this just to be safe.
    // Development builds of React are slow and not intended for production.
    if (isProd && environ.stringified["process.env"].NODE_ENV !== '"production"') {
        throw new Error("Production builds must have NODE_ENV=production.");
    }

    const plugins = [
        new HtmlWebpackPlugin({
            inject: true,
            template: paths.appHtml,
            minify: isProd ? {
                removeComments: true,
                collapseWhitespace: true,
                removeRedundantAttributes: true,
                useShortDoctype: true,
                removeEmptyAttributes: true,
                removeStyleLinkTypeAttributes: true,
                keepClosingSlash: true,
                minifyJS: true,
                minifyCSS: true,
                minifyURLs: true,
            } : undefined,
        }),
        // 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 was set to production here.
        // Otherwise React will be compiled in the very slow development mode.
        new webpack.DefinePlugin(environ.stringified),
        new CaseSensitivePathsPlugin(),
        // TODO: doesn't work with typescript target: es6
        isProd && new UglifyJsPlugin({
            sourceMap: shouldUseSourceMap,
        }),
        isDev && new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(),
        new HappyPack({
            id: "ts",
            threads: 2,
            loaders: [{
                loader: "ts-loader",
                options: {
                    configFile: paths.appTsConfig,
                    happyPackMode: true,
                },
            }],
        }),
        new ForkTsCheckerWebpackPlugin({
            checkSyntacticErrors: true,
            tsconfig: paths.appTsConfig,
            tslint: paths.resolveRoot("tslint.json"),
        }),
        isDev && new DashboardPlugin(),
        new BundleAnalyzerPlugin({
            analyzerMode: "static",
            openAnalyzer: false,
            reportFilename: path.resolve(paths.serverBuildDir, "report.html"),
        }),
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
        isProd && new MiniCssExtractPlugin({
            filename: "static/css/[name].[chunkhash:8].css",
            chunkFilename: "static/css/[id].[chunkhash:8].css",
        })
    ].filter(Boolean);

    return {
        mode: isProd ? "production" : "development",
        bail: isProd,
        devtool: shouldUseSourceMap ?
            isProd ? "source-map" : "inline-source-map" :
            false,
        entry: [
            isDev && require.resolve("react-hot-loader/patch"),
            isDev && require.resolve("react-dev-utils/webpackHotDevClient"),
            require.resolve("./polyfills"),
            paths.appEntry,
        ].filter(Boolean),
        output: {
            path: paths.appBuildDir,
            pathinfo: isDev,
            filename: isProd ?
                'static/js/[name].[chunkhash:8].js' :
                "static/js/bundle.js",
            chunkFilename: isProd ?
                'static/js/[name].[chunkhash:8].chunk.js' :
                "static/js/[name].chunk.js",
            publicPath: publicPath,
            devtoolModuleFilenameTemplate: isDev ?
                (info) =>
                    "webpack://" + path.resolve(info.absoluteResourcePath).replace(/\\/g, "/") : undefined,
        },
        resolve: {
            extensions: [".ts", ".tsx", ".js", ".json"],
            alias: {
                "@app": paths.appDir,
                "@common": paths.commonDir,
            }
        },
        module: {
            rules: rules(env),
        },
        plugins: plugins,
        optimization: {
            namedModules: isProd,
        },
        devServer: {
            hot: true,
            historyApiFallback: true,
            port: 8081,
            proxy: [{
                context: ["/api"], // TODO: update when there is actually an api
                target: paths.publicUrl,
            }],
        },
    }
};