const webpack = require('webpack');
const path = require('path');
const fs = require('fs');
const _ = require('lodash');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const TerserWebpackPlugin = require('terser-webpack-plugin');

const externalModules = [
	'async_hooks',
	'sqlite3',
	'mysql2',
	'pg',
	'mariasql',
	'mssql',
	'mysql',
	'strong-oracle',
	'oracle',
	'oracledb',
	'pg-query-stream',
	'tedious',
	'better-sqlite3',
	/mssql\/.*/,
	'osx-temperature-sensor',
	'@balena/systemd',
];

let requiredModules = [];
let maybeOptionalModules = [];
const lookForOptionalDeps = function (sourceDir) {
	// We iterate over the node modules and mark all optional dependencies as external
	const dirs = fs.readdirSync(sourceDir);
	for (const dir of dirs) {
		let packageJson = {};
		const internalNodeModules = path.join(sourceDir, dir, 'node_modules');
		if (fs.existsSync(internalNodeModules)) {
			lookForOptionalDeps(internalNodeModules);
		}
		try {
			packageJson = JSON.parse(
				fs.readFileSync(path.join(sourceDir, dir, '/package.json'), 'utf8'),
			);
		} catch {
			continue;
		}
		if (packageJson.optionalDependencies != null) {
			maybeOptionalModules = maybeOptionalModules.concat(
				_.keys(packageJson.optionalDependencies),
			);
		}
		if (packageJson.dependencies != null) {
			requiredModules = requiredModules.concat(
				_.keys(packageJson.dependencies),
			);
		}
	}
};

lookForOptionalDeps('./node_modules');
externalModules.push(
	new RegExp(
		'^(' +
			_.reject(maybeOptionalModules, requiredModules)
				.map(_.escapeRegExp)
				.join('|') +
			')(/.*)?$',
	),
);

console.log('Using the following dependencies as external:', externalModules);

module.exports = function (env) {
	return {
		mode: env == null || !env.noOptimize ? 'production' : 'development',
		entry: './src/app.ts',
		output: {
			filename: 'app.js',
			path: path.resolve(__dirname, 'dist'),
		},
		resolve: {
			extensions: ['.js', '.ts', '.json'],
			alias: {
				// Use the es2018 build instead of the default es2015 build
				'pinejs-client-core': 'pinejs-client-core/es2018',
			},
		},
		target: 'node',
		node: {
			__dirname: false,
		},
		optimization: {
			minimize: true,
			minimizer: [
				new TerserWebpackPlugin({
					terserOptions: {
						mangle: false,
						keep_classnames: true,
					},
				}),
			],
		},
		module: {
			rules: [
				{
					include: [
						new RegExp(
							_.escapeRegExp(
								// this is the path as of knex@2.5.1
								path.join('knex', 'lib', 'migrations', 'common'),
							),
						),
					],
					use: require.resolve('./build-utils/hardcode-migrations'),
				},
				{
					test: new RegExp(
						_.escapeRegExp(path.join('JSONStream', 'index.js')) + '$',
					),
					use: require.resolve('./build-utils/fix-jsonstream'),
				},
				{
					test: /\.ts$|\.js$/,
					exclude: /node_modules/,
					use: [
						{
							loader: 'ts-loader',
							options: {
								transpileOnly: true,
								configFile: 'tsconfig.release.json',
							},
						},
					],
				},
				{
					test: /\.node$/,
					loader: 'node-loader',
				},
			],
		},
		externals: (_context, request, callback) => {
			for (const m of externalModules) {
				if (
					(typeof m === 'string' && m === request) ||
					(m instanceof RegExp && m.test(request))
				) {
					return callback(null, 'commonjs ' + request);
				} else if (typeof m !== 'string' && !(m instanceof RegExp)) {
					throw new Error('Invalid entry in external modules: ' + m);
				}
			}
			return callback();
		},
		plugins: [
			new ForkTsCheckerWebpackPlugin({
				async: false,
			}),
			new CopyWebpackPlugin({
				patterns: [
					{
						from: './build/migrations',
						to: 'migrations',
					},
				],
			}),
			new webpack.ContextReplacementPlugin(
				/\.\/migrations/,
				path.resolve(__dirname, 'build/migrations'),
			),
		],
	};
};