/*
 * @Author: 赵伟
 * @Date: 2024-06-07 11:24:10
 * @Description: 模型演化
 */

import { useEffectWhen } from '@/hooks/useEffectWhen';
import { getModelAtlasReq } from '@/services/dataset/index.js';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import G6, { G6GraphEvent, Graph, INode } from '@antv/g6';
import { useCallback, useEffect, useRef, useState } from 'react';

import NodeTooltips from '../NodeTooltips';
import styles from './index.less';
import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils';
import {
  NodeType,
  getGraphData,
  nodeFontSize,
  nodeHeight,
  nodeWidth,
  normalizeTreeData,
  traverseHierarchically,
} from './utils';

type modeModelEvolutionProps = {
  resourceId: number;
  identifier: string;
  version?: string;
  isActive: boolean;
  onVersionChange: (version: string) => void;
};

let graph: Graph;
function ModelEvolution({
  resourceId,
  identifier,
  version,
  isActive,
  onVersionChange,
}: modeModelEvolutionProps) {
  const graphRef = useRef<HTMLDivElement>(null);
  const [showNodeTooltip, setShowNodeTooltip] = useState(false);
  const [enterTooltip, setEnterTooltip] = useState(false);
  const [nodeTooltipX, setNodeToolTipX] = useState(0);
  const [nodeTooltipY, setNodeToolTipY] = useState(0);
  const [isNodeTooltipLeft, setIsNodeTooltipLeft] = useState(true);
  const [hoverNodeData, setHoverNodeData] = useState<
    ModelDepsData | ProjectDependency | TrainDataset | undefined
  >(undefined);
  const apiData = useRef<ModelDepsData | undefined>(undefined); // 接口返回的树形结构
  const hierarchyNodes = useRef<ModelDepsData[]>([]); // 层级迭代树形结构，得到的节点列表
  const leaveNodeTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
  const leaveTooltipTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);

  useEffect(() => {
    initGraph();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    const changeSize = () => {
      if (!graph || graph.get('destroyed')) return;
      if (!graphRef.current) return;
      graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight);
      graph.fitView();
    };

    window.addEventListener('resize', changeSize);
    return () => {
      window.removeEventListener('resize', changeSize);
    };
  }, []);

  const getModelAtlas = useCallback(async () => {
    // 请求失败或者版本不存在时，清除图形
    function clearGraphData() {
      graph.data({
        nodes: [],
        edges: [],
      });
      graph.render();
      graph.fitView();
    }

    if (!resourceId || !identifier || !version) {
      clearGraphData();
      return;
    }

    const params = {
      id: resourceId,
      identifier,
      version,
    };
    const [res] = await to(getModelAtlasReq(params));
    if (res && res.data) {
      const data = normalizeTreeData(res.data);
      apiData.current = data;
      hierarchyNodes.current = traverseHierarchically(data);
      const graphData = getGraphData(data, hierarchyNodes.current);

      graph.data(graphData);
      graph.render();
      graph.fitView();
      setShowNodeTooltip(false);
      setEnterTooltip(false);
    } else {
      clearGraphData();
    }
  }, [resourceId, identifier, version]);

  useEffectWhen(getModelAtlas, isActive, [resourceId, identifier, version]);

  // 初始化图
  const initGraph = () => {
    graph = new G6.Graph({
      container: graphRef.current!,
      width: graphRef.current!.clientWidth,
      height: graphRef.current!.clientHeight,
      fitView: true,
      fitViewPadding: 100,
      minZoom: 0.5,
      maxZoom: 5,
      defaultNode: {
        type: 'rect',
        size: [nodeWidth, nodeHeight],
        anchorPoints: [
          [0, 0.5],
          [1, 0.5],
          [0.5, 0],
          [0.5, 1],
        ],
        style: {
          fill: themes['primaryColor'],
          lineWidth: 0,
          radius: 6,
          cursor: 'pointer',
        },
        labelCfg: {
          position: 'center',
          style: {
            fill: '#ffffff',
            fontSize: nodeFontSize,
            textAlign: 'center',
            cursor: 'pointer',
          },
        },
      },
      defaultEdge: {
        type: 'cubic-horizontal',
        labelCfg: {
          autoRotate: true,
        },
        style: {
          stroke: '#DEE0E5',
          lineWidth: 1,
        },
      },
      modes: {
        default: ['drag-canvas', 'zoom-canvas'],
      },
    });

    bindEvents();
  };

  // 绑定事件
  const bindEvents = () => {
    graph.on('node:mouseenter', (e: G6GraphEvent) => {
      // 清除延时关闭tooltip的定时器
      if (leaveNodeTimeout.current) {
        clearTimeout(leaveNodeTimeout.current);
        leaveNodeTimeout.current = null;
      }

      const nodeItem = e.item;
      graph.setItemState(nodeItem, 'hover', true);

      const model = nodeItem.getModel() as ModelDepsData;
      const { x, y } = model;
      const point = graph.getCanvasByPoint(x!, y!);
      const zoom = graph.getZoom();

      // 根据缩放，调整 tooltip 位置
      // const offsetX = (nodeWidth * zoom) / 4;
      const offsetY = (nodeHeight * zoom) / 2;
      // 25 是 `.model-evolution` 的 `padding-left` 值
      const tooltipX = point.x + 25 - 20;
      // 20 是 `.model-evolution` 的 `padding-bottom` 值
      const tooltipY = graphRef.current!.clientHeight - point.y + offsetY + 20 + 10;
      setNodeToolTipY(tooltipY);

      // 如果右边显示不下
      const canvasWidth = graphRef.current!.clientWidth;
      // 300 是 NodeTool 的宽度，canvasWidth + 50 是 `.model-evolution` 的宽度
      if (tooltipX + 300 > canvasWidth + 50) {
        setIsNodeTooltipLeft(false);
        setNodeToolTipX(canvasWidth + 50 - (point.x + 25) - 20);
      } else {
        setNodeToolTipX(tooltipX);
        setIsNodeTooltipLeft(true);
      }

      setHoverNodeData(model);
      setShowNodeTooltip(true);
    });

    graph.on('node:mouseleave', (e: G6GraphEvent) => {
      const nodeItem = e.item;
      graph.setItemState(nodeItem, 'hover', false);
      leaveNodeTimeout.current = setTimeout(() => {
        setShowNodeTooltip(false);
      }, 100);
    });

    graph.on('node:click', (e: G6GraphEvent) => {
      const nodeItem = e.item as INode;
      const model = nodeItem.getModel() as ModelDepsData | ProjectDependency | TrainDataset;
      const { model_type } = model;
      if (
        model_type === NodeType.Project ||
        model_type === NodeType.TrainDataset ||
        model_type === NodeType.TestDataset ||
        !apiData.current ||
        !hierarchyNodes.current
      ) {
        return;
      }

      setShowNodeTooltip(false);
      setEnterTooltip(false);
      toggleExpended(model.id);
      const graphData = getGraphData(apiData.current, hierarchyNodes.current);
      graph.data(graphData);
      graph.render();
      graph.fitView();
    });

    // 鼠标滚轮缩放时，隐藏 tooltip
    graph.on('wheelzoom', () => {
      setShowNodeTooltip(false);
      setEnterTooltip(false);
    });

    // 开始拖拽画布时触发
    graph.on('DRAG_START', () => {
      setShowNodeTooltip(false);
      setEnterTooltip(false);
    });
  };

  // toggle 展开
  const toggleExpended = (id: string) => {
    const nodes = hierarchyNodes.current;
    for (const node of nodes) {
      if (node.id === id) {
        node.expanded = !node.expanded;
        break;
      }
    }
  };

  const handleTooltipsMouseEnter = () => {
    // 清除延时关闭tooltip的定时器
    if (leaveTooltipTimeout.current) {
      clearTimeout(leaveTooltipTimeout.current);
      leaveTooltipTimeout.current = null;
    }
    setEnterTooltip(true);
  };

  const handleTooltipsMouseLeave = () => {
    leaveTooltipTimeout.current = setTimeout(() => {
      setEnterTooltip(false);
    }, 100);
  };

  return (
    <div className={styles['model-evolution']}>
      <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div>
      {(showNodeTooltip || enterTooltip) && (
        <NodeTooltips
          resourceId={resourceId}
          x={nodeTooltipX}
          y={nodeTooltipY}
          isLeft={isNodeTooltipLeft}
          data={hoverNodeData!}
          onVersionChange={onVersionChange}
          onMouseEnter={handleTooltipsMouseEnter}
          onMouseLeave={handleTooltipsMouseLeave}
        />
      )}
    </div>
  );
}

export default ModelEvolution;
