/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 * @format
 */

'use strict';

const NativeModules = require('../BatchedBridge/NativeModules');
const Platform = require('../Utilities/Platform');
const UIManagerProperties = require('./UIManagerProperties');

const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');

import NativeUIManager from './NativeUIManager';

const viewManagerConfigs = {};

const triedLoadingConfig = new Set();

let NativeUIManagerConstants = {};
let isNativeUIManagerConstantsSet = false;
function getConstants(): Object {
  if (!isNativeUIManagerConstantsSet) {
    NativeUIManagerConstants = NativeUIManager.getConstants();
    isNativeUIManagerConstantsSet = true;
  }
  return NativeUIManagerConstants;
}

const UIManagerJS = {
  /* $FlowFixMe(>=0.111.0 site=react_native_fb) This comment suppresses an
   * error found when Flow v0.111 was deployed. To see the error, delete this
   * comment and run Flow. */
  ...NativeUIManager,
  getConstants(): Object {
    return getConstants();
  },
  getViewManagerConfig: function(viewManagerName: string): any {
    if (
      viewManagerConfigs[viewManagerName] === undefined &&
      NativeUIManager.getConstantsForViewManager
    ) {
      try {
        viewManagerConfigs[
          viewManagerName
        ] = NativeUIManager.getConstantsForViewManager(viewManagerName);
      } catch (e) {
        viewManagerConfigs[viewManagerName] = null;
      }
    }

    const config = viewManagerConfigs[viewManagerName];
    if (config) {
      return config;
    }

    // If we're in the Chrome Debugger, let's not even try calling the sync
    // method.
    if (!global.nativeCallSyncHook) {
      return config;
    }

    if (
      NativeUIManager.lazilyLoadView &&
      !triedLoadingConfig.has(viewManagerName)
    ) {
      const result = NativeUIManager.lazilyLoadView(viewManagerName);
      triedLoadingConfig.add(viewManagerName);
      if (result.viewConfig) {
        getConstants()[viewManagerName] = result.viewConfig;
        lazifyViewManagerConfig(viewManagerName);
      }
    }

    return viewManagerConfigs[viewManagerName];
  },
};

// TODO (T45220498): Remove this.
// 3rd party libs may be calling `NativeModules.UIManager.getViewManagerConfig()`
// instead of `UIManager.getViewManagerConfig()` off UIManager.js.
// This is a workaround for now.
// $FlowFixMe
NativeUIManager.getViewManagerConfig = UIManagerJS.getViewManagerConfig;

function lazifyViewManagerConfig(viewName) {
  const viewConfig = getConstants()[viewName];
  viewManagerConfigs[viewName] = viewConfig;
  if (viewConfig.Manager) {
    defineLazyObjectProperty(viewConfig, 'Constants', {
      get: () => {
        const viewManager = NativeModules[viewConfig.Manager];
        const constants = {};
        viewManager &&
          Object.keys(viewManager).forEach(key => {
            const value = viewManager[key];
            if (typeof value !== 'function') {
              constants[key] = value;
            }
          });
        return constants;
      },
    });
    defineLazyObjectProperty(viewConfig, 'Commands', {
      get: () => {
        const viewManager = NativeModules[viewConfig.Manager];
        const commands = {};
        let index = 0;
        viewManager &&
          Object.keys(viewManager).forEach(key => {
            const value = viewManager[key];
            if (typeof value === 'function') {
              commands[key] = index++;
            }
          });
        return commands;
      },
    });
  }
}

/**
 * Copies the ViewManager constants and commands into UIManager. This is
 * only needed for iOS, which puts the constants in the ViewManager
 * namespace instead of UIManager, unlike Android.
 */
if (Platform.OS === 'ios') {
  Object.keys(getConstants()).forEach(viewName => {
    lazifyViewManagerConfig(viewName);
  });
} else if (getConstants().ViewManagerNames) {
  NativeUIManager.getConstants().ViewManagerNames.forEach(viewManagerName => {
    defineLazyObjectProperty(NativeUIManager, viewManagerName, {
      get: () => NativeUIManager.getConstantsForViewManager(viewManagerName),
    });
  });
}

if (!global.nativeCallSyncHook) {
  Object.keys(getConstants()).forEach(viewManagerName => {
    if (!UIManagerProperties.includes(viewManagerName)) {
      if (!viewManagerConfigs[viewManagerName]) {
        viewManagerConfigs[viewManagerName] = getConstants()[viewManagerName];
      }
      defineLazyObjectProperty(NativeUIManager, viewManagerName, {
        get: () => {
          console.warn(
            `Accessing view manager configs directly off UIManager via UIManager['${viewManagerName}'] ` +
              `is no longer supported. Use UIManager.getViewManagerConfig('${viewManagerName}') instead.`,
          );

          return UIManagerJS.getViewManagerConfig(viewManagerName);
        },
      });
    }
  });
}

module.exports = UIManagerJS;
