/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

const LogPlugin = require('@docusaurus/core/lib/webpack/plugins/LogPlugin');
const {compile} = require('@docusaurus/core/lib/webpack/utils');
const path = require('path');
const webpack = require('webpack');
const {injectManifest} = require('workbox-build');
const {PluginOptionSchema} = require('./pluginOptionSchema');
const Terser = require('terser-webpack-plugin');

const isProd = process.env.NODE_ENV === 'production';

function getSWBabelLoader() {
  return {
    loader: 'babel-loader',
    options: {
      babelrc: false,
      configFile: false,
      presets: [
        [
          require.resolve('@babel/preset-env'),
          {
            useBuiltIns: 'entry',
            corejs: '3',
            // See https://twitter.com/jeffposnick/status/1280223070876315649
            targets: 'chrome >= 56',
          },
        ],
      ],
      plugins: [
        require.resolve('@babel/plugin-proposal-object-rest-spread'),
        require.resolve('@babel/plugin-proposal-optional-chaining'),
        require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
      ],
    },
  };
}

function plugin(context, options) {
  const {outDir, baseUrl} = context;
  const {
    debug,
    offlineModeActivationStrategies,
    injectManifestConfig,
    reloadPopup,
    pwaHead,
    swCustom,
    swRegister,
  } = options;

  return {
    name: 'docusaurus-plugin-pwa',

    getThemePath() {
      return path.resolve(__dirname, './theme');
    },

    getClientModules() {
      return isProd ? [swRegister] : [];
    },

    configureWebpack(config) {
      if (!isProd) {
        return {};
      }

      return {
        plugins: [
          new webpack.EnvironmentPlugin({
            PWA_DEBUG: debug,
            PWA_SERVICE_WORKER_URL: path.posix.resolve(
              `${config.output.publicPath || '/'}`,
              'sw.js',
            ),
            PWA_OFFLINE_MODE_ACTIVATION_STRATEGIES: offlineModeActivationStrategies,
            PWA_RELOAD_POPUP: reloadPopup,
          }),
        ],
      };
    },

    injectHtmlTags() {
      const headTags = [];
      if (isProd && pwaHead) {
        pwaHead.forEach(({tagName, ...attributes}) =>
          headTags.push({
            tagName,
            attributes,
          }),
        );
      }
      return {headTags};
    },

    async postBuild(props) {
      if (!isProd) {
        return;
      }

      const swSourceFileTest = /\.m?js$/;

      const swWebpackConfig = {
        entry: path.resolve(__dirname, 'sw.js'),
        output: {
          path: outDir,
          filename: 'sw.js',
          publicPath: baseUrl,
        },
        target: 'webworker',
        mode: debug ? 'development' : 'production',
        devtool: debug ? 'source-map' : false,
        optimization: {
          splitChunks: false,
          minimize: !debug,
          // see https://developers.google.com/web/tools/workbox/guides/using-bundlers#webpack
          minimizer: [
            !debug &&
              new Terser({
                test: swSourceFileTest,
              }),
          ].filter(Boolean),
        },
        plugins: [
          new webpack.EnvironmentPlugin({
            PWA_SW_CUSTOM: swCustom,
          }),
          new LogPlugin({
            name: 'Service Worker',
            color: 'red',
          }),
        ],
        module: {
          rules: [
            {
              test: swSourceFileTest,
              exclude: /(node_modules)/,
              use: getSWBabelLoader(),
            },
          ],
        },
      };

      await compile([swWebpackConfig]);

      const swDest = path.resolve(props.outDir, 'sw.js');

      await injectManifest({
        ...injectManifestConfig,
        globPatterns: [
          '**/*.{js,json,css,html}',
          '**/*.{png,jpg,jpeg,gif,svg,ico}',
          '**/*.{woff,woff2,eot,ttf,otf}',
          ...(injectManifest.globPatterns || []),
        ],
        // those attributes are not overrideable
        swDest,
        swSrc: swDest,
        globDirectory: props.outDir,
      });
    },
  };
}

module.exports = plugin;

plugin.validateOptions = function validateOptions({validate, options}) {
  return validate(PluginOptionSchema, options);
};
