const fs = require('node:fs');
const path = require('node:path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const WrapperPlugin = require('wrapper-webpack-plugin');

const electronRoot = path.resolve(__dirname, '../..');

class AccessDependenciesPlugin {
  apply (compiler) {
    compiler.hooks.compilation.tap('AccessDependenciesPlugin', compilation => {
      compilation.hooks.finishModules.tap('AccessDependenciesPlugin', modules => {
        const filePaths = modules.map(m => m.resource).filter(p => p).map(p => path.relative(electronRoot, p));
        console.info(JSON.stringify(filePaths));
      });
    });
  }
}

module.exports = ({
  alwaysHasNode,
  loadElectronFromAlternateTarget,
  targetDeletesNodeGlobals,
  target,
  wrapInitWithProfilingTimeout,
  wrapInitWithTryCatch
}) => {
  let entry = path.resolve(electronRoot, 'lib', target, 'init.ts');
  if (!fs.existsSync(entry)) {
    entry = path.resolve(electronRoot, 'lib', target, 'init.js');
  }

  const electronAPIFile = path.resolve(electronRoot, 'lib', loadElectronFromAlternateTarget || target, 'api', 'exports', 'electron.ts');

  return (env = {}, argv = {}) => {
    const onlyPrintingGraph = !!env.PRINT_WEBPACK_GRAPH;
    const outputFilename = argv['output-filename'] || `${target}.bundle.js`;

    const defines = {
      BUILDFLAG: onlyPrintingGraph ? '(a => a)' : ''
    };

    if (env.buildflags) {
      const flagFile = fs.readFileSync(env.buildflags, 'utf8');
      for (const line of flagFile.split(/(\r\n|\r|\n)/g)) {
        const flagMatch = line.match(/#define BUILDFLAG_INTERNAL_(.+?)\(\) \(([01])\)/);
        if (flagMatch) {
          const [, flagName, flagValue] = flagMatch;
          defines[flagName] = JSON.stringify(Boolean(parseInt(flagValue, 10)));
        }
      }
    }

    const ignoredModules = [];

    if (defines.ENABLE_VIEWS_API === 'false') {
      ignoredModules.push(
        '@electron/internal/browser/api/views/image-view.js'
      );
    }

    const plugins = [];

    if (onlyPrintingGraph) {
      plugins.push(new AccessDependenciesPlugin());
    }

    if (targetDeletesNodeGlobals) {
      plugins.push(new webpack.ProvidePlugin({
        Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
        global: ['@electron/internal/common/webpack-provider', '_global'],
        process: ['@electron/internal/common/webpack-provider', 'process']
      }));
    }

    // Webpack 5 no longer polyfills process or Buffer.
    if (!alwaysHasNode) {
      plugins.push(new webpack.ProvidePlugin({
        Buffer: ['buffer', 'Buffer'],
        process: 'process/browser'
      }));
    }

    plugins.push(new webpack.ProvidePlugin({
      Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
    }));

    plugins.push(new webpack.DefinePlugin(defines));

    if (wrapInitWithProfilingTimeout) {
      plugins.push(new WrapperPlugin({
        header: 'function ___electron_webpack_init__() {',
        footer: `
};
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
  setTimeout(___electron_webpack_init__, 0);
} else {
  ___electron_webpack_init__();
}`
      }));
    }

    if (wrapInitWithTryCatch) {
      plugins.push(new WrapperPlugin({
        header: 'try {',
        footer: `
} catch (err) {
  console.error('Electron ${outputFilename} script failed to run');
  console.error(err);
}`
      }));
    }

    return {
      mode: 'development',
      devtool: false,
      entry,
      target: alwaysHasNode ? 'node' : 'web',
      output: {
        filename: outputFilename
      },
      resolve: {
        alias: {
          '@electron/internal': path.resolve(electronRoot, 'lib'),
          electron$: electronAPIFile,
          'electron/main$': electronAPIFile,
          'electron/renderer$': electronAPIFile,
          'electron/common$': electronAPIFile,
          // Force timers to resolve to our dependency that doesn't use window.postMessage
          timers: path.resolve(electronRoot, 'node_modules', 'timers-browserify', 'main.js')
        },
        extensions: ['.ts', '.js'],
        fallback: {
          // We provide our own "timers" import above, any usage of setImmediate inside
          // one of our renderer bundles should import it from the 'timers' package
          setImmediate: false
        }
      },
      module: {
        rules: [{
          test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
          loader: 'null-loader'
        }, {
          test: /\.ts$/,
          loader: 'ts-loader',
          options: {
            configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
            transpileOnly: onlyPrintingGraph,
            ignoreDiagnostics: [
              // File '{0}' is not under 'rootDir' '{1}'.
              6059
            ]
          }
        }]
      },
      node: {
        __dirname: false,
        __filename: false
      },
      optimization: {
        minimize: env.mode === 'production',
        minimizer: [
          new TerserPlugin({
            terserOptions: {
              keep_classnames: true,
              keep_fnames: true
            }
          })
        ]
      },
      plugins
    };
  };
};
