text: /**
   * Copyright 2016-present, Facebook, Inc.
   * All rights reserved.
   *
   * This source code is licensed under the BSD-style license found in the
   * LICENSE file in the root directory of this source tree. An additional grant
   * of patent rights can be found in the PATENTS file in the same directory.
   *
   * 
   */
  
  'use strict';
  
  var _prodInvariant = require('./reactProdInvariant');
  
  var ReactCurrentOwner = require('./ReactCurrentOwner');
  
  var invariant = require('fbjs/lib/invariant');
  var warning = require('fbjs/lib/warning');
  
  function isNative(fn) {
    // Based on isNative() from Lodash
    var funcToString = Function.prototype.toString;
    var hasOwnProperty = Object.prototype.hasOwnProperty;
    var reIsNative = RegExp('^' + funcToString
    // Take an example native function source for comparison
    .call(hasOwnProperty)
    // Strip regex characters so we can use it for regex
    .replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
    // Remove hasOwnProperty from the template to make it generic
    .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
    try {
      var source = funcToString.call(fn);
      return reIsNative.test(source);
    } catch (err) {
      return false;
    }
  }
  
  var canUseCollections =
  // Array.from
  typeof Array.from === 'function' &&
  // Map
  typeof Map === 'function' && isNative(Map) &&
  // Map.prototype.keys
  Map.prototype != null && typeof Map.prototype.keys === 'function' && isNative(Map.prototype.keys) &&
  // Set
  typeof Set === 'function' && isNative(Set) &&
  // Set.prototype.keys
  Set.prototype != null && typeof Set.prototype.keys === 'function' && isNative(Set.prototype.keys);
  
  if (canUseCollections) {
    var itemMap = new Map();
    var rootIDSet = new Set();
  
    var setItem = function (id, item) {
      itemMap.set(id, item);
    };
    var getItem = function (id) {
      return itemMap.get(id);
    };
    var removeItem = function (id) {
      itemMap['delete'](id);
    };
    var getItemIDs = function () {
      return Array.from(itemMap.keys());
    };
  
    var addRoot = function (id) {
      rootIDSet.add(id);
    };
    var removeRoot = function (id) {
      rootIDSet['delete'](id);
    };
    var getRootIDs = function () {
      return Array.from(rootIDSet.keys());
    };
  } else {
    var itemByKey = {};
    var rootByKey = {};
  
    // Use non-numeric keys to prevent V8 performance issues:
    // https://github.com/facebook/react/pull/7232
    var getKeyFromID = function (id) {
      return '.' + id;
    };
    var getIDFromKey = function (key) {
      return parseInt(key.substr(1), 10);
    };
  
    var setItem = function (id, item) {
      var key = getKeyFromID(id);
      itemByKey[key] = item;
    };
    var getItem = function (id) {
      var key = getKeyFromID(id);
      return itemByKey[key];
    };
    var removeItem = function (id) {
      var key = getKeyFromID(id);
      delete itemByKey[key];
    };
    var getItemIDs = function () {
      return Object.keys(itemByKey).map(getIDFromKey);
    };
  
    var addRoot = function (id) {
      var key = getKeyFromID(id);
      rootByKey[key] = true;
    };
    var removeRoot = function (id) {
      var key = getKeyFromID(id);
      delete rootByKey[key];
    };
    var getRootIDs = function () {
      return Object.keys(rootByKey).map(getIDFromKey);
    };
  }
  
  var unmountedIDs = [];
  
  function purgeDeep(id) {
    var item = getItem(id);
    if (item) {
      var childIDs = item.childIDs;
  
      removeItem(id);
      childIDs.forEach(purgeDeep);
    }
  }
  
  function describeComponentFrame(name, source, ownerName) {
    return '\n    in ' + (name || 'Unknown') + (source ? ' (at ' + source.fileName.replace(/^.*[\\\/]/, '') + ':' + source.lineNumber + ')' : ownerName ? ' (created by ' + ownerName + ')' : '');
  }
  
  function getDisplayName(element) {
    if (element == null) {
      return '#empty';
    } else if (typeof element === 'string' || typeof element === 'number') {
      return '#text';
    } else if (typeof element.type === 'string') {
      return element.type;
    } else {
      return element.type.displayName || element.type.name || 'Unknown';
    }
  }
  
  function describeID(id) {
    var name = ReactComponentTreeHook.getDisplayName(id);
    var element = ReactComponentTreeHook.getElement(id);
    var ownerID = ReactComponentTreeHook.getOwnerID(id);
    var ownerName;
    if (ownerID) {
      ownerName = ReactComponentTreeHook.getDisplayName(ownerID);
    }
    process.env.NODE_ENV !== 'production' ? warning(element, 'ReactComponentTreeHook: Missing React element for debugID %s when ' + 'building stack', id) : void 0;
    return describeComponentFrame(name, element && element._source, ownerName);
  }
  
  var ReactComponentTreeHook = {
    onSetChildren: function (id, nextChildIDs) {
      var item = getItem(id);
      invariant(item, 'Item must have been set');
      item.childIDs = nextChildIDs;
  
      for (var i = 0; i < nextChildIDs.length; i++) {
        var nextChildID = nextChildIDs[i];
        var nextChild = getItem(nextChildID);
        !nextChild ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected hook events to fire for the child before its parent includes it in onSetChildren().') : _prodInvariant('140') : void 0;
        !(nextChild.childIDs != null || typeof nextChild.element !== 'object' || nextChild.element == null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onSetChildren() to fire for a container child before its parent includes it in onSetChildren().') : _prodInvariant('141') : void 0;
        !nextChild.isMounted ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onMountComponent() to fire for the child before its parent includes it in onSetChildren().') : _prodInvariant('71') : void 0;
        if (nextChild.parentID == null) {
          nextChild.parentID = id;
          // TODO: This shouldn't be necessary but mounting a new root during in
          // componentWillMount currently causes not-yet-mounted components to
          // be purged from our tree data so their parent id is missing.
        }
        !(nextChild.parentID === id) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onBeforeMountComponent() parent and onSetChildren() to be consistent (%s has parents %s and %s).', nextChildID, nextChild.parentID, id) : _prodInvariant('142', nextChildID, nextChild.parentID, id) : void 0;
      }
    },
    onBeforeMountComponent: function (id, element, parentID) {
      var item = {
        element: element,
        parentID: parentID,
        text: null,
        childIDs: [],
        isMounted: false,
        updateCount: 0
      };
      setItem(id, item);
    },
    onBeforeUpdateComponent: function (id, element) {
      var item = getItem(id);
      if (!item || !item.isMounted) {
        // We may end up here as a result of setState() in componentWillUnmount().
        // In this case, ignore the element.
        return;
      }
      item.element = element;
    },
    onMountComponent: function (id) {
      var item = getItem(id);
      invariant(item, 'Item must have been set');
      item.isMounted = true;
      var isRoot = item.parentID === 0;
      if (isRoot) {
        addRoot(id);
      }
    },
    onUpdateComponent: function (id) {
      var item = getItem(id);
      if (!item || !item.isMounted) {
        // We may end up here as a result of setState() in componentWillUnmount().
        // In this case, ignore the element.
        return;
      }
      item.updateCount++;
    },
    onUnmountComponent: function (id) {
      var item = getItem(id);
      if (item) {
        // We need to check if it exists.
        // `item` might not exist if it is inside an error boundary, and a sibling
        // error boundary child threw while mounting. Then this instance never
        // got a chance to mount, but it still gets an unmounting event during
        // the error boundary cleanup.
        item.isMounted = false;
        var isRoot = item.parentID === 0;
        if (isRoot) {
          removeRoot(id);
        }
      }
      unmountedIDs.push(id);
    },
    purgeUnmountedComponents: function () {
      if (ReactComponentTreeHook._preventPurging) {
        // Should only be used for testing.
        return;
      }
  
      for (var i = 0; i < unmountedIDs.length; i++) {
        var id = unmountedIDs[i];
        purgeDeep(id);
      }
      unmountedIDs.length = 0;
    },
    isMounted: function (id) {
      var item = getItem(id);
      return item ? item.isMounted : false;
    },
    getCurrentStackAddendum: function (topElement) {
      var info = '';
      if (topElement) {
        var name = getDisplayName(topElement);
        var owner = topElement._owner;
        info += describeComponentFrame(name, topElement._source, owner && owner.getName());
      }
  
      var currentOwner = ReactCurrentOwner.current;
      var id = currentOwner && currentOwner._debugID;
  
      info += ReactComponentTreeHook.getStackAddendumByID(id);
      return info;
    },
    getStackAddendumByID: function (id) {
      var info = '';
      while (id) {
        info += describeID(id);
        id = ReactComponentTreeHook.getParentID(id);
      }
      return info;
    },
    getChildIDs: function (id) {
      var item = getItem(id);
      return item ? item.childIDs : [];
    },
    getDisplayName: function (id) {
      var element = ReactComponentTreeHook.getElement(id);
      if (!element) {
        return null;
      }
      return getDisplayName(element);
    },
    getElement: function (id) {
      var item = getItem(id);
      return item ? item.element : null;
    },
    getOwnerID: function (id) {
      var element = ReactComponentTreeHook.getElement(id);
      if (!element || !element._owner) {
        return null;
      }
      return element._owner._debugID;
    },
    getParentID: function (id) {
      var item = getItem(id);
      return item ? item.parentID : null;
    },
    getSource: function (id) {
      var item = getItem(id);
      var element = item ? item.element : null;
      var source = element != null ? element._source : null;
      return source;
    },
    getText: function (id) {
      var element = ReactComponentTreeHook.getElement(id);
      if (typeof element === 'string') {
        return element;
      } else if (typeof element === 'number') {
        return '' + element;
      } else {
        return null;
      }
    },
    getUpdateCount: function (id) {
      var item = getItem(id);
      return item ? item.updateCount : 0;
    },
  
  
    getRootIDs: getRootIDs,
    getRegisteredIDs: getItemIDs
  };
  
  module.exports = ReactComponentTreeHook;