import {NativeModules, NativeEventEmitter} from 'react-native';
const {RNSentry, RNSentryEventEmitter} = NativeModules;

const DEFAULT_MODULE_IGNORES = [
  'AccessibilityManager',
  'ActionSheetManager',
  'AlertManager',
  'AppState',
  'AsyncLocalStorage',
  'Clipboard',
  'DevLoadingView',
  'DevMenu',
  'ExceptionsManager',
  'I18nManager',
  'ImageEditingManager',
  'ImageStoreManager',
  'ImageViewManager',
  'IOSConstants',
  'JSCExecutor',
  'JSCSamplingProfiler',
  'KeyboardObserver',
  'LinkingManager',
  'LocationObserver',
  'NativeAnimatedModule',
  'NavigatorManager',
  'NetInfo',
  'Networking',
  'RedBox',
  'ScrollViewManager',
  'SettingsManager',
  'SourceCode',
  'StatusBarManager',
  'Timing',
  'UIManager',
  'Vibration',
  'WebSocketModule',
  'WebViewManager'
];

export class NativeClient {
  constructor(dsn, options) {
    if (dsn.constructor !== String) {
      throw new Error('Sentry: A DSN must be provided');
    }
    if (!RNSentry) {
      throw new Error('Sentry: There is no native client installed.');
    }

    this._dsn = dsn;
    this._activatedMerging = false;
    this.options = {
      ignoreModulesExclude: [],
      ignoreModulesInclude: [],
      deactivateStacktraceMerging: false
    };
    Object.assign(this.options, options);

    RNSentry.startWithDsnString(this._dsn);
    if (this.options.deactivateStacktraceMerging === false) {
      this._activateStacktraceMerging();
      const eventEmitter = new NativeEventEmitter(RNSentryEventEmitter);
      eventEmitter.addListener(RNSentryEventEmitter.MODULE_TABLE, moduleTable => {
        this._updateIgnoredModules(JSON.parse(moduleTable.payload));
      });
    }
    RNSentry.setLogLevel(options.logLevel);
  }

  nativeCrash() {
    RNSentry.crash();
  }

  captureEvent(event) {
    RNSentry.captureEvent(event);
  }

  setUserContext(user) {
    RNSentry.setUser(user);
  }

  setTagsContext(tags) {
    RNSentry.setTags(tags);
  }

  setExtraContext(extra) {
    RNSentry.setExtra(extra);
  }

  addExtraContext(key, value) {
    RNSentry.addExtra(key, value);
  }

  captureBreadcrumb(crumb) {
    RNSentry.captureBreadcrumb(crumb);
  }

  clearContext() {
    RNSentry.clearContext();
  }

  _updateIgnoredModules(modules) {
    const values = Object.values(modules);
    const keys = Object.keys(modules);
    for (let i = 0; i < values.length; i++) {
      const moduleName = values[i].replace(/RCT/, '');
      const moduleID = keys[i];
      if (this._ignoredModules[moduleID]) {
        continue;
      }
      if (
        this.options.ignoreModulesExclude.indexOf(moduleName) === -1 &&
        (DEFAULT_MODULE_IGNORES.indexOf(moduleName) >= 0 ||
          this.options.ignoreModulesInclude.indexOf(moduleName) >= 0)
      ) {
        this._ignoredModules[moduleID] = true;
      }
    }
  }

  _activateStacktraceMerging = async () => {
    return RNSentry.activateStacktraceMerging()
      .then(activated => {
        if (this._activatedMerging) {
          return;
        }
        this._ignoredModules = {};
        const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
        if (typeof __fbBatchedBridgeConfig !== 'undefined') {
          /* global __fbBatchedBridgeConfig */
          __fbBatchedBridgeConfig.remoteModuleConfig.forEach((module, moduleID) => {
            if (
              module !== null &&
              this.options.ignoreModulesExclude.indexOf(module[0]) === -1 &&
              (DEFAULT_MODULE_IGNORES.indexOf(module[0]) >= 0 ||
                this.options.ignoreModulesInclude.indexOf(module[0]) >= 0)
            ) {
              this._ignoredModules[moduleID] = true;
            }
          });
        } else if (BatchedBridge._remoteModuleTable) {
          for (let module in BatchedBridge._remoteModuleTable) {
            if (BatchedBridge._remoteModuleTable.hasOwnProperty(module)) {
              let moduleName = BatchedBridge._remoteModuleTable[module];
              if (
                this.options.ignoreModulesExclude.indexOf(moduleName) === -1 &&
                (DEFAULT_MODULE_IGNORES.indexOf(moduleName) >= 0 ||
                  this.options.ignoreModulesInclude.indexOf(moduleName) >= 0)
              ) {
                this._ignoredModules[module] = true;
              }
            }
          }
        }
        this._activatedMerging = true;
        this._overwriteEnqueueNativeCall();
      })
      .catch(function(reason) {
        // eslint-disable-next-line
        console.log(reason);
      });
  };

  _overwriteEnqueueNativeCall() {
    const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
    const original = BatchedBridge.enqueueNativeCall;
    const that = this;
    BatchedBridge.enqueueNativeCall = function(
      moduleID: number,
      methodID: number,
      params: Array<any>,
      onFail: ?Function,
      onSucc: ?Function
    ) {
      if (that._ignoredModules[moduleID]) {
        return original.apply(this, arguments);
      }
      params.push({
        __sentry_stack: new Error().stack,
        __sentry_moduleID: moduleID
      });
      return original.apply(this, arguments);
    };
  }
}

@@ -2525,31 +2525,134 @@
     %7D%0A      
+this._addIgnoredModule(moduleID, moduleName);%0A    %7D%0A  %7D%0A%0A  _addIgnoredModule(moduleID, moduleName) %7B%0A    
 if (%0A
-  
       this.o
@@ -2711,26 +2711,24 @@
 -1 &&%0A      
- 
- 
 (DEFAULT_MOD
@@ -2759,34 +2759,32 @@
 leName) %3E= 0 %7C%7C%0A
-  
         this.opt
@@ -2835,32 +2835,28 @@
 ) %3E= 0)%0A    
- 
- 
 ) %7B%0A
-  
       this._
@@ -2884,32 +2884,24 @@
 ID%5D = true;%0A
-      %7D%0A
     %7D%0A  %7D%0A%0A 
@@ -3423,31 +3423,16 @@
     if (
-%0A              
 module !
@@ -3438,19 +3438,19 @@
 !== null
- &&
+) %7B
 %0A       
@@ -3465,273 +3465,46 @@
 his.
-options.i
+_addI
 gnore
+d
 Module
-sExclude.indexOf(module%5B0%5D) === -1 &&%0A              (DEFAULT_MODULE_IGNORES.indexOf(module%5B0%5D) %3E= 0 %7C%7C%0A                this.options.ignoreModulesInclude.indexOf(module%5B0%5D) %3E= 0)%0A            ) %7B%0A              this._ignoredModules%5BmoduleID%5D = true
+(moduleID, module%5B0%5D)
 ;%0A  
@@ -3609,16 +3609,18 @@
 t module
+ID
  in Batc
@@ -3721,16 +3721,18 @@
 y(module
+ID
 )) %7B%0A   
@@ -3802,32 +3802,13 @@
 dule
+ID
 %5D;%0A
-              if (%0A  
     
@@ -3826,299 +3826,48 @@
 his.
-options.i
+_addI
 gnore
+d
 Module
-sExclude.indexOf(moduleName) === -1 &&%0A                (DEFAULT_MODULE_IGNORES.indexOf(moduleName) %3E= 0 %7C%7C%0A                  this.options.ignoreModulesInclude.indexOf(moduleName) %3E= 0)%0A              ) %7B%0A                this._ignoredModules%5Bmodule%5D = true;%0A              %7D
+(moduleID, moduleName);
 %0A   

Refactor addIgnoredModule function