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

/* eslint-env node */

'use strict';

// eslint-disable-next-line lint/sort-imports
const {
  transformFromAstSync: babelTransformFromAstSync,
  transformSync: babelTransformSync,
} = require('@babel/core');
const generate = require('@babel/generator').default;
const createCacheKeyFunction =
  require('@jest/create-cache-key-function').default;
const metroBabelRegister = require('metro-babel-register');
const nullthrows = require('nullthrows');

if (process.env.FBSOURCE_ENV === '1') {
  // If we're running in the Meta-internal monorepo, use the central Babel
  // registration, which registers all of the relevant source directories
  // including Metro's root.
  //
  // $FlowExpectedError[cannot-resolve-module] - Won't resolve in OSS
  require('@fb-tools/babel-register');
} else {
  // Register Babel to allow local packages to be loaded from source
  require('../scripts/build/babel-register').registerForMonorepo();
}

const transformer = require('@react-native/metro-babel-transformer');
const metroTransformPlugins = require('metro-transform-plugins');

// Files matching this pattern will be transformed with the Node JS Babel
// transformer, rather than with the React Native Babel transformer. Scripts
// intended to run through Node JS should be included here.
const nodeFiles = /[\\/]metro(?:-[^/]*)[\\/]/;

// Get Babel config from metro-babel-register, without registering a require
// hook. This is used below to configure babelTransformSync under Jest.
const {only: _, ...nodeBabelOptions} = metroBabelRegister.config([]);

// Set BUILD_EXCLUDE_BABEL_REGISTER (see ../scripts/build/babel-register.js) to
// prevent inline Babel registration in code under test, normally required when
// running from source, but not in combination with the Jest transformer.
const babelPluginPreventBabelRegister = [
  require.resolve('babel-plugin-transform-define'),
  {
    'process.env.BUILD_EXCLUDE_BABEL_REGISTER': true,
  },
];

module.exports = {
  process(src /*: string */, file /*: string */) /*: {code: string, ...} */ {
    if (nodeFiles.test(file)) {
      // node specific transforms only
      return babelTransformSync(src, {
        filename: file,
        sourceType: 'script',
        ...nodeBabelOptions,
        plugins: [
          ...(nodeBabelOptions.plugins ?? []),
          babelPluginPreventBabelRegister,
        ],
        ast: false,
      });
    }

    let {ast} = transformer.transform({
      filename: file,
      options: {
        ast: true, // needed for open source (?) https://github.com/facebook/react-native/commit/f8d6b97140cffe8d18b2558f94570c8d1b410d5c#r28647044
        dev: true,
        enableBabelRuntime: false,
        experimentalImportSupport: false,
        globalPrefix: '',
        hermesParser: true,
        hot: false,
        // $FlowFixMe[incompatible-call] TODO: Remove when `inlineRequires` has been removed from metro-babel-transformer in OSS
        inlineRequires: true,
        minify: false,
        platform: '',
        projectRoot: '',
        publicPath: '/assets',
        retainLines: true,
        sourceType: 'unambiguous', // b7 required. detects module vs script mode
      },
      src,
    });

    const babelTransformResult = babelTransformFromAstSync(ast, src, {
      ast: true,
      retainLines: true,
      plugins: [
        metroTransformPlugins.inlineRequiresPlugin,
        babelPluginPreventBabelRegister,
      ],
      sourceType: 'module',
    });
    ast = nullthrows(babelTransformResult.ast);

    return generate(
      ast,
      // $FlowFixMe[prop-missing] Error found when improving flow typing for libs
      {
        code: true,
        comments: false,
        compact: false,
        filename: file,
        retainLines: true,
        sourceFileName: file,
        sourceMaps: true,
      },
      src,
    );
  },

  // $FlowFixMe[signature-verification-failure]
  getCacheKey: createCacheKeyFunction([
    __filename,
    require.resolve('@react-native/metro-babel-transformer'),
    require.resolve('@babel/core/package.json'),
  ]),
};
