/**
 * 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.
 *
 * @flow
 */

const acorn = require('acorn-loose');

const url = require('url');

const Module = require('module');

module.exports = function register() {
  const Server: any = require('react-server-dom-webpack/server');
  const registerServerReference = Server.registerServerReference;
  const createClientModuleProxy = Server.createClientModuleProxy;

  // $FlowFixMe[prop-missing] found when upgrading Flow
  const originalCompile = Module.prototype._compile;

  // $FlowFixMe[prop-missing] found when upgrading Flow
  Module.prototype._compile = function (
    this: any,
    content: string,
    filename: string,
  ): void {
    // Do a quick check for the exact string. If it doesn't exist, don't
    // bother parsing.
    if (
      content.indexOf('use client') === -1 &&
      content.indexOf('use server') === -1
    ) {
      return originalCompile.apply(this, arguments);
    }

    let body;
    try {
      body = acorn.parse(content, {
        ecmaVersion: '2024',
        sourceType: 'source',
      }).body;
    } catch (x) {
      console['error']('Error parsing %s %s', url, x.message);
      return originalCompile.apply(this, arguments);
    }

    let useClient = false;
    let useServer = false;
    for (let i = 0; i < body.length; i++) {
      const node = body[i];
      if (node.type !== 'ExpressionStatement' || !node.directive) {
        break;
      }
      if (node.directive === 'use client') {
        useClient = true;
      }
      if (node.directive === 'use server') {
        useServer = true;
      }
    }

    if (!useClient && !useServer) {
      return originalCompile.apply(this, arguments);
    }

    if (useClient && useServer) {
      throw new Error(
        'Cannot have both "use client" and "use server" directives in the same file.',
      );
    }

    if (useClient) {
      const moduleId: string = (url.pathToFileURL(filename).href: any);
      this.exports = createClientModuleProxy(moduleId);
    }

    if (useServer) {
      originalCompile.apply(this, arguments);

      const moduleId: string = (url.pathToFileURL(filename).href: any);

      const exports = this.exports;

      // This module is imported server to server, but opts in to exposing functions by
      // reference. If there are any functions in the export.
      if (typeof exports === 'function') {
        // The module exports a function directly,
        registerServerReference(
          (exports: any),
          moduleId,
          // Represents the whole Module object instead of a particular import.
          null,
        );
      } else {
        const keys = Object.keys(exports);
        for (let i = 0; i < keys.length; i++) {
          const key = keys[i];
          const value = exports[keys[i]];
          if (typeof value === 'function') {
            registerServerReference((value: any), moduleId, key);
          }
        }
      }
    }
  };
};
