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

package com.facebook.react.uimanager;

import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_END;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_START;
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
import static com.facebook.react.uimanager.common.UIManagerType.LEGACY;

import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UIManagerListener;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.uimanager.internal.LegacyArchitectureShadowNodeLogger;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Native module to allow JS to create and update native Views.
 *
 * <p>
 *
 * <h2>== Transactional Requirement ==</h2>
 *
 * A requirement of this class is to make sure that transactional UI updates occur all at once,
 * meaning that no intermediate state is ever rendered to the screen. For example, if a JS
 * application update changes the background of View A to blue and the width of View B to 100, both
 * need to appear at once. Practically, this means that all UI update code related to a single
 * transaction must be executed as a single code block on the UI thread. Executing as multiple code
 * blocks could allow the platform UI system to interrupt and render a partial UI state.
 *
 * <p>To facilitate this, this module enqueues operations that are then applied to native view
 * hierarchy through {@link NativeViewHierarchyManager} at the end of each transaction.
 *
 * <p>
 *
 * <h2>== CSSNodes ==</h2>
 *
 * In order to allow layout and measurement to occur on a non-UI thread, this module also operates
 * on intermediate CSSNodeDEPRECATED objects that correspond to a native view. These
 * CSSNodeDEPRECATED are able to calculate layout according to their styling rules, and then the
 * resulting x/y/width/height of that layout is scheduled as an operation that will be applied to
 * native view hierarchy at the end of current batch. TODO(5241856): Investigate memory usage of
 * creating many small objects in UIManageModule and consider implementing a pool TODO(5483063):
 * Don't dispatch the view hierarchy at the end of a batch if no UI changes occurred
 */
@ReactModule(name = UIManagerModule.NAME)
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
    since = "This class is part of Legacy Architecture and will be removed in a future release")
public class UIManagerModule extends ReactContextBaseJavaModule
    implements LifecycleEventListener, UIManager {
  static {
    LegacyArchitectureLogger.assertLegacyArchitecture(
        "UIManagerModule", LegacyArchitectureLogLevel.ERROR);
  }

  public static final String TAG = UIManagerModule.class.getSimpleName();

  /** Resolves a name coming from native side to a name of the event that is exposed to JS. */
  public interface CustomEventNamesResolver {
    /** Returns custom event name by the provided event name. */
    @Nullable
    String resolveCustomEventName(String eventName);
  }

  public static final String NAME = "UIManager";

  private static final boolean DEBUG =
      PrinterHolder.getPrinter().shouldDisplayLogMessage(ReactDebugOverlayTags.UI_MANAGER);

  private final Map<String, Object> mModuleConstants;
  private final Map<String, Object> mCustomDirectEvents;
  private final ViewManagerRegistry mViewManagerRegistry;
  private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback();
  private final CopyOnWriteArrayList<UIManagerListener> mUIManagerListeners =
      new CopyOnWriteArrayList<>();

  public UIManagerModule(
      ReactApplicationContext reactContext,
      ViewManagerResolver viewManagerResolver,
      int minTimeLeftInFrameForNonBatchedOperationMs) {
    super(reactContext);
    DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
    mModuleConstants = createConstants(viewManagerResolver);
    mCustomDirectEvents = UIManagerModuleConstants.directEventTypeConstants;
    mViewManagerRegistry = new ViewManagerRegistry(viewManagerResolver);
    reactContext.addLifecycleEventListener(this);
  }

  public UIManagerModule(
      ReactApplicationContext reactContext,
      List<ViewManager> viewManagersList,
      int minTimeLeftInFrameForNonBatchedOperationMs) {
    super(reactContext);
    DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
    mCustomDirectEvents = MapBuilder.newHashMap();
    mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents);
    mViewManagerRegistry = new ViewManagerRegistry(viewManagersList);
    if (ReactBuildConfig.DEBUG) {
      for (ViewManager<?, ?> viewManager : viewManagersList) {
        LegacyArchitectureShadowNodeLogger.assertUnsupportedViewManager(
            reactContext, viewManager.getShadowNodeClass(), viewManager.getClass().getSimpleName());
      }
    }

    reactContext.addLifecycleEventListener(this);
  }

  @Override
  public @NonNull String getName() {
    return NAME;
  }

  @Override
  public Map<String, Object> getConstants() {
    return mModuleConstants;
  }

  @Override
  public void initialize() {
    getReactApplicationContext().registerComponentCallbacks(mMemoryTrimCallback);
    getReactApplicationContext().registerComponentCallbacks(mViewManagerRegistry);
  }

  @Override
  public void onHostResume() {}

  @Override
  public void onHostPause() {}

  @Override
  public void onHostDestroy() {}

  @Override
  public void invalidate() {
    super.invalidate();

    ReactApplicationContext reactApplicationContext = getReactApplicationContext();
    reactApplicationContext.unregisterComponentCallbacks(mMemoryTrimCallback);
    reactApplicationContext.unregisterComponentCallbacks(mViewManagerRegistry);
    ViewManagerPropertyUpdater.clear();
  }

  @Override
  public void markActiveTouchForTag(int surfaceId, int reactTag) {
    // Not implemented for Paper.
  }

  @Override
  public void sweepActiveTouchForTag(int surfaceId, int reactTag) {
    // Not implemented for Paper.
  }

  /**
   * This method is intended to reuse the {@link ViewManagerRegistry} with FabricUIManager. Do not
   * use this method as this will be removed in the near future.
   */
  @Deprecated
  public ViewManagerRegistry getViewManagerRegistry_DO_NOT_USE() {
    return mViewManagerRegistry;
  }

  /**
   * @deprecated This method is a stub retained for backward compatibility with third-party
   *     libraries. It always returns null. UIImplementation is part of the Legacy Architecture and
   *     will be removed in a future release.
   */
  @Deprecated
  public UIImplementation getUIImplementation() {
    return new UIImplementation(null, null, null, 0);
  }

  private static Map<String, Object> createConstants(ViewManagerResolver viewManagerResolver) {
    ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START);
    SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "CreateUIManagerConstants")
        .arg("Lazy", true)
        .flush();
    try {
      return UIManagerModuleConstantsHelper.internal_createConstants(viewManagerResolver);
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT);
      ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END);
    }
  }

  public static Map<String, Object> createConstants(
      List<ViewManager> viewManagers,
      @Nullable Map<String, Object> customBubblingEvents,
      @Nullable Map<String, Object> customDirectEvents) {
    ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START);
    SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT, "CreateUIManagerConstants")
        .arg("Lazy", false)
        .flush();
    try {
      return UIManagerModuleConstantsHelper.internal_createConstants(
          viewManagers, customBubblingEvents, customDirectEvents);
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT);
      ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END);
    }
  }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public @Nullable WritableMap getConstantsForViewManager(String viewManagerName) {
    ViewManager targetView = mViewManagerRegistry.getViewManagerIfExists(viewManagerName);
    if (targetView == null) {
      return null;
    }

    return getConstantsForViewManager(targetView, mCustomDirectEvents);
  }

  public static @Nullable WritableMap getConstantsForViewManager(
      ViewManager viewManager, Map<String, Object> customDirectEvents) {
    SystraceMessage.beginSection(
            Systrace.TRACE_TAG_REACT, "UIManagerModule.getConstantsForViewManager")
        .arg("ViewManager", viewManager.getName())
        .arg("Lazy", true)
        .flush();
    try {
      Map<String, Object> viewManagerConstants =
          UIManagerModuleConstantsHelper.internal_createConstantsForViewManager(
              viewManager, null, null, null, customDirectEvents);
      if (viewManagerConstants != null) {
        return Arguments.makeNativeMap(viewManagerConstants);
      }
      return null;
    } finally {
      SystraceMessage.endSection(Systrace.TRACE_TAG_REACT).flush();
    }
  }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public WritableMap getDefaultEventTypes() {
    return Arguments.makeNativeMap(UIManagerModuleConstantsHelper.getDefaultExportableEventTypes());
  }

  /** Resolves Direct Event name exposed to JS from the one known to the Native side. */
  @Deprecated
  public CustomEventNamesResolver getDirectEventNamesResolver() {
    return new CustomEventNamesResolver() {
      @Override
      public @Nullable String resolveCustomEventName(@Nullable String eventName) {
        return resolveCustomDirectEventName(eventName);
      }
    };
  }

  @Override
  @Deprecated
  @Nullable
  public String resolveCustomDirectEventName(@Nullable String eventName) {
    if (eventName != null) {
      Map<String, String> customEventType =
          (Map<String, String>) mCustomDirectEvents.get(eventName);
      if (customEventType != null) {
        return customEventType.get("registrationName");
      }
    }
    return eventName;
  }

  @Override
  public void profileNextBatch() {}

  @Override
  public Map<String, Long> getPerformanceCounters() {
    return new java.util.HashMap<>();
  }

  public <T extends View> int addRootView(final T rootView) {
    return addRootView(rootView, null);
  }

  /**
   * Used by native animated module to bypass the process of updating the values through the shadow
   * view hierarchy. This method will directly update native views, which means that updates for
   * layout-related propertied won't be handled properly. Make sure you know what you're doing
   * before calling this method :)
   */
  @Override
  public void synchronouslyUpdateViewOnUIThread(int tag, ReadableMap props) {}

  /**
   * Registers a new root view. JS can use the returned tag with manageChildren to add/remove
   * children to this view.
   *
   * <p>Calling addRootView through UIManagerModule calls addRootView in the non-Fabric renderer,
   * always. This is deprecated in favor of calling startSurface in Fabric, which must be done
   * directly through the FabricUIManager.
   *
   * <p>Note that this must be called after getWidth()/getHeight() actually return something. See
   * CatalystApplicationFragment as an example.
   *
   * <p>TODO(6242243): Make addRootView thread safe NB: this method is horribly not-thread-safe.
   */
  @Override
  public <T extends View> int addRootView(final T rootView, WritableMap initialProps) {
    Systrace.beginSection(Systrace.TRACE_TAG_REACT, "UIManagerModule.addRootView");
    final int tag = ReactRootViewTagGenerator.getNextRootViewTag();
    Systrace.endSection(Systrace.TRACE_TAG_REACT);
    return tag;
  }

  @Override
  public <T extends View> int startSurface(
      final T rootView,
      final String moduleName,
      final WritableMap initialProps,
      int widthMeasureSpec,
      int heightMeasureSpec) {
    throw new UnsupportedOperationException();
  }

  @Override
  public void stopSurface(final int surfaceId) {
    throw new UnsupportedOperationException();
  }

  /** Unregisters a new root view. */
  @ReactMethod
  public void removeRootView(int rootViewTag) {}

  public void updateNodeSize(int nodeViewTag, int newWidth, int newHeight) {}

  public void updateInsetsPadding(int nodeViewTag, int top, int left, int bottom, int right) {}

  /**
   * Sets local data for a shadow node corresponded with given tag. In some cases we need a way to
   * specify some environmental data to shadow node to improve layout (or do something similar), so
   * {@code localData} serves these needs. For example, any stateful embedded native views may
   * benefit from this. Have in mind that this data is not supposed to interfere with the state of
   * the shadow view. Please respect one-directional data flow of React.
   */
  public void setViewLocalData(final int tag, final Object data) {}

  @ReactMethod
  public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
    if (DEBUG) {
      String message =
          "(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props;
      FLog.d(ReactConstants.TAG, message);
      PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
    }
  }

  @ReactMethod
  public void updateView(final int tag, final String className, final ReadableMap props) {
    if (DEBUG) {
      String message =
          "(UIManager.updateView) tag: " + tag + ", class: " + className + ", props: " + props;
      FLog.d(ReactConstants.TAG, message);
      PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
    }
  }

  /**
   * Interface for adding/removing/moving views within a parent view from JS.
   *
   * @param viewTag the view tag of the parent view
   * @param moveFrom a list of indices in the parent view to move views from
   * @param moveTo parallel to moveFrom, a list of indices in the parent view to move views to
   * @param addChildTags a list of tags of views to add to the parent
   * @param addAtIndices parallel to addChildTags, a list of indices to insert those children at
   * @param removeFrom a list of indices of views to permanently remove. The memory for the
   *     corresponding views and data structures should be reclaimed.
   */
  @ReactMethod
  public void manageChildren(
      int viewTag,
      @Nullable ReadableArray moveFrom,
      @Nullable ReadableArray moveTo,
      @Nullable ReadableArray addChildTags,
      @Nullable ReadableArray addAtIndices,
      @Nullable ReadableArray removeFrom) {
    if (DEBUG) {
      String message =
          "(UIManager.manageChildren) tag: "
              + viewTag
              + ", moveFrom: "
              + moveFrom
              + ", moveTo: "
              + moveTo
              + ", addTags: "
              + addChildTags
              + ", atIndices: "
              + addAtIndices
              + ", removeFrom: "
              + removeFrom;
      FLog.d(ReactConstants.TAG, message);
      PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
    }
  }

  /**
   * Interface for fast tracking the initial adding of views. Children view tags are assumed to be
   * in order
   *
   * @param viewTag the view tag of the parent view
   * @param childrenTags An array of tags to add to the parent in order
   */
  @ReactMethod
  public void setChildren(int viewTag, ReadableArray childrenTags) {
    if (DEBUG) {
      String message = "(UIManager.setChildren) tag: " + viewTag + ", children: " + childrenTags;
      FLog.d(ReactConstants.TAG, message);
      PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
    }
  }

  /**
   * Determines the location on screen, width, and height of the given view and returns the values
   * via an async callback.
   */
  @ReactMethod
  public void measure(int reactTag, Callback callback) {}

  /**
   * Determines the location on screen, width, and height of the given view relative to the device
   * screen and returns the values via an async callback. This is the absolute position including
   * things like the status bar
   */
  @ReactMethod
  public void measureInWindow(int reactTag, Callback callback) {}

  /**
   * Measures the view specified by tag relative to the given ancestorTag. This means that the
   * returned x, y are relative to the origin x, y of the ancestor view. Results are stored in the
   * given outputBuffer. We allow ancestor view and measured view to be the same, in which case the
   * position always will be (0, 0) and method will only measure the view dimensions.
   *
   * <p>NB: Unlike {@link #measure}, this will measure relative to the view layout, not the visible
   * window which can cause unexpected results when measuring relative to things like ScrollViews
   * that can have offset content on the screen.
   */
  @ReactMethod
  public void measureLayout(
      int tag, int ancestorTag, Callback errorCallback, Callback successCallback) {}

  /**
   * Find the touch target child native view in the supplied root view hierarchy, given a react
   * target location.
   *
   * <p>This method is currently used only by Element Inspector DevTool.
   *
   * @param reactTag the tag of the root view to traverse
   * @param point an array containing both X and Y target location
   * @param callback will be called if with the identified child view react ID, and measurement
   *     info. If no view was found, callback will be invoked with no data.
   */
  @ReactMethod
  public void findSubviewIn(
      final int reactTag, final ReadableArray point, final Callback callback) {}

  /**
   * Check if the first shadow node is the descendant of the second shadow node
   *
   * @deprecated this method will not be available in FabricUIManager class.
   */
  @ReactMethod
  @Deprecated
  public void viewIsDescendantOf(
      final int reactTag, final int ancestorReactTag, final Callback callback) {}

  @ReactMethod
  public void setJSResponder(int reactTag, boolean blockNativeResponder) {}

  @ReactMethod
  public void clearJSResponder() {}

  @ReactMethod
  public void dispatchViewManagerCommand(
      int reactTag, Dynamic commandId, @Nullable ReadableArray commandArgs) {
    // Fabric dispatchCommands should go through the JSI API - this will crash in Fabric.
    @Nullable
    UIManager uiManager =
        UIManagerHelper.getUIManager(
            getReactApplicationContext(), ViewUtil.getUIManagerType(reactTag));
    if (uiManager == null) {
      return;
    }

    if (commandId.getType() == ReadableType.Number) {
      uiManager.dispatchCommand(reactTag, commandId.asInt(), commandArgs);
    } else if (commandId.getType() == ReadableType.String) {
      uiManager.dispatchCommand(reactTag, commandId.asString(), commandArgs);
    }
  }

  /** Deprecated, use {@link #dispatchCommand(int, String, ReadableArray)} instead. */
  @Deprecated
  @Override
  public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray commandArgs) {}

  @Override
  public void dispatchCommand(
      int reactTag, String commandId, @Nullable ReadableArray commandArgs) {}

  /**
   * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled
   * explicitly in order to avoid regression in existing application written for iOS using this API.
   *
   * <p>Warning : This method will be removed in future version of React Native, and layout
   * animation will be enabled by default, so always check for its existence before invoking it.
   *
   * <p>TODO(9139831) : remove this method once layout animation is fully stable.
   *
   * @param enabled whether layout animation is enabled or not
   */
  @ReactMethod
  public void setLayoutAnimationEnabledExperimental(boolean enabled) {}

  /**
   * Configure an animation to be used for the native layout changes, and native views creation. The
   * animation will only apply during the current batch operations.
   *
   * <p>TODO(7728153) : animating view deletion is currently not supported.
   *
   * @param config the configuration of the animation for view addition/removal/update.
   * @param success will be called when the animation completes, or when the animation get
   *     interrupted. In this case, callback parameter will be false.
   * @param error will be called if there was an error processing the animation
   */
  @ReactMethod
  public void configureNextLayoutAnimation(ReadableMap config, Callback success, Callback error) {}

  @Override
  public EventDispatcher getEventDispatcher() {
    return null;
  }

  @Override
  @ReactMethod
  public void sendAccessibilityEvent(int tag, int eventType) {
    int uiManagerType = ViewUtil.getUIManagerType(tag);
    if (uiManagerType == FABRIC) {
      // TODO: T65793557 Refactor sendAccessibilityEvent to use ViewCommands
      UIManager fabricUIManager =
          UIManagerHelper.getUIManager(getReactApplicationContext(), uiManagerType);
      if (fabricUIManager != null) {
        fabricUIManager.sendAccessibilityEvent(tag, eventType);
      }
    }
  }

  /**
   * Schedule a block to be executed on the UI thread. Useful if you need to execute view logic
   * after all currently queued view updates have completed.
   *
   * @param block that contains UI logic you want to execute.
   *     <p>Usage Example:
   *     <p>UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
   *     uiManager.addUIBlock(new UIBlock() { public void execute (NativeViewHierarchyManager nvhm)
   *     { View view = nvhm.resolveView(tag); // ...execute your code on View (e.g. snapshot the
   *     view) } });
   */
  public void addUIBlock(UIBlock block) {}

  /**
   * Schedule a block to be executed on the UI thread. Useful if you need to execute view logic
   * before all currently queued view updates have completed.
   *
   * @param block that contains UI logic you want to execute.
   */
  public void prependUIBlock(UIBlock block) {}

  @Override
  public void addUIManagerEventListener(UIManagerListener listener) {
    mUIManagerListeners.add(listener);
  }

  @Override
  public void removeUIManagerEventListener(UIManagerListener listener) {
    mUIManagerListeners.remove(listener);
  }

  /**
   * Given a reactTag from a component, find its root node tag, if possible. Otherwise, this will
   * return 0. If the reactTag belongs to a root node, this will return the same reactTag.
   *
   * @deprecated this method is not going to be supported in the near future, use {@link
   *     ViewUtil#isRootTag(int)} to verify if a react Tag is a root or not
   *     <p>TODO: T63569137 Delete the method UIManagerModule.resolveRootTagFromReactTag
   * @param reactTag the component tag
   * @return the rootTag
   */
  @Deprecated
  public int resolveRootTagFromReactTag(int reactTag) {
    return ViewUtil.isRootTag(reactTag) ? reactTag : 0;
  }

  /** Dirties the node associated with the given react tag */
  public void invalidateNodeLayout(int tag) {}

  /**
   * Updates the styles of the {@link ReactShadowNode} based on the Measure specs received by
   * parameters. offsetX and offsetY aren't used in non-Fabric, so they're ignored here.
   */
  @Override
  public void updateRootLayoutSpecs(
      final int rootViewTag,
      final int widthMeasureSpec,
      final int heightMeasureSpec,
      int offsetX,
      int offsetY) {}

  /** Listener that drops the CSSNode pool on low memory when the app is backgrounded. */
  private static class MemoryTrimCallback implements ComponentCallbacks2 {

    @Override
    public void onTrimMemory(int level) {}

    @Override
    public void onConfigurationChanged(Configuration newConfig) {}

    @Override
    public void onLowMemory() {}
  }

  @Override
  public @Nullable View resolveView(int tag) {
    UiThreadUtil.assertOnUiThread();
    return null;
  }

  @Override
  public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap event) {
    receiveEvent(-1, reactTag, eventName, event);
  }

  @Override
  public void receiveEvent(
      int surfaceId, int reactTag, String eventName, @Nullable WritableMap event) {
    assert ViewUtil.getUIManagerType(reactTag) == LEGACY;
    getReactApplicationContext()
        .getJSModule(RCTEventEmitter.class)
        .receiveEvent(reactTag, eventName, event);
  }
}
