"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _lodash = _interopRequireDefault(require("lodash"));

var _edges = _interopRequireDefault(require("./edges"));

var _nodes = _interopRequireDefault(require("./nodes"));

var _propTypes = _interopRequireDefault(require("prop-types"));

var _ZoomableCanvas = _interopRequireDefault(require("./ZoomableCanvas"));

var _d2 = require("d3");

var _constants = require("../utils/constants");

var _react = _interopRequireWildcard(require("react"));

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }

function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }

function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }

function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }

function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }

function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }

function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }

function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }

function computeHierarchy(rootDtNode) {
  var hierarchy = (0, _d2.hierarchy)(rootDtNode, function (treeNode) {
    return treeNode.children;
  });
  var index = 0;
  hierarchy.each(function (hNode) {
    // Unique id based on index
    hNode.id = index;
    index += 1; // Deal with decision rules

    if (hNode.parent) {
      hNode.idPath = [].concat(_toConsumableArray(hNode.parent.idPath), [hNode.id]);

      var _index = hNode.parent.children.findIndex(function (child) {
        return child === hNode;
      });

      hNode.path = "".concat(hNode.parent.path).concat(_constants.NODE_PATH_SEPARATOR).concat(_index);
    } else {
      // root node
      hNode.idPath = [hNode.id];
      hNode.path = '0';
    }
  });
  return hierarchy;
}

function applyFolding(hierarchy) {
  var foldedNodes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  // Apply the folded node to the hierarchy
  hierarchy.each(function (hNode) {
    var folded = hNode.foldedChildren != null;
    var shouldBeFolded = foldedNodes.findIndex(function (path) {
      return hNode.path === path;
    }) >= 0;
    var toFold = shouldBeFolded && !folded;
    var toUnfold = !shouldBeFolded && folded;

    if (toFold) {
      hNode.foldedChildren = hNode.children;
      hNode.children = null;
    }

    if (toUnfold) {
      hNode.children = hNode.foldedChildren;
      hNode.foldedChildren = null;
    }
  });
  return hierarchy;
}

var d3ComputeHierarchyLayout = (0, _d2.tree)().nodeSize([_constants.NODE_WIDTH + _constants.NODE_WIDTH_MARGIN, _constants.NODE_HEIGHT]);

function applyHierarchyLayout(hierarchy) {
  var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
  d3ComputeHierarchyLayout(hierarchy);
  var treeDepth = 0;
  var dxMin = Number.MAX_SAFE_INTEGER;
  var dxMax = Number.MIN_SAFE_INTEGER; // Find out the boundaries of the hierarchy

  hierarchy.each(function (hNode) {
    treeDepth = Math.max(treeDepth, hNode.depth); // Normalize for fixed-depth.

    hNode.y = hNode.depth * _constants.NODE_DEPTH;
    dxMin = Math.min(hNode.x, dxMin);
    dxMax = Math.max(hNode.x, dxMax);
  });
  var canvasHeight = (treeDepth + 1) * _constants.NODE_DEPTH;

  var canvasWidth = Math.abs(dxMin) + Math.abs(dxMax) + _constants.NODE_WIDTH;

  var offsetX = Math.abs(dxMin) + _constants.NODE_WIDTH / 2; // place correctly the tree in the svg with the canvasWidth

  hierarchy.each(function (hNode) {
    hNode.x = hNode.x + offsetX;
    hNode.y = hNode.y + _constants.NODE_HEIGHT / 3; // take in account the height of the node above the link
  });
  return {
    canvasWidth: canvasWidth,
    canvasHeight: canvasHeight,
    version: version + 1
  };
}

function recursiveHNodeFromPath(nodePath, hNode) {
  if (nodePath.length !== 0) {
    var indexChild = parseInt(nodePath[0]);
    return recursiveHNodeFromPath(_lodash.default.tail(nodePath), hNode.children[indexChild]);
  }

  return hNode;
}

function hNodeFromPath(nodePathStr, hierarchy) {
  if (!nodePathStr) {
    return null;
  }

  if (nodePathStr === '0') {
    return hierarchy;
  } // Check validity of the selectedNode


  if (!_constants.NODE_PATH_REGEXP.test(nodePathStr)) {
    throw new Error("Given node path is not valid: ".concat(nodePathStr));
  }

  var nodePath = nodePathStr.split(_constants.NODE_PATH_SEPARATOR); // remove the first element of the path because it is the root path;

  return recursiveHNodeFromPath(_lodash.default.tail(nodePath), hierarchy);
}

var DEFAULT_PROPS = {
  scale: -1,
  updatePositionAndZoom: function updatePositionAndZoom(position, scale) {},
  foldedNodes: []
};

var Tree = _react.default.memo(function Tree(_ref) {
  var interpreter = _ref.interpreter,
      configuration = _ref.configuration,
      height = _ref.height,
      width = _ref.width,
      position = _ref.position,
      _ref$scale = _ref.scale,
      scale = _ref$scale === void 0 ? DEFAULT_PROPS.scale : _ref$scale,
      updateFoldedNodes = _ref.updateFoldedNodes,
      _ref$updatePositionAn = _ref.updatePositionAndZoom,
      updatePositionAndZoom = _ref$updatePositionAn === void 0 ? DEFAULT_PROPS.updatePositionAndZoom : _ref$updatePositionAn,
      updateSelectedNode = _ref.updateSelectedNode,
      edgeType = _ref.edgeType,
      selectedNode = _ref.selectedNode,
      _ref$foldedNodes = _ref.foldedNodes,
      foldedNodes = _ref$foldedNodes === void 0 ? DEFAULT_PROPS.foldedNodes : _ref$foldedNodes;
  var hierarchy = (0, _react.useMemo)(function () {
    return applyFolding(computeHierarchy(interpreter.dt), foldedNodes);
  }, // No dependency on 'foldedNodes' because this should only occur on the hierarchy full recomputation.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  [interpreter.dt]);

  var _useState = (0, _react.useState)(true),
      _useState2 = _slicedToArray(_useState, 2),
      zooming = _useState2[0],
      setZooming = _useState2[1];

  var _useState3 = (0, _react.useState)({
    layout: applyHierarchyLayout(hierarchy),
    initialZoom: scale > 0 && {
      x: position[0],
      y: position[1],
      k: scale
    }
  }),
      _useState4 = _slicedToArray(_useState3, 2),
      _useState4$ = _useState4[0],
      layout = _useState4$.layout,
      initialZoom = _useState4$.initialZoom,
      setLayoutAndInitialZoom = _useState4[1];

  var zoom = (0, _react.useRef)(initialZoom);
  (0, _react.useEffect)(function () {
    setLayoutAndInitialZoom(function (_ref2) {
      var layout = _ref2.layout,
          initialZoom = _ref2.initialZoom;
      return {
        layout: applyHierarchyLayout(hierarchy, layout.version),
        initialZoom: initialZoom
      };
    });
  }, [hierarchy]);
  (0, _react.useEffect)(function () {
    setLayoutAndInitialZoom(function (_ref3) {
      var layout = _ref3.layout,
          initialZoom = _ref3.initialZoom;
      return {
        layout: layout,
        initialZoom: scale > 0 && {
          x: position[0],
          y: position[1],
          k: scale
        }
      };
    });
  }, [position, scale]);
  var handleZoomChange = (0, _react.useCallback)(function (newZoom) {
    zoom.current = newZoom;

    if (updatePositionAndZoom) {
      updatePositionAndZoom([newZoom.x, newZoom.y], newZoom.k);
    }
  }, [updatePositionAndZoom]);
  var applyFoldingAndFixPosition = (0, _react.useCallback)(function (foldedNodes, referenceHNode) {
    // 'Save' the position of the reference node
    var previousPosX = referenceHNode.x;
    var previousPosY = referenceHNode.y;
    applyFolding(hierarchy, foldedNodes); // Refresh the layout

    setLayoutAndInitialZoom(function (_ref4) {
      var layout = _ref4.layout;
      var updatedLayout = applyHierarchyLayout(hierarchy, layout.version);
      return {
        layout: updatedLayout,
        // Update the zoom to compensate for the movement induced by the folding
        initialZoom: {
          x: zoom.current.x - (referenceHNode.x - previousPosX) * zoom.current.k,
          y: zoom.current.y - (referenceHNode.y - previousPosY) * zoom.current.k,
          k: zoom.current.k
        }
      };
    });
  }, [hierarchy]); // Apply foldedNodes at the initialization and keep the root of the hierarchy at the same location.
  // eslint-disable-next-line react-hooks/exhaustive-deps

  (0, _react.useEffect)(function () {
    return applyFoldingAndFixPosition(foldedNodes, hierarchy);
  }, [applyFoldingAndFixPosition, hierarchy]);
  var selectedHNode = (0, _react.useMemo)(function () {
    return hNodeFromPath(selectedNode, hierarchy);
  }, [selectedNode, hierarchy]);

  var _useState5 = (0, _react.useState)(foldedNodes),
      _useState6 = _slicedToArray(_useState5, 2),
      foldedNodesState = _useState6[0],
      setFoldedNodesState = _useState6[1];

  var toggleSubtreeFold = (0, _react.useCallback)(function (hNode) {
    var folded = hNode.foldedChildren != null;
    var currentFoldedNodes = updateFoldedNodes ? foldedNodes : foldedNodesState;
    var newFoldedNodes = folded ? currentFoldedNodes.filter(function (path) {
      return path !== hNode.path;
    }) : [].concat(_toConsumableArray(currentFoldedNodes), [hNode.path]);
    applyFoldingAndFixPosition(newFoldedNodes, hNode);

    if (updateFoldedNodes) {
      updateFoldedNodes(newFoldedNodes);
    } else {
      setFoldedNodesState(newFoldedNodes);
    }
  }, [applyFoldingAndFixPosition, foldedNodes, foldedNodesState, updateFoldedNodes]); // Unselect the previously selected node if a parent were folded

  (0, _react.useEffect)(function () {
    if (selectedNode && updateSelectedNode) {
      foldedNodesState.forEach(function (path) {
        if (selectedNode.startsWith(path) && selectedNode !== path) {
          updateSelectedNode();
          return;
        }
      });
    }
  }, [foldedNodesState, selectedNode, updateSelectedNode]);
  return _react.default.createElement(_ZoomableCanvas.default, {
    initialZoom: initialZoom,
    canvasWidth: layout.canvasWidth,
    canvasHeight: layout.canvasHeight,
    onZooming: setZooming,
    onZoomChange: handleZoomChange,
    minZoomScale: _constants.ZOOM_EXTENT[0],
    maxZoomScale: _constants.ZOOM_EXTENT[1],
    style: {
      height: height,
      width: width,
      backgroundColor: 'white',
      minWidth: 400
    }
  }, _react.default.createElement(_react.default.Fragment, {
    key: layout.version
  }, _react.default.createElement(_nodes.default, {
    selectable: !zooming,
    interpreter: interpreter,
    configuration: configuration,
    hierarchy: hierarchy,
    updateSelectedNode: updateSelectedNode,
    selectedNodePath: selectedNode || '',
    onToggleSubtreeFold: toggleSubtreeFold
  }), _react.default.createElement(_edges.default, {
    selectedNodeIdPath: selectedHNode ? selectedHNode.idPath : [],
    dt: interpreter.dt,
    hierarchy: hierarchy,
    width: layout.canvasWidth,
    height: layout.canvasHeight,
    edgeType: interpreter.version == 1 ? 'constant' : edgeType
  })));
});

Tree.propTypes = {
  interpreter: _propTypes.default.object.isRequired,
  configuration: _propTypes.default.object.isRequired,
  height: _propTypes.default.number.isRequired,
  width: _propTypes.default.number.isRequired,
  position: _propTypes.default.array.isRequired,
  scale: _propTypes.default.number.isRequired,
  updateFoldedNodes: _propTypes.default.func,
  updatePositionAndZoom: _propTypes.default.func,
  updateSelectedNode: _propTypes.default.func,
  edgeType: _propTypes.default.string,
  selectedNode: _propTypes.default.string,
  foldedNodes: _propTypes.default.arrayOf(_propTypes.default.string)
};
var _default = Tree;
exports.default = _default;