import { dirname } from 'path';

/*
 * Babel preset to provide TypeScript support and module/nomodule for Nx.
 */

export interface NxWebBabelPresetOptions {
  useBuiltIns?: boolean | string;
  decorators?: {
    decoratorsBeforeExport?: boolean;
    legacy?: boolean;
  };
  classProperties?: {
    loose?: boolean;
  };
}

module.exports = function (api: any, options: NxWebBabelPresetOptions = {}) {
  api.assertVersion(7);

  const isModern = api.caller((caller) => caller?.isModern);

  // This is set by `@nrwl/web:rollup` executor
  const isNxPackage = api.caller((caller) => caller?.isNxPackage);

  const emitDecoratorMetadata = api.caller(
    (caller) => caller?.emitDecoratorMetadata ?? true
  );

  // Determine settings  for `@babel/plugin-proposal-class-properties`,
  // so that we can sync the `loose` option with `@babel/preset-env`.
  const classProperties = options.classProperties ?? { loose: true };

  return {
    presets: [
      // Support module/nomodule pattern.
      [
        require.resolve('@babel/preset-env'),
        // For Jest tests, NODE_ENV is set as 'test' and we only want to set target as Node.
        // All other options will fail in Jest since Node does not support some ES features
        // such as import syntax.
        process.env.NODE_ENV === 'test'
          ? { targets: { node: 'current' }, loose: true }
          : {
              // Allow importing core-js in entrypoint and use browserlist to select polyfills.
              useBuiltIns: options.useBuiltIns ?? 'entry',
              corejs: 3,
              // Do not transform modules to CJS
              modules: false,
              targets: isModern ? { esmodules: 'intersect' } : undefined,
              bugfixes: true,
              // Exclude transforms that make all code slower
              exclude: ['transform-typeof-symbol'],
              // This must match the setting for `@babel/plugin-proposal-class-properties`
              loose: classProperties.loose,
            },
      ],
      [
        require.resolve('@babel/preset-typescript'),
        {
          allowDeclareFields: true,
        },
      ],
    ],
    plugins: [
      !isNxPackage
        ? [
            require.resolve('@babel/plugin-transform-runtime'),
            {
              corejs: false,
              helpers: true,
              regenerator: true,
              useESModules: isModern,
              absoluteRuntime: dirname(
                require.resolve('@babel/runtime/package.json')
              ),
            },
          ]
        : null,
      require.resolve('babel-plugin-macros'),
      emitDecoratorMetadata
        ? require.resolve('babel-plugin-transform-typescript-metadata')
        : undefined,
      // Must use legacy decorators to remain compatible with TypeScript.
      [
        require.resolve('@babel/plugin-proposal-decorators'),
        options.decorators ?? { legacy: true },
      ],
      [
        require.resolve('@babel/plugin-proposal-class-properties'),
        classProperties,
      ],
    ].filter(Boolean),
    overrides: [
      // Convert `const enum` to `enum`. The former cannot be supported by babel
      // but at least we can get it to not error out.
      {
        test: /\.tsx?$/,
        plugins: [
          [
            require.resolve('babel-plugin-const-enum'),
            {
              transform: 'removeConst',
            },
          ],
        ],
      },
    ],
  };
};
