/**
 * @flow
 */
import { NativeModules } from 'react-native';
import Log from '../utils/log';
import { nativeWithApp } from './../utils';
import INTERNALS from './../internals';
import FirebaseCore from './../firebase';

const logs = {};

// Firebase Native SDKs that support multiple app instances
const MULTI_APP_MODULES = [
  'auth',
  'database',
  'storage',
];

const NATIVE_MODULE_EVENTS = {
  Storage: [
    'storage_event',
    'storage_error',
  ],
  Auth: [
    'onAuthStateChanged',
    'phone_auth_event',
  ],
  Database: [
    // 'database_on_event',
    // 'database_cancel_event',
    'database_transaction_event',
    // 'database_server_offset', // TODO
  ],
};

const DEFAULTS = {
  Database: {
    persistence: false,
  },
};

export default class ModuleBase {
  /**
   *
   * @param firebaseApp
   * @param options
   * @param moduleName
   * @param withEventEmitter
   */
  constructor(firebaseApp, options, withEventEmitter = false) {
    this._module = this.constructor._NATIVE_MODULE.replace('RNFirebase', '');
    this._firebaseApp = firebaseApp;
    this._appName = firebaseApp._name;
    this._namespace = `${this._appName}:${this._module}`;
    this._options = Object.assign({}, DEFAULTS[this._module] || {}, options);

    // check if native module exists as all native
    // modules are now optionally part of build
    const nativeModule = NativeModules[this.constructor._NATIVE_MODULE];

    if (!nativeModule) {
      throw new Error(
        INTERNALS.STRINGS.ERROR_MISSING_MODULE(
          this.constructor._NAMESPACE,
          this.constructor._NATIVE_MODULE,
        ),
      );
    }

    // used by the modules that extend ModuleBase
    // to access their native module counterpart
    if (!MULTI_APP_MODULES.includes(this._module.toLowerCase())) {
      this._native = nativeModule;
    } else {
      this._native = nativeWithApp(this._appName, nativeModule);
    }

    if (withEventEmitter) {
      this._setupEventEmitter(nativeModule, this._module);
    }
  }

  /**
   *
   * @param nativeModule
   * @param moduleName
   * @private
   */
  _setupEventEmitter(nativeModule, moduleName) {
    this._eventEmitter = FirebaseCore._getOrSetNativeEmitter(`${this._appName}-${this._module}`, nativeModule);
    const events = NATIVE_MODULE_EVENTS[moduleName];

    if (events && events.length) {
      for (let i = 0, len = events.length; i < len; i++) {
        FirebaseCore._subscribeForDistribution(events[i], this._eventEmitter);
      }
    }
  }

  /**
   *
   * @param eventName
   * @return {string}
   * @private
   */
  _getAppEventName(eventName) {
    return `${this._appName}-${eventName}`;
  }

  /**
   * Returns the FirebaseApp instance for current module
   * @return {*}
   */
  get app() {
    return this._firebaseApp;
  }

  get log(): Log {
    if (logs[this._namespace]) return logs[this._namespace];
    return logs[this._namespace] = Log.createLogger(
      `🔥 ${this._namespace.toUpperCase()}`,
    );
  }

  /*
   * Proxy functions to shared event emitter instance
   * https://github.com/facebook/react-native/blob/master/Libraries/EventEmitter/EventEmitter.js
   */
  get sharedEventEmitter() {
    return INTERNALS.SharedEventEmitter;
  }

  get addListener() {
    return INTERNALS.SharedEventEmitter.addListener.bind(INTERNALS.SharedEventEmitter);
  }

  get once() {
    return INTERNALS.SharedEventEmitter.once.bind(INTERNALS.SharedEventEmitter);
  }

  get on() {
    return INTERNALS.SharedEventEmitter.addListener.bind(INTERNALS.SharedEventEmitter);
  }

  get emit() {
    return INTERNALS.SharedEventEmitter.emit.bind(INTERNALS.SharedEventEmitter);
  }

  get listeners() {
    return INTERNALS.SharedEventEmitter.listeners.bind(INTERNALS.SharedEventEmitter);
  }

  hasListeners(eventType: string): Boolean {
    const subscriptions = INTERNALS.SharedEventEmitter._subscriber.getSubscriptionsForType(eventType);
    return subscriptions && subscriptions.length;
  }

  get removeListener() {
    return INTERNALS.SharedEventEmitter.removeListener.bind(INTERNALS.SharedEventEmitter);
  }

  get removeAllListeners() {
    return INTERNALS.SharedEventEmitter.removeAllListeners.bind(INTERNALS.SharedEventEmitter);
  }
}
