/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2026, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////

// Import file, libraries and plugins
const path = require('path');
const webpack = require('webpack');
const sourceDir = __dirname + '/pgadmin/static';
// webpack.shim.js contains path references for resolve > alias configuration
// and other util function used in CommonsChunksPlugin.
const webpackShimConfig = require('./webpack.shim');
const PRODUCTION = process.env.NODE_ENV === 'production';
const TerserPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const extractStyle = new MiniCssExtractPlugin({
  filename: '[name].css',
  chunkFilename: '[name].css',
});
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const CopyPlugin = require('copy-webpack-plugin');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');

let isSharpAvailable = true;
try {
  const sharp = require('sharp');
  // It is possible that sharp is installed but fails on running
  sharp();
} catch {
  isSharpAvailable = false;
  console.warn('Sharp is not available, image optimization will be disabled.');
}

const envType = PRODUCTION ? 'production': 'development';
const devToolVal = PRODUCTION ? false : 'eval';
const analyzerMode = process.env.ANALYZE=='true' ? 'static' : 'disabled';

const outputPath = __dirname + '/pgadmin/static/js/generated';

// Expose libraries in app context so they need not to
// require('libname') when used in a module
const providePlugin = new webpack.ProvidePlugin({
  _: 'lodash',
  pgAdmin: 'sources/pgadmin',
  'moment': 'moment',
  'window.moment':'moment',
  process: 'process/browser',
  Buffer: ['buffer', 'Buffer'],
});

// Helps in debugging each single file, it extracts the module files
// from bundle so that they are accessible by search in Chrome's sources panel.
// Reference: https://webpack.js.org/plugins/source-map-dev-tool-plugin/#components/sidebar/sidebar.jsx
const sourceMapDevToolPlugin = new webpack.SourceMapDevToolPlugin({
  filename: '[name].js.map',
  exclude: /(vendor|codemirror|pgadmin\.js|pgadmin.theme|pgadmin.static|style\.js|popper)/,
  columns: false,
});

// can be enabled using bundle:analyze
const bundleAnalyzer = new BundleAnalyzerPlugin({
  analyzerMode: analyzerMode,
  reportFilename: 'analyze_report.html',
});

const copyFiles = new CopyPlugin({
  patterns: [
    {
      from: './pgadmin/static/img/*.png',
      to: 'img/[name][ext]',
    },
  ],
});

module.exports = [{
  mode: envType,
  devtool: devToolVal,
  stats: { children: false, builtAt: true, chunks: true, timings: true },
  // The base directory, an absolute path, for resolving entry points and loaders
  // from configuration.
  context: __dirname,
  // Specify entry points of application
  entry: {
    'app.bundle': sourceDir + '/bundle/app.js',
    'security.pages': 'security.pages',
    sqleditor: './pgadmin/tools/sqleditor/static/js/index.js',
    schema_diff: './pgadmin/tools/schema_diff/static/js/index.js',
    erd_tool: './pgadmin/tools/erd/static/js/index.js',
    psql_tool: './pgadmin/tools/psql/static/js/index.js',
    debugger: './pgadmin/tools/debugger/static/js/index.js',
    style: ['./pgadmin/static/css/style.css', './pgadmin/static/js/pgadmin.fonticon.js']
  },
  // path: The output directory for generated bundles(defined in entry)
  // Ref: https://webpack.js.org/configuration/output/#output-library
  output: {
    libraryTarget: 'amd',
    path: outputPath,
    filename: '[name].js',
    chunkFilename: '[name].chunk.js?id=[chunkhash]',
    libraryExport: 'default',
    publicPath: '',
  },
  // Templates files which contains python code needs to load dynamically
  // Such files specified in externals are loaded at first and defined in
  // the start of generated bundle within define(['libname'],fn) etc.
  externals: webpackShimConfig.externals,
  module: {
    // References:
    // Module and Rules: https://webpack.js.org/configuration/module/
    // Loaders: https://webpack.js.org/loaders/
    //
    // imports-loader: it adds dependent modules(use:imports-loader?module1)
    // at the beginning of module it is dependency.
    // It solves number of problems
    // Ref: http:/github.com/webpack-contrib/imports-loader/
    rules: [{
      test: /\.fonticon\.js/,
      use: [
        MiniCssExtractPlugin.loader,
        {
          loader: 'css-loader',
          options: {
            url: false,
          },
        },
        'webfonts-loader',
      ],
    },{
      test: /\.m?js$/,
      resolve: {
        fullySpecified: false
      },
    },{
      test: /\.tsx?$|\.ts?$|\.jsx?$/,
      exclude: [/node_modules/, /vendor/],
      use: {
        loader: 'babel-loader',
        options: {
          presets: [['@babel/preset-env', {'modules': 'commonjs', 'useBuiltIns': 'usage', 'corejs': 3}], ['@babel/preset-react', {
            'runtime': 'automatic'
          }], '@babel/preset-typescript'],
          plugins: ['@babel/plugin-transform-class-properties', '@babel/plugin-transform-object-rest-spread'],
        },
      },
    }, {
      test: /external_table.*\.js/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [['@babel/preset-env', {'modules': 'commonjs', 'useBuiltIns': 'usage', 'corejs': 3}]],
        },
      },
    }, {
      // Transforms the code in a way that it works in the webpack environment.
      // It uses imports-loader internally to load dependency. Its
      // configuration is specified in webpack.shim.js
      // Ref: https://www.npmjs.com/package/shim-loader
      test: /\.js/,
      exclude: [/external_table/],
      loader: 'shim-loader',
      options: webpackShimConfig,
      include: path.join(__dirname, '/pgadmin/browser'),
    }, {
      // imports-loader: it adds dependent modules(use:imports-loader?module1)
      // at the beginning of module it is dependency.
      // It solves number of problems
      // Ref: http:/github.com/webpack-contrib/imports-loader/
      test: require.resolve('./pgadmin/tools/sqleditor/static/js/index'),
      use: {
        loader: 'imports-loader',
        options: {
          type: 'commonjs',
          imports: [
            'pure|pgadmin.tools.user_management',
            'pure|pgadmin.browser.bgprocessmanager',
            'pure|pgadmin.node.server_group',
            'pure|pgadmin.node.server',
            'pure|pgadmin.node.database',
            'pure|pgadmin.node.role',
            'pure|pgadmin.node.cast',
            'pure|pgadmin.node.publication',
            'pure|pgadmin.node.subscription',
            'pure|pgadmin.node.tablespace',
            'pure|pgadmin.node.directory',
            'pure|pgadmin.node.resource_group',
            'pure|pgadmin.node.event_trigger',
            'pure|pgadmin.node.extension',
            'pure|pgadmin.node.language',
            'pure|pgadmin.node.foreign_data_wrapper',
            'pure|pgadmin.node.foreign_server',
            'pure|pgadmin.node.user_mapping',
            'pure|pgadmin.node.schema',
            'pure|pgadmin.node.catalog',
            'pure|pgadmin.node.foreign_table_column',
            'pure|pgadmin.node.catalog_object',
            'pure|pgadmin.node.collation',
            'pure|pgadmin.node.domain',
            'pure|pgadmin.node.domain_constraints',
            'pure|pgadmin.node.foreign_table',
            'pure|pgadmin.node.fts_configuration',
            'pure|pgadmin.node.fts_dictionary',
            'pure|pgadmin.node.fts_parser',
            'pure|pgadmin.node.fts_template',
            'pure|pgadmin.node.function',
            'pure|pgadmin.node.procedure',
            'pure|pgadmin.node.edbfunc',
            'pure|pgadmin.node.edbproc',
            'pure|pgadmin.node.edbvar',
            'pure|pgadmin.node.trigger_function',
            'pure|pgadmin.node.package',
            'pure|pgadmin.node.sequence',
            'pure|pgadmin.node.synonym',
            'pure|pgadmin.node.type',
            'pure|pgadmin.node.rule',
            'pure|pgadmin.node.index',
            'pure|pgadmin.node.row_security_policy',
            'pure|pgadmin.node.trigger',
            'pure|pgadmin.node.catalog_object_column',
            'pure|pgadmin.node.view',
            'pure|pgadmin.node.mview',
            'pure|pgadmin.node.table',
            'pure|pgadmin.node.partition',
            'pure|pgadmin.node.compound_trigger',
            'pure|pgadmin.node.aggregate',
            'pure|pgadmin.node.operator',
            'pure|pgadmin.node.dbms_job_scheduler',
            'pure|pgadmin.node.replica_node',
            'pure|pgadmin.node.pgd_replication_groups',
            'pure|pgadmin.node.pgd_replication_servers',
          ],
        },
      },
    },{
      test: require.resolve('./pgadmin/static/bundle/browser'),
      use: {
        loader: 'imports-loader',
        options: {
          type: 'commonjs',
          imports: [
            'pure|pgadmin.about',
            'pure|pgadmin.preferences',
            'pure|pgadmin.settings',
            'pure|pgadmin.tools.backup',
            'pure|pgadmin.tools.restore',
            'pure|pgadmin.tools.grant_wizard',
            'pure|pgadmin.tools.maintenance',
            'pure|pgadmin.tools.import_export',
            'pure|pgadmin.tools.import_export_servers',
            'pure|pgadmin.tools.debugger',
            'pure|pgadmin.node.pga_job',
            'pure|pgadmin.tools.schema_diff',
            'pure|pgadmin.tools.file_manager',
            'pure|pgadmin.tools.search_objects',
            'pure|pgadmin.tools.erd',
            'pure|pgadmin.tools.psql',
            'pure|pgadmin.tools.sqleditor',
            'pure|pgadmin.misc.cloud',
            'pure|pgadmin.browser.ai_tools',
          ],
        },
      },
    },
    {
      test: /\.svg$/,
      oneOf: [
        {
          issuer: /\.[jt]sx?$/,
          resourceQuery: /svgr/,
          use: ['@svgr/webpack'],
        },
        {
          type: 'asset',
          parser: {
            dataUrlCondition: {
              maxSize: 4 * 1024, // 4kb
            }
          }
        },
      ],
    },{
      test: /\.(jpe?g|png|gif)$/i,
      type: 'asset',
      parser: {
        dataUrlCondition: {
          maxSize: 4 * 1024, // 4kb
        },
      },
      generator: {
        filename: 'img/[name][ext]',
      },
      exclude: /vendor/,
    },{
      test: /\.(eot|ttf|woff|woff2)$/,
      type: 'asset/resource',
      generator: {
        filename: 'fonts/[name][ext]',
      },
      include: [
        /node_modules/,
        path.join(sourceDir, '/css/'),
        path.join(sourceDir, '/fonts/'),
      ],
      exclude: /vendor/,
    },
    {
      test: /\.css$/,
      use: [
        {
          loader: MiniCssExtractPlugin.loader,
          options: {
            publicPath: '',
          },
        },
        'css-loader',
        {
          loader: 'postcss-loader',
          options: {
            postcssOptions: () =>({
              plugins: [
                require('autoprefixer')(),
              ],
            }),
          },
        },
      ],
    }],
    // Prevent module from parsing through webpack, helps in reducing build time
    noParse: [/moment.js/],
  },
  resolve: {
    alias: webpackShimConfig.resolveAlias,
    modules: ['node_modules', '.'],
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    unsafeCache: true,
    fallback: {
      'fs': false
    },
  },
  // Watch mode Configuration: After initial build, webpack will watch for
  // changes in files and compiles only files which are changed,
  // if watch is set to True
  // Reference: https://webpack.js.org/configuration/watch/#components/sidebar/sidebar.jsx
  watchOptions: {
    aggregateTimeout: 300,
    poll: 1000,
    ignored: /node_modules/,
  },
  optimization: {
    minimizer: PRODUCTION ? [
      new TerserPlugin({
        parallel: false,
        extractComments: true,
        terserOptions: {
          compress: true,
        },
      }),
    ].concat(isSharpAvailable ? [
      new ImageMinimizerPlugin({
        test: /\.(jpe?g|png|gif)$/i,
        minimizer: {
          implementation: ImageMinimizerPlugin.sharpMinify,
          options: {
            encodeOptions: {
              jpeg: {
                quality: 100,
              },
              webp: {
                lossless: true,
              },
              avif: {
                lossless: true,
              },
              png: {},
              gif: {},
            },
          },
        },
      }),
    ] : []) : [],
    splitChunks: {
      cacheGroups: {
        vendor_sqleditor: {
          name: 'vendor_sqleditor',
          filename: 'vendor.sqleditor.js',
          chunks: 'all',
          reuseExistingChunk: true,
          priority: 9,
          minChunks: 2,
          enforce: true,
          test(module) {
            return webpackShimConfig.matchModules(module, ['jsoneditor', 'leaflet']);
          },
        },
        vendor_react: {
          name: 'vendor_react',
          filename: 'vendor.react.js',
          chunks: 'all',
          reuseExistingChunk: true,
          priority: 8,
          minChunks: 2,
          enforce: true,
          test(module) {
            return webpackShimConfig.matchModules(module, ['react', 'react-dom']);
          },
        },
        vendor_main: {
          name: 'vendor_main',
          filename: 'vendor.main.js',
          chunks: 'all',
          reuseExistingChunk: true,
          priority: 7,
          minChunks: 2,
          enforce: true,
          test(module) {
            return webpackShimConfig.matchModules(module, ['codemirror', 'rc-', '@mui']);
          },
        },
        vendor_others: {
          name: 'vendor_others',
          filename: 'vendor.others.js',
          chunks: 'all',
          reuseExistingChunk: true,
          priority: 6,
          minChunks: 2,
          enforce: true,
          test(module) {
            return webpackShimConfig.isExternal(module);
          },
        },
        secondary: {
          name: 'pgadmin_commons',
          filename: 'pgadmin_commons.js',
          chunks: 'all',
          priority: 5,
          minChunks: 2,
          enforce: true,
          test(module) {
            return webpackShimConfig.isPgAdminLib(module);
          },
        },
        browser_nodes: {
          name: 'browser_nodes',
          filename: 'browser_nodes.js',
          chunks: 'all',
          priority: 4,
          minChunks: 2,
          enforce: true,
          test(module) {
            return webpackShimConfig.isBrowserNode(module);
          },
        },
      },
    },
  },
  // Define list of Plugins used in Production or development mode
  // Ref:https://webpack.js.org/concepts/plugins/#components/sidebar/sidebar.jsx
  plugins: PRODUCTION ? [
    extractStyle,
    providePlugin,
    sourceMapDevToolPlugin,
    bundleAnalyzer,
    copyFiles,
  ]: [
    extractStyle,
    providePlugin,
    sourceMapDevToolPlugin,
    copyFiles,
  ],
}];
