let path = require('path');
let Entry = require('./Entry');
let webpackRules = require('./webpack-rules');
let webpackPlugins = require('./webpack-plugins');
let webpackDefaultConfig = require('./webpack-default');

class WebpackConfig {
    /**
     * Create a new instance.
     *
     * @param {import("../Mix.js")} mix
     */
    constructor(mix) {
        this.mix = mix;
        this.chunks = mix.chunks;

        /** @type {ReturnType<webpackDefaultConfig>} */
        this.webpackConfig = {};
    }

    /**
     * Build the Webpack configuration object.
     */
    async build() {
        this.webpackConfig = webpackDefaultConfig(this.mix);

        await this.buildEntry();
        this.buildOutput();
        this.configureHMR();
        await this.buildRules();
        await this.buildPlugins();
        this.buildChunks();

        // We'll announce that the core config object has been
        // generated by Mix. At this point, any plugins may
        // hook in and modify the config as necessary.
        await this.mix.dispatch('configReady', this.webpackConfig);

        // Rebuild the chunks as plugins may have added new ones
        this.buildChunks();

        // Finally, we'll make one last announcement for the user
        // to hook into - using mix.override().
        await this.mix.dispatch('configReadyForUser', this.webpackConfig);

        // Rebuild the chunks as the user may have changed things
        this.buildChunks();

        return this.webpackConfig;
    }

    /**
     * Build the entry object.
     */
    async buildEntry() {
        let entry = new Entry(this.mix);

        if (!this.mix.bundlingJavaScript) {
            entry.addDefault();
        }

        await this.mix.dispatch('loading-entry', entry);

        this.webpackConfig.entry = entry.get();
    }

    /**
     * Build the output object.
     */
    buildOutput() {
        this.webpackConfig.output = {
            hashFunction: 'xxhash64',
            path: path.resolve(this.mix.config.publicPath),
            filename: '[name].js',

            chunkFilename: pathData => {
                let hasAbsolutePathChunkName =
                    pathData.chunk &&
                    pathData.chunk.name &&
                    pathData.chunk.name.startsWith('/');

                if (
                    (this.mix.components.get('js') || this.mix.components.get('ts')) &&
                    !hasAbsolutePathChunkName
                ) {
                    let output = this.mix.components.get('ts')
                        ? this.mix.components.get('ts').toCompile[0].output
                        : this.mix.components.get('js').toCompile[0].output;

                    return `${output.normalizedOutputPath()}/[name].js`;
                }

                return '[name].js';
            },

            publicPath: '/'
        };
    }

    configureHMR() {
        if (!this.mix.isUsing('hmr')) {
            return;
        }

        // TODO: Centralize this code between HotReloading and here…
        // It's duplicated
        const { https, host, port } = this.mix.config.hmrOptions;
        const protocol = https ? 'https' : 'http';
        const url = `${protocol}://${host}:${port}/`;

        this.webpackConfig.output = {
            ...this.webpackConfig.output,

            publicPath: url
        };

        this.webpackConfig.devServer = {
            host,
            port,

            client: {
                webSocketURL: {
                    hostname: host,
                    pathname: '/ws',
                    port
                }
            },

            liveReload: false,

            https,

            devMiddleware: {
                headers: {
                    'Access-Control-Allow-Origin': '*',
                    'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
                    'Access-Control-Allow-Headers':
                        'X-Requested-With, Content-Type, Authorization'
                }
            },

            /**
             *
             * @param {{app: import("express").Application}} param0
             */
            onBeforeSetupMiddleware({ app }) {
                app.use(function (req, _, next) {
                    // Something causes hot update chunks (except for the JSON payload)
                    // to start with a double slash
                    // e.g. GET http://localhost:8080//js/app.[hash].hot-update.js

                    // This causes loading those chunks to fail so we patch it up here
                    // This is super hacky and a proper solution should be found eventually
                    req.url = req.url.replace(/^\/\//, '/');

                    next();
                });
            },

            ...this.webpackConfig.devServer
        };
    }

    /**
     * Build the rules array.
     */
    async buildRules() {
        this.webpackConfig.module = this.webpackConfig.module || {};
        this.webpackConfig.module.rules = this.webpackConfig.module.rules || [];

        this.webpackConfig.module.rules.push(...webpackRules(this.mix));

        await this.mix.dispatch('loading-rules', this.webpackConfig.module.rules);
    }

    /**
     * Build the plugins array.
     */
    async buildPlugins() {
        this.webpackConfig.plugins = this.webpackConfig.plugins || [];
        this.webpackConfig.plugins.push(...webpackPlugins(this.mix));

        await this.mix.dispatch('loading-plugins', this.webpackConfig.plugins);
    }

    /**
     * Build the resolve object.
     */
    buildChunks() {
        this.webpackConfig = require('./MergeWebpackConfig')(
            this.webpackConfig,
            this.mix.chunks.config()
        );
    }
}

module.exports = WebpackConfig;
