/**
 * 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.
 *
 * @providesModule ReactComponentTreeDevtool
 */

'use strict';

var invariant = require('fbjs/lib/invariant');

var tree = {};
var rootIDs = [];

function updateTree(id, update) {
  if (!tree[id]) {
    tree[id] = {
      parentID: null,
      ownerID: null,
      text: null,
      childIDs: [],
      displayName: 'Unknown',
      isMounted: false,
      updateCount: 0
    };
  }
  update(tree[id]);
}

function purgeDeep(id) {
  var item = tree[id];
  if (item) {
    var childIDs = item.childIDs;

    delete tree[id];
    childIDs.forEach(purgeDeep);
  }
}

var ReactComponentTreeDevtool = {
  onSetDisplayName: function (id, displayName) {
    updateTree(id, function (item) {
      return item.displayName = displayName;
    });
  },
  onSetChildren: function (id, nextChildIDs) {
    updateTree(id, function (item) {
      var prevChildIDs = item.childIDs;
      item.childIDs = nextChildIDs;

      nextChildIDs.forEach(function (nextChildID) {
        var nextChild = tree[nextChildID];
        !nextChild ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected devtool events to fire for the child ' + 'before its parent includes it in onSetChildren().') : invariant(false) : void 0;
        !(nextChild.displayName != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onSetDisplayName() to fire for the child ' + 'before its parent includes it in onSetChildren().') : invariant(false) : void 0;
        !(nextChild.childIDs != null || nextChild.text != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Expected onSetChildren() or onSetText() to fire for the child ' + 'before its parent includes it in onSetChildren().') : invariant(false) : 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().') : invariant(false) : void 0;

        if (prevChildIDs.indexOf(nextChildID) === -1) {
          nextChild.parentID = id;
        }
      });
    });
  },
  onSetOwner: function (id, ownerID) {
    updateTree(id, function (item) {
      return item.ownerID = ownerID;
    });
  },
  onSetText: function (id, text) {
    updateTree(id, function (item) {
      return item.text = text;
    });
  },
  onMountComponent: function (id) {
    updateTree(id, function (item) {
      return item.isMounted = true;
    });
  },
  onMountRootComponent: function (id) {
    rootIDs.push(id);
  },
  onUpdateComponent: function (id) {
    updateTree(id, function (item) {
      return item.updateCount++;
    });
  },
  onUnmountComponent: function (id) {
    updateTree(id, function (item) {
      return item.isMounted = false;
    });
    rootIDs = rootIDs.filter(function (rootID) {
      return rootID !== id;
    });
  },
  purgeUnmountedComponents: function () {
    if (ReactComponentTreeDevtool._preventPurging) {
      // Should only be used for testing.
      return;
    }

    Object.keys(tree).filter(function (id) {
      return !tree[id].isMounted;
    }).forEach(purgeDeep);
  },
  isMounted: function (id) {
    var item = tree[id];
    return item ? item.isMounted : false;
  },
  getChildIDs: function (id) {
    var item = tree[id];
    return item ? item.childIDs : [];
  },
  getDisplayName: function (id) {
    var item = tree[id];
    return item ? item.displayName : 'Unknown';
  },
  getOwnerID: function (id) {
    var item = tree[id];
    return item ? item.ownerID : null;
  },
  getParentID: function (id) {
    var item = tree[id];
    return item ? item.parentID : null;
  },
  getText: function (id) {
    var item = tree[id];
    return item ? item.text : null;
  },
  getUpdateCount: function (id) {
    var item = tree[id];
    return item ? item.updateCount : 0;
  },
  getRootIDs: function () {
    return rootIDs;
  },
  getRegisteredIDs: function () {
    return Object.keys(tree);
  }
};

module.exports = ReactComponentTreeDevtool;