/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/


/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { __extends } from "tslib";
import * as zrUtil from 'zrender/lib/core/util.js';
import * as graphic from '../../util/graphic.js';
import { getECData } from '../../util/innerStore.js';
import SymbolClz from '../helper/Symbol.js';
import { radialCoordinate } from './layoutHelper.js';
import * as bbox from 'zrender/lib/core/bbox.js';
import View from '../../coord/View.js';
import * as roamHelper from '../../component/helper/roamHelper.js';
import RoamController from '../../component/helper/RoamController.js';
import { onIrrelevantElement } from '../../component/helper/cursorHelper.js';
import { parsePercent } from '../../util/number.js';
import ChartView from '../../view/Chart.js';
import Path from 'zrender/lib/graphic/Path.js';
import { setStatesStylesFromModel, setStatesFlag, setDefaultStateProxy, HOVER_STATE_BLUR } from '../../util/states.js';

var TreeEdgeShape =
/** @class */
function () {
  function TreeEdgeShape() {
    this.parentPoint = [];
    this.childPoints = [];
  }

  return TreeEdgeShape;
}();

var TreePath =
/** @class */
function (_super) {
  __extends(TreePath, _super);

  function TreePath(opts) {
    return _super.call(this, opts) || this;
  }

  TreePath.prototype.getDefaultStyle = function () {
    return {
      stroke: '#000',
      fill: null
    };
  };

  TreePath.prototype.getDefaultShape = function () {
    return new TreeEdgeShape();
  };

  TreePath.prototype.buildPath = function (ctx, shape) {
    var childPoints = shape.childPoints;
    var childLen = childPoints.length;
    var parentPoint = shape.parentPoint;
    var firstChildPos = childPoints[0];
    var lastChildPos = childPoints[childLen - 1];

    if (childLen === 1) {
      ctx.moveTo(parentPoint[0], parentPoint[1]);
      ctx.lineTo(firstChildPos[0], firstChildPos[1]);
      return;
    }

    var orient = shape.orient;
    var forkDim = orient === 'TB' || orient === 'BT' ? 0 : 1;
    var otherDim = 1 - forkDim;
    var forkPosition = parsePercent(shape.forkPosition, 1);
    var tmpPoint = [];
    tmpPoint[forkDim] = parentPoint[forkDim];
    tmpPoint[otherDim] = parentPoint[otherDim] + (lastChildPos[otherDim] - parentPoint[otherDim]) * forkPosition;
    ctx.moveTo(parentPoint[0], parentPoint[1]);
    ctx.lineTo(tmpPoint[0], tmpPoint[1]);
    ctx.moveTo(firstChildPos[0], firstChildPos[1]);
    tmpPoint[forkDim] = firstChildPos[forkDim];
    ctx.lineTo(tmpPoint[0], tmpPoint[1]);
    tmpPoint[forkDim] = lastChildPos[forkDim];
    ctx.lineTo(tmpPoint[0], tmpPoint[1]);
    ctx.lineTo(lastChildPos[0], lastChildPos[1]);

    for (var i = 1; i < childLen - 1; i++) {
      var point = childPoints[i];
      ctx.moveTo(point[0], point[1]);
      tmpPoint[forkDim] = point[forkDim];
      ctx.lineTo(tmpPoint[0], tmpPoint[1]);
    }
  };

  return TreePath;
}(Path);

var TreeView =
/** @class */
function (_super) {
  __extends(TreeView, _super);

  function TreeView() {
    var _this = _super !== null && _super.apply(this, arguments) || this;

    _this.type = TreeView.type;
    _this._mainGroup = new graphic.Group();
    return _this;
  }

  TreeView.prototype.init = function (ecModel, api) {
    this._controller = new RoamController(api.getZr());
    this._controllerHost = {
      target: this.group
    };
    this.group.add(this._mainGroup);
  };

  TreeView.prototype.render = function (seriesModel, ecModel, api) {
    var data = seriesModel.getData();
    var layoutInfo = seriesModel.layoutInfo;
    var group = this._mainGroup;
    var layout = seriesModel.get('layout');

    if (layout === 'radial') {
      group.x = layoutInfo.x + layoutInfo.width / 2;
      group.y = layoutInfo.y + layoutInfo.height / 2;
    } else {
      group.x = layoutInfo.x;
      group.y = layoutInfo.y;
    }

    this._updateViewCoordSys(seriesModel, api);

    this._updateController(seriesModel, ecModel, api);

    var oldData = this._data;
    data.diff(oldData).add(function (newIdx) {
      if (symbolNeedsDraw(data, newIdx)) {
        // Create node and edge
        updateNode(data, newIdx, null, group, seriesModel);
      }
    }).update(function (newIdx, oldIdx) {
      var symbolEl = oldData.getItemGraphicEl(oldIdx);

      if (!symbolNeedsDraw(data, newIdx)) {
        symbolEl && removeNode(oldData, oldIdx, symbolEl, group, seriesModel);
        return;
      } // Update node and edge


      updateNode(data, newIdx, symbolEl, group, seriesModel);
    }).remove(function (oldIdx) {
      var symbolEl = oldData.getItemGraphicEl(oldIdx); // When remove a collapsed node of subtree, since the collapsed
      // node haven't been initialized with a symbol element,
      // you can't found it's symbol element through index.
      // so if we want to remove the symbol element we should insure
      // that the symbol element is not null.

      if (symbolEl) {
        removeNode(oldData, oldIdx, symbolEl, group, seriesModel);
      }
    }).execute();
    this._nodeScaleRatio = seriesModel.get('nodeScaleRatio');

    this._updateNodeAndLinkScale(seriesModel);

    if (seriesModel.get('expandAndCollapse') === true) {
      data.eachItemGraphicEl(function (el, dataIndex) {
        el.off('click').on('click', function () {
          api.dispatchAction({
            type: 'treeExpandAndCollapse',
            seriesId: seriesModel.id,
            dataIndex: dataIndex
          });
        });
      });
    }

    this._data = data;
  };

  TreeView.prototype._updateViewCoordSys = function (seriesModel, api) {
    var data = seriesModel.getData();
    var points = [];
    data.each(function (idx) {
      var layout = data.getItemLayout(idx);

      if (layout && !isNaN(layout.x) && !isNaN(layout.y)) {
        points.push([+layout.x, +layout.y]);
      }
    });
    var min = [];
    var max = [];
    bbox.fromPoints(points, min, max); // If don't Store min max when collapse the root node after roam,
    // the root node will disappear.

    var oldMin = this._min;
    var oldMax = this._max; // If width or height is 0

    if (max[0] - min[0] === 0) {
      min[0] = oldMin ? oldMin[0] : min[0] - 1;
      max[0] = oldMax ? oldMax[0] : max[0] + 1;
    }

    if (max[1] - min[1] === 0) {
      min[1] = oldMin ? oldMin[1] : min[1] - 1;
      max[1] = oldMax ? oldMax[1] : max[1] + 1;
    }

    var viewCoordSys = seriesModel.coordinateSystem = new View();
    viewCoordSys.zoomLimit = seriesModel.get('scaleLimit');
    viewCoordSys.setBoundingRect(min[0], min[1], max[0] - min[0], max[1] - min[1]);
    viewCoordSys.setCenter(seriesModel.get('center'), api);
    viewCoordSys.setZoom(seriesModel.get('zoom')); // Here we use viewCoordSys just for computing the 'position' and 'scale' of the group

    this.group.attr({
      x: viewCoordSys.x,
      y: viewCoordSys.y,
      scaleX: viewCoordSys.scaleX,
      scaleY: viewCoordSys.scaleY
    });
    this._min = min;
    this._max = max;
  };

  TreeView.prototype._updateController = function (seriesModel, ecModel, api) {
    var _this = this;

    var controller = this._controller;
    var controllerHost = this._controllerHost;
    var group = this.group;
    controller.setPointerChecker(function (e, x, y) {
      var rect = group.getBoundingRect();
      rect.applyTransform(group.transform);
      return rect.contain(x, y) && !onIrrelevantElement(e, api, seriesModel);
    });
    controller.enable(seriesModel.get('roam'));
    controllerHost.zoomLimit = seriesModel.get('scaleLimit');
    controllerHost.zoom = seriesModel.coordinateSystem.getZoom();
    controller.off('pan').off('zoom').on('pan', function (e) {
      roamHelper.updateViewOnPan(controllerHost, e.dx, e.dy);
      api.dispatchAction({
        seriesId: seriesModel.id,
        type: 'treeRoam',
        dx: e.dx,
        dy: e.dy
      });
    }).on('zoom', function (e) {
      roamHelper.updateViewOnZoom(controllerHost, e.scale, e.originX, e.originY);
      api.dispatchAction({
        seriesId: seriesModel.id,
        type: 'treeRoam',
        zoom: e.scale,
        originX: e.originX,
        originY: e.originY
      });

      _this._updateNodeAndLinkScale(seriesModel); // Only update label layout on zoom


      api.updateLabelLayout();
    });
  };

  TreeView.prototype._updateNodeAndLinkScale = function (seriesModel) {
    var data = seriesModel.getData();

    var nodeScale = this._getNodeGlobalScale(seriesModel);

    data.eachItemGraphicEl(function (el, idx) {
      el.setSymbolScale(nodeScale);
    });
  };

  TreeView.prototype._getNodeGlobalScale = function (seriesModel) {
    var coordSys = seriesModel.coordinateSystem;

    if (coordSys.type !== 'view') {
      return 1;
    }

    var nodeScaleRatio = this._nodeScaleRatio;
    var groupZoom = coordSys.scaleX || 1; // Scale node when zoom changes

    var roamZoom = coordSys.getZoom();
    var nodeScale = (roamZoom - 1) * nodeScaleRatio + 1;
    return nodeScale / groupZoom;
  };

  TreeView.prototype.dispose = function () {
    this._controller && this._controller.dispose();
    this._controllerHost = null;
  };

  TreeView.prototype.remove = function () {
    this._mainGroup.removeAll();

    this._data = null;
  };

  TreeView.type = 'tree';
  return TreeView;
}(ChartView);

function symbolNeedsDraw(data, dataIndex) {
  var layout = data.getItemLayout(dataIndex);
  return layout && !isNaN(layout.x) && !isNaN(layout.y);
}

function updateNode(data, dataIndex, symbolEl, group, seriesModel) {
  var isInit = !symbolEl;
  var node = data.tree.getNodeByDataIndex(dataIndex);
  var itemModel = node.getModel();
  var visualColor = node.getVisual('style').fill;
  var symbolInnerColor = node.isExpand === false && node.children.length !== 0 ? visualColor : '#fff';
  var virtualRoot = data.tree.root;
  var source = node.parentNode === virtualRoot ? node : node.parentNode || node;
  var sourceSymbolEl = data.getItemGraphicEl(source.dataIndex);
  var sourceLayout = source.getLayout();
  var sourceOldLayout = sourceSymbolEl ? {
    x: sourceSymbolEl.__oldX,
    y: sourceSymbolEl.__oldY,
    rawX: sourceSymbolEl.__radialOldRawX,
    rawY: sourceSymbolEl.__radialOldRawY
  } : sourceLayout;
  var targetLayout = node.getLayout();

  if (isInit) {
    symbolEl = new SymbolClz(data, dataIndex, null, {
      symbolInnerColor: symbolInnerColor,
      useNameLabel: true
    });
    symbolEl.x = sourceOldLayout.x;
    symbolEl.y = sourceOldLayout.y;
  } else {
    symbolEl.updateData(data, dataIndex, null, {
      symbolInnerColor: symbolInnerColor,
      useNameLabel: true
    });
  }

  symbolEl.__radialOldRawX = symbolEl.__radialRawX;
  symbolEl.__radialOldRawY = symbolEl.__radialRawY;
  symbolEl.__radialRawX = targetLayout.rawX;
  symbolEl.__radialRawY = targetLayout.rawY;
  group.add(symbolEl);
  data.setItemGraphicEl(dataIndex, symbolEl);
  symbolEl.__oldX = symbolEl.x;
  symbolEl.__oldY = symbolEl.y;
  graphic.updateProps(symbolEl, {
    x: targetLayout.x,
    y: targetLayout.y
  }, seriesModel);
  var symbolPath = symbolEl.getSymbolPath();

  if (seriesModel.get('layout') === 'radial') {
    var realRoot = virtualRoot.children[0];
    var rootLayout = realRoot.getLayout();
    var length_1 = realRoot.children.length;
    var rad = void 0;
    var isLeft = void 0;

    if (targetLayout.x === rootLayout.x && node.isExpand === true && realRoot.children.length) {
      var center = {
        x: (realRoot.children[0].getLayout().x + realRoot.children[length_1 - 1].getLayout().x) / 2,
        y: (realRoot.children[0].getLayout().y + realRoot.children[length_1 - 1].getLayout().y) / 2
      };
      rad = Math.atan2(center.y - rootLayout.y, center.x - rootLayout.x);

      if (rad < 0) {
        rad = Math.PI * 2 + rad;
      }

      isLeft = center.x < rootLayout.x;

      if (isLeft) {
        rad = rad - Math.PI;
      }
    } else {
      rad = Math.atan2(targetLayout.y - rootLayout.y, targetLayout.x - rootLayout.x);

      if (rad < 0) {
        rad = Math.PI * 2 + rad;
      }

      if (node.children.length === 0 || node.children.length !== 0 && node.isExpand === false) {
        isLeft = targetLayout.x < rootLayout.x;

        if (isLeft) {
          rad = rad - Math.PI;
        }
      } else {
        isLeft = targetLayout.x > rootLayout.x;

        if (!isLeft) {
          rad = rad - Math.PI;
        }
      }
    }

    var textPosition = isLeft ? 'left' : 'right';
    var normalLabelModel = itemModel.getModel('label');
    var rotate = normalLabelModel.get('rotate');
    var labelRotateRadian = rotate * (Math.PI / 180);
    var textContent = symbolPath.getTextContent();

    if (textContent) {
      symbolPath.setTextConfig({
        position: normalLabelModel.get('position') || textPosition,
        rotation: rotate == null ? -rad : labelRotateRadian,
        origin: 'center'
      });
      textContent.setStyle('verticalAlign', 'middle');
    }
  } // Handle status


  var focus = itemModel.get(['emphasis', 'focus']);
  var focusDataIndices = focus === 'relative' ? zrUtil.concatArray(node.getAncestorsIndices(), node.getDescendantIndices()) : focus === 'ancestor' ? node.getAncestorsIndices() : focus === 'descendant' ? node.getDescendantIndices() : null;

  if (focusDataIndices) {
    // Modify the focus to data indices.
    getECData(symbolEl).focus = focusDataIndices;
  }

  drawEdge(seriesModel, node, virtualRoot, symbolEl, sourceOldLayout, sourceLayout, targetLayout, group);

  if (symbolEl.__edge) {
    symbolEl.onHoverStateChange = function (toState) {
      if (toState !== 'blur') {
        // NOTE: Ensure the parent elements will been blurred firstly.
        // According to the return of getAncestorsIndices and getDescendantIndices
        // TODO: A bit tricky.
        var parentEl = node.parentNode && data.getItemGraphicEl(node.parentNode.dataIndex);

        if (!(parentEl && parentEl.hoverState === HOVER_STATE_BLUR)) {
          setStatesFlag(symbolEl.__edge, toState);
        }
      }
    };
  }
}

function drawEdge(seriesModel, node, virtualRoot, symbolEl, sourceOldLayout, sourceLayout, targetLayout, group) {
  var itemModel = node.getModel();
  var edgeShape = seriesModel.get('edgeShape');
  var layout = seriesModel.get('layout');
  var orient = seriesModel.getOrient();
  var curvature = seriesModel.get(['lineStyle', 'curveness']);
  var edgeForkPosition = seriesModel.get('edgeForkPosition');
  var lineStyle = itemModel.getModel('lineStyle').getLineStyle();
  var edge = symbolEl.__edge; // curve edge from node -> parent
  // polyline edge from node -> children

  if (edgeShape === 'curve') {
    if (node.parentNode && node.parentNode !== virtualRoot) {
      if (!edge) {
        edge = symbolEl.__edge = new graphic.BezierCurve({
          shape: getEdgeShape(layout, orient, curvature, sourceOldLayout, sourceOldLayout)
        });
      }

      graphic.updateProps(edge, {
        shape: getEdgeShape(layout, orient, curvature, sourceLayout, targetLayout)
      }, seriesModel);
    }
  } else if (edgeShape === 'polyline') {
    if (layout === 'orthogonal') {
      if (node !== virtualRoot && node.children && node.children.length !== 0 && node.isExpand === true) {
        var children = node.children;
        var childPoints = [];

        for (var i = 0; i < children.length; i++) {
          var childLayout = children[i].getLayout();
          childPoints.push([childLayout.x, childLayout.y]);
        }

        if (!edge) {
          edge = symbolEl.__edge = new TreePath({
            shape: {
              parentPoint: [targetLayout.x, targetLayout.y],
              childPoints: [[targetLayout.x, targetLayout.y]],
              orient: orient,
              forkPosition: edgeForkPosition
            }
          });
        }

        graphic.updateProps(edge, {
          shape: {
            parentPoint: [targetLayout.x, targetLayout.y],
            childPoints: childPoints
          }
        }, seriesModel);
      }
    } else {
      if (process.env.NODE_ENV !== 'production') {
        throw new Error('The polyline edgeShape can only be used in orthogonal layout');
      }
    }
  } // show all edge when edgeShape is 'curve', filter node `isExpand` is false when edgeShape is 'polyline'


  if (edge && !(edgeShape === 'polyline' && !node.isExpand)) {
    edge.useStyle(zrUtil.defaults({
      strokeNoScale: true,
      fill: null
    }, lineStyle));
    setStatesStylesFromModel(edge, itemModel, 'lineStyle');
    setDefaultStateProxy(edge);
    group.add(edge);
  }
}

function removeNodeEdge(node, data, group, seriesModel, removeAnimationOpt) {
  var virtualRoot = data.tree.root;

  var _a = getSourceNode(virtualRoot, node),
      source = _a.source,
      sourceLayout = _a.sourceLayout;

  var symbolEl = data.getItemGraphicEl(node.dataIndex);

  if (!symbolEl) {
    return;
  }

  var sourceSymbolEl = data.getItemGraphicEl(source.dataIndex);
  var sourceEdge = sourceSymbolEl.__edge; // 1. when expand the sub tree, delete the children node should delete the edge of
  // the source at the same time. because the polyline edge shape is only owned by the source.
  // 2.when the node is the only children of the source, delete the node should delete the edge of
  // the source at the same time. the same reason as above.

  var edge = symbolEl.__edge || (source.isExpand === false || source.children.length === 1 ? sourceEdge : undefined);
  var edgeShape = seriesModel.get('edgeShape');
  var layoutOpt = seriesModel.get('layout');
  var orient = seriesModel.get('orient');
  var curvature = seriesModel.get(['lineStyle', 'curveness']);

  if (edge) {
    if (edgeShape === 'curve') {
      graphic.removeElement(edge, {
        shape: getEdgeShape(layoutOpt, orient, curvature, sourceLayout, sourceLayout),
        style: {
          opacity: 0
        }
      }, seriesModel, {
        cb: function () {
          group.remove(edge);
        },
        removeOpt: removeAnimationOpt
      });
    } else if (edgeShape === 'polyline' && seriesModel.get('layout') === 'orthogonal') {
      graphic.removeElement(edge, {
        shape: {
          parentPoint: [sourceLayout.x, sourceLayout.y],
          childPoints: [[sourceLayout.x, sourceLayout.y]]
        },
        style: {
          opacity: 0
        }
      }, seriesModel, {
        cb: function () {
          group.remove(edge);
        },
        removeOpt: removeAnimationOpt
      });
    }
  }
}

function getSourceNode(virtualRoot, node) {
  var source = node.parentNode === virtualRoot ? node : node.parentNode || node;
  var sourceLayout;

  while (sourceLayout = source.getLayout(), sourceLayout == null) {
    source = source.parentNode === virtualRoot ? source : source.parentNode || source;
  }

  return {
    source: source,
    sourceLayout: sourceLayout
  };
}

function removeNode(data, dataIndex, symbolEl, group, seriesModel) {
  var node = data.tree.getNodeByDataIndex(dataIndex);
  var virtualRoot = data.tree.root;
  var sourceLayout = getSourceNode(virtualRoot, node).sourceLayout; // Use same duration and easing with update to have more consistent animation.

  var removeAnimationOpt = {
    duration: seriesModel.get('animationDurationUpdate'),
    easing: seriesModel.get('animationEasingUpdate')
  };
  graphic.removeElement(symbolEl, {
    x: sourceLayout.x + 1,
    y: sourceLayout.y + 1
  }, seriesModel, {
    cb: function () {
      group.remove(symbolEl);
      data.setItemGraphicEl(dataIndex, null);
    },
    removeOpt: removeAnimationOpt
  });
  symbolEl.fadeOut(null, data.hostModel, {
    fadeLabel: true,
    animation: removeAnimationOpt
  }); // remove edge as parent node

  node.children.forEach(function (childNode) {
    removeNodeEdge(childNode, data, group, seriesModel, removeAnimationOpt);
  }); // remove edge as child node

  removeNodeEdge(node, data, group, seriesModel, removeAnimationOpt);
}

function getEdgeShape(layoutOpt, orient, curvature, sourceLayout, targetLayout) {
  var cpx1;
  var cpy1;
  var cpx2;
  var cpy2;
  var x1;
  var x2;
  var y1;
  var y2;

  if (layoutOpt === 'radial') {
    x1 = sourceLayout.rawX;
    y1 = sourceLayout.rawY;
    x2 = targetLayout.rawX;
    y2 = targetLayout.rawY;
    var radialCoor1 = radialCoordinate(x1, y1);
    var radialCoor2 = radialCoordinate(x1, y1 + (y2 - y1) * curvature);
    var radialCoor3 = radialCoordinate(x2, y2 + (y1 - y2) * curvature);
    var radialCoor4 = radialCoordinate(x2, y2);
    return {
      x1: radialCoor1.x || 0,
      y1: radialCoor1.y || 0,
      x2: radialCoor4.x || 0,
      y2: radialCoor4.y || 0,
      cpx1: radialCoor2.x || 0,
      cpy1: radialCoor2.y || 0,
      cpx2: radialCoor3.x || 0,
      cpy2: radialCoor3.y || 0
    };
  } else {
    x1 = sourceLayout.x;
    y1 = sourceLayout.y;
    x2 = targetLayout.x;
    y2 = targetLayout.y;

    if (orient === 'LR' || orient === 'RL') {
      cpx1 = x1 + (x2 - x1) * curvature;
      cpy1 = y1;
      cpx2 = x2 + (x1 - x2) * curvature;
      cpy2 = y2;
    }

    if (orient === 'TB' || orient === 'BT') {
      cpx1 = x1;
      cpy1 = y1 + (y2 - y1) * curvature;
      cpx2 = x2;
      cpy2 = y2 + (y1 - y2) * curvature;
    }
  }

  return {
    x1: x1,
    y1: y1,
    x2: x2,
    y2: y2,
    cpx1: cpx1,
    cpy1: cpy1,
    cpx2: cpx2,
    cpy2: cpy2
  };
}

export default TreeView;