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

'use strict';

global.IS_REACT_ACT_ENVIRONMENT = true;
// Suppress the `react-test-renderer` warnings until New Architecture and legacy
// mode are no longer supported by React Native.
global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true;

const MockNativeMethods = jest.requireActual('./MockNativeMethods');
const mockComponent = jest.requireActual('./mockComponent');

jest.requireActual('@react-native/js-polyfills/error-guard');

Object.defineProperties(global, {
  __DEV__: {
    configurable: true,
    enumerable: true,
    value: true,
    writable: true,
  },
  cancelAnimationFrame: {
    configurable: true,
    enumerable: true,
    value: id => clearTimeout(id),
    writable: true,
  },
  nativeFabricUIManager: {
    configurable: true,
    enumerable: true,
    value: {},
    writable: true,
  },
  performance: {
    configurable: true,
    enumerable: true,
    value: {
      now: jest.fn(Date.now),
    },
    writable: true,
  },
  regeneratorRuntime: {
    configurable: true,
    enumerable: true,
    value: jest.requireActual('regenerator-runtime/runtime'),
    writable: true,
  },
  requestAnimationFrame: {
    configurable: true,
    enumerable: true,
    value: callback => setTimeout(() => callback(jest.now()), 0),
    writable: true,
  },
  window: {
    configurable: true,
    enumerable: true,
    value: global,
    writable: true,
  },
});

jest
  .mock('../Libraries/Core/InitializeCore', () => {})
  .mock('../Libraries/Core/NativeExceptionsManager')
  .mock('../Libraries/ReactNative/UIManager', () => ({
    AndroidViewPager: {
      Commands: {
        setPage: jest.fn(),
        setPageWithoutAnimation: jest.fn(),
      },
    },
    blur: jest.fn(),
    createView: jest.fn(),
    customBubblingEventTypes: {},
    customDirectEventTypes: {},
    dispatchViewManagerCommand: jest.fn(),
    focus: jest.fn(),
    getViewManagerConfig: jest.fn(name => {
      if (name === 'AndroidDrawerLayout') {
        return {
          Constants: {
            DrawerPosition: {
              Left: 10,
            },
          },
        };
      }
    }),
    hasViewManagerConfig: jest.fn(name => {
      return name === 'AndroidDrawerLayout';
    }),
    measure: jest.fn(),
    manageChildren: jest.fn(),
    setChildren: jest.fn(),
    updateView: jest.fn(),
    AndroidDrawerLayout: {
      Constants: {
        DrawerPosition: {
          Left: 10,
        },
      },
    },
    AndroidTextInput: {
      Commands: {},
    },
    ScrollView: {
      Constants: {},
    },
    View: {
      Constants: {},
    },
  }))
  .mock('../Libraries/Image/Image', () =>
    mockComponent('../Libraries/Image/Image'),
  )
  .mock('../Libraries/Text/Text', () =>
    mockComponent('../Libraries/Text/Text', MockNativeMethods),
  )
  .mock('../Libraries/Components/TextInput/TextInput', () =>
    mockComponent('../Libraries/Components/TextInput/TextInput', {
      ...MockNativeMethods,
      isFocused: jest.fn(),
      clear: jest.fn(),
      getNativeRef: jest.fn(),
    }),
  )
  .mock('../Libraries/Modal/Modal', () => {
    const baseComponent = mockComponent('../Libraries/Modal/Modal');
    const mockModal = jest.requireActual('./mockModal');
    return mockModal(baseComponent);
  })
  .mock('../Libraries/Components/View/View', () =>
    mockComponent('../Libraries/Components/View/View', MockNativeMethods),
  )
  .mock('../Libraries/Components/AccessibilityInfo/AccessibilityInfo', () => ({
    __esModule: true,
    default: {
      addEventListener: jest.fn(() => ({
        remove: jest.fn(),
      })),
      announceForAccessibility: jest.fn(),
      announceForAccessibilityWithOptions: jest.fn(),
      isAccessibilityServiceEnabled: jest.fn(() => Promise.resolve(false)),
      isBoldTextEnabled: jest.fn(() => Promise.resolve(false)),
      isGrayscaleEnabled: jest.fn(() => Promise.resolve(false)),
      isInvertColorsEnabled: jest.fn(() => Promise.resolve(false)),
      isReduceMotionEnabled: jest.fn(() => Promise.resolve(false)),
      isHighTextContrastEnabled: jest.fn(() => Promise.resolve(false)),
      isDarkerSystemColorsEnabled: jest.fn(() => Promise.resolve(false)),
      prefersCrossFadeTransitions: jest.fn(() => Promise.resolve(false)),
      isReduceTransparencyEnabled: jest.fn(() => Promise.resolve(false)),
      isScreenReaderEnabled: jest.fn(() => Promise.resolve(false)),
      setAccessibilityFocus: jest.fn(),
      sendAccessibilityEvent: jest.fn(),
      getRecommendedTimeoutMillis: jest.fn(() => Promise.resolve(false)),
    },
  }))
  .mock('../Libraries/Components/Clipboard/Clipboard', () => ({
    getString: jest.fn(() => ''),
    setString: jest.fn(),
  }))
  .mock('../Libraries/Components/RefreshControl/RefreshControl', () =>
    jest.requireActual(
      '../Libraries/Components/RefreshControl/__mocks__/RefreshControlMock',
    ),
  )
  .mock('../Libraries/Components/ScrollView/ScrollView', () => {
    const baseComponent = mockComponent(
      '../Libraries/Components/ScrollView/ScrollView',
      {
        ...MockNativeMethods,
        getScrollResponder: jest.fn(),
        getScrollableNode: jest.fn(),
        getInnerViewNode: jest.fn(),
        getInnerViewRef: jest.fn(),
        getNativeScrollRef: jest.fn(),
        scrollTo: jest.fn(),
        scrollToEnd: jest.fn(),
        flashScrollIndicators: jest.fn(),
        scrollResponderZoomTo: jest.fn(),
        scrollResponderScrollNativeHandleToKeyboard: jest.fn(),
      },
    );
    const mockScrollView = jest.requireActual('./mockScrollView');
    return mockScrollView(baseComponent);
  })
  .mock('../Libraries/Components/ActivityIndicator/ActivityIndicator', () => ({
    __esModule: true,
    default: mockComponent(
      '../Libraries/Components/ActivityIndicator/ActivityIndicator',
      null,
      true,
    ),
  }))
  .mock('../Libraries/AppState/AppState', () => ({
    addEventListener: jest.fn(() => ({
      remove: jest.fn(),
    })),
    removeEventListener: jest.fn(),
    currentState: jest.fn(),
  }))
  .mock('../Libraries/Linking/Linking', () => ({
    openURL: jest.fn(),
    canOpenURL: jest.fn(() => Promise.resolve(true)),
    openSettings: jest.fn(),
    addEventListener: jest.fn(() => ({
      remove: jest.fn(),
    })),
    getInitialURL: jest.fn(() => Promise.resolve()),
    sendIntent: jest.fn(),
  }))
  // Mock modules defined by the native layer (ex: Objective-C, Java)
  .mock('../Libraries/BatchedBridge/NativeModules', () => ({
    AlertManager: {
      alertWithArgs: jest.fn(),
    },
    AsyncLocalStorage: {
      multiGet: jest.fn((keys, callback) =>
        process.nextTick(() => callback(null, [])),
      ),
      multiSet: jest.fn((entries, callback) =>
        process.nextTick(() => callback(null)),
      ),
      multiRemove: jest.fn((keys, callback) =>
        process.nextTick(() => callback(null)),
      ),
      multiMerge: jest.fn((entries, callback) =>
        process.nextTick(() => callback(null)),
      ),
      clear: jest.fn(callback => process.nextTick(() => callback(null))),
      getAllKeys: jest.fn(callback =>
        process.nextTick(() => callback(null, [])),
      ),
    },
    DeviceInfo: {
      getConstants() {
        return {
          Dimensions: {
            window: {
              fontScale: 2,
              height: 1334,
              scale: 2,
              width: 750,
            },
            screen: {
              fontScale: 2,
              height: 1334,
              scale: 2,
              width: 750,
            },
          },
        };
      },
    },
    DevSettings: {
      addMenuItem: jest.fn(),
      reload: jest.fn(),
    },
    ImageLoader: {
      getSize: jest.fn(url => Promise.resolve([320, 240])),
      getSizeWithHeaders: jest.fn((url, headers) =>
        Promise.resolve({height: 222, width: 333}),
      ),
      prefetchImage: jest.fn(),
      prefetchImageWithMetadata: jest.fn(),
      queryCache: jest.fn(),
    },
    ImageViewManager: {
      getSize: jest.fn((uri, success) =>
        process.nextTick(() => success(320, 240)),
      ),
      prefetchImage: jest.fn(),
    },
    KeyboardObserver: {
      addListener: jest.fn(),
      removeListeners: jest.fn(),
    },
    NativeAnimatedModule: {
      createAnimatedNode: jest.fn(),
      updateAnimatedNodeConfig: jest.fn(),
      getValue: jest.fn(),
      startListeningToAnimatedNodeValue: jest.fn(),
      stopListeningToAnimatedNodeValue: jest.fn(),
      connectAnimatedNodes: jest.fn(),
      disconnectAnimatedNodes: jest.fn(),
      startAnimatingNode: jest.fn(
        (animationId, nodeTag, config, endCallback) => {
          setTimeout(() => endCallback({finished: true}), 16);
        },
      ),
      stopAnimation: jest.fn(),
      setAnimatedNodeValue: jest.fn(),
      setAnimatedNodeOffset: jest.fn(),
      flattenAnimatedNodeOffset: jest.fn(),
      extractAnimatedNodeOffset: jest.fn(),
      connectAnimatedNodeToView: jest.fn(),
      disconnectAnimatedNodeFromView: jest.fn(),
      restoreDefaultValues: jest.fn(),
      dropAnimatedNode: jest.fn(),
      addAnimatedEventToView: jest.fn(),
      removeAnimatedEventFromView: jest.fn(),
      addListener: jest.fn(),
      removeListener: jest.fn(),
      removeListeners: jest.fn(),
    },
    Networking: {
      sendRequest: jest.fn(),
      abortRequest: jest.fn(),
      addListener: jest.fn(),
      removeListeners: jest.fn(),
    },
    PlatformConstants: {
      getConstants() {
        return {
          reactNativeVersion: {
            major: 1000,
            minor: 0,
            patch: 0,
            prerelease: undefined,
          },
        };
      },
    },
    PushNotificationManager: {
      presentLocalNotification: jest.fn(),
      scheduleLocalNotification: jest.fn(),
      cancelAllLocalNotifications: jest.fn(),
      removeAllDeliveredNotifications: jest.fn(),
      getDeliveredNotifications: jest.fn(callback =>
        process.nextTick(() => []),
      ),
      removeDeliveredNotifications: jest.fn(),
      setApplicationIconBadgeNumber: jest.fn(),
      getApplicationIconBadgeNumber: jest.fn(callback =>
        process.nextTick(() => callback(0)),
      ),
      cancelLocalNotifications: jest.fn(),
      getScheduledLocalNotifications: jest.fn(callback =>
        process.nextTick(() => callback()),
      ),
      requestPermissions: jest.fn(() =>
        Promise.resolve({alert: true, badge: true, sound: true}),
      ),
      abandonPermissions: jest.fn(),
      checkPermissions: jest.fn(callback =>
        process.nextTick(() =>
          callback({alert: true, badge: true, sound: true}),
        ),
      ),
      getInitialNotification: jest.fn(() => Promise.resolve(null)),
      addListener: jest.fn(),
      removeListeners: jest.fn(),
    },
    SourceCode: {
      getConstants() {
        return {
          scriptURL: null,
        };
      },
    },
    StatusBarManager: {
      setColor: jest.fn(),
      setStyle: jest.fn(),
      setHidden: jest.fn(),
      setNetworkActivityIndicatorVisible: jest.fn(),
      setBackgroundColor: jest.fn(),
      setTranslucent: jest.fn(),
      getConstants: () => ({
        HEIGHT: 42,
      }),
    },
    Timing: {
      createTimer: jest.fn(),
      deleteTimer: jest.fn(),
    },
    UIManager: {},
    BlobModule: {
      getConstants: () => ({BLOB_URI_SCHEME: 'content', BLOB_URI_HOST: null}),
      addNetworkingHandler: jest.fn(),
      enableBlobSupport: jest.fn(),
      disableBlobSupport: jest.fn(),
      createFromParts: jest.fn(),
      sendBlob: jest.fn(),
      release: jest.fn(),
    },
    WebSocketModule: {
      connect: jest.fn(),
      send: jest.fn(),
      sendBinary: jest.fn(),
      ping: jest.fn(),
      close: jest.fn(),
      addListener: jest.fn(),
      removeListeners: jest.fn(),
    },
    I18nManager: {
      allowRTL: jest.fn(),
      forceRTL: jest.fn(),
      swapLeftAndRightInRTL: jest.fn(),
      getConstants: () => ({
        isRTL: false,
        doLeftAndRightSwapInRTL: true,
      }),
    },
  }))
  .mock('../Libraries/NativeComponent/NativeComponentRegistry', () => {
    return {
      get: jest.fn((name, viewConfigProvider) => {
        return jest.requireActual('./mockNativeComponent').default(name);
      }),
      getWithFallback_DEPRECATED: jest.fn((name, viewConfigProvider) => {
        return jest.requireActual('./mockNativeComponent').default(name);
      }),
      setRuntimeConfigProvider: jest.fn(),
    };
  })
  .mock('../Libraries/ReactNative/requireNativeComponent', () => {
    return jest.requireActual('./mockNativeComponent');
  })
  .mock('../Libraries/Vibration/Vibration', () => ({
    vibrate: jest.fn(),
    cancel: jest.fn(),
  }))
  .mock('../Libraries/Components/View/ViewNativeComponent', () => {
    const React = require('react');
    const Component = class extends React.Component {
      render() {
        return React.createElement('View', this.props, this.props.children);
      }
    };

    Component.displayName = 'View';

    return {
      __esModule: true,
      default: Component,
    };
  })
  // In tests, we can use the default version instead of the one using
  // dependency injection.
  .mock('../Libraries/ReactNative/RendererProxy', () => {
    return jest.requireActual(
      '../Libraries/ReactNative/RendererImplementation',
    );
  });
