import KFIcon from '@/components/KFIcon';
import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible';
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString, parseJsonText, s8 } from '@/utils';
import { to } from '@/utils/promise';
import G6 from '@antv/g6';
import { useNavigate, useParams } from '@umijs/max';
import { App, Button } from 'antd';
import { useEffect, useRef, useState } from 'react';
import GlobalParamsDrawer from '../components/GlobalParamsDrawer';
import ModelMenu from '../components/ModelMenu';
import Props from '../components/PipelineNodeDrawer';
import styles from './index.less';
import { findAllParentNodes } from './utils';

let graph = null;

const EditPipeline = () => {
  const navigate = useNavigate();
  const locationParams = useParams(); //新版本获取路由参数接口
  const graphRef = useRef();
  const paramsDrawerRef = useRef();
  const propsRef = useRef();
  const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false);
  const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]);
  const [workflowInfo, setWorkflowInfo] = useState(undefined);
  const { message } = App.useApp();
  let sourceAnchorIdx, targetAnchorIdx, dropAnchorIdx;
  let dragSourceNode;

  useEffect(() => {
    initGraph();
    getFirstWorkflow(locationParams.id);
    // 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 onDragEnd = (val) => {
    const { x, y } = val;
    const point = graph.getPointByClient(x, y);
    // 元模型
    const model = {
      ...val,
      x: point.x,
      y: point.y,
      id: val.component_name + '-' + s8(),
      isCluster: false,
      formError: true,
    };
    // console.log('model', model);
    graph.addItem('node', model, false);
  };

  // 节点数据发生变化
  const handleFormChange = (val) => {
    if (graph) {
      const data = graph.save();
      const index = data.nodes.findIndex((item) => {
        return item.id === val.id;
      });
      data.nodes[index] = val;
      const zoom = graph.getZoom();
      // 在拉取新数据重新渲染页面之前先获取点（0， 0）在画布上的位置
      const lastPoint = graph.getCanvasByPoint(0, 0);
      graph.changeData(data);
      graph.render();
      graph.zoomTo(zoom);
      // 获取重新渲染之后点（0， 0）在画布的位置
      const newPoint = graph.getCanvasByPoint(0, 0);
      // 移动画布相对位移;
      graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y);
    }
  };

  // 保存
  const savePipeline = async (isBack) => {
    const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields());
    if (globalParamError) {
      message.error('全局参数配置有误');
      openParamsDrawer();
      return;
    }
    closeParamsDrawer();

    const [propsRes, propsError] = await to(propsRef.current.validateFields());
    if (propsError) {
      message.error('节点必填项必须配置');
      return;
    }
    propsRef.current.close();

    setTimeout(() => {
      const data = graph.save();
      // console.log(data);
      const errorNode = data.nodes.find((item) => item.formError === true);
      if (errorNode) {
        message.error(`【${errorNode.label}】节点配置验证失败`);
        const graphNode = graph.findById(errorNode.id);
        if (graphNode) {
          openNodeDrawer(graphNode, true);
        }
        return;
      }
      const params = {
        ...locationParams,
        name: workflowInfo?.name,
        dag: JSON.stringify(data),
        global_param: JSON.stringify(globalParamRes.global_param),
      };
      saveWorkflow(params).then((ret) => {
        message.success('保存成功');
        setTimeout(() => {
          if (isBack) {
            navigate({ pathname: `/pipeline/template` });
          }
        }, 500);
      });
    }, 500);
  };

  // 渲染数据
  const getGraphData = (data) => {
    if (graph && data) {
      graph.data(data);
      graph.render();
    } else {
      setTimeout(() => {
        getGraphData(data);
      }, 500);
    }
  };

  // 处理并行边，暂时没有用
  const processParallelEdgesOnAnchorPoint = (
    edges,
    offsetDiff = 15,
    multiEdgeType = 'cubic-vertical',
    singleEdgeType = undefined,
    loopEdgeType = undefined,
  ) => {
    const len = edges.length;
    const cod = offsetDiff * 2;
    const loopPosition = [
      'top',
      'top-right',
      'right',
      'bottom-right',
      'bottom',
      'bottom-left',
      'left',
      'top-left',
    ];
    const edgeMap = {};
    const tags = [];
    const reverses = {};
    for (let i = 0; i < len; i++) {
      const edge = edges[i];
      const { source, target, sourceAnchor, targetAnchor } = edge;
      const sourceTarget = `${source}|${sourceAnchor}-${target}|${targetAnchor}`;

      if (tags[i]) continue;
      if (!edgeMap[sourceTarget]) {
        edgeMap[sourceTarget] = [];
      }
      tags[i] = true;
      edgeMap[sourceTarget].push(edge);
      for (let j = 0; j < len; j++) {
        if (i === j) continue;
        const sedge = edges[j];
        const {
          source: src,
          target: dst,
          sourceAnchor: srcAnchor,
          targetAnchor: dstAnchor,
        } = sedge;

        // 两个节点之间共同的边
        // 第一条的source = 第二条的target
        // 第一条的target = 第二条的source
        if (!tags[j]) {
          if (
            source === dst &&
            sourceAnchor === dstAnchor &&
            target === src &&
            targetAnchor === srcAnchor
          ) {
            edgeMap[sourceTarget].push(sedge);
            tags[j] = true;
            reverses[
              `${src}|${srcAnchor}|${dst}|${dstAnchor}|${edgeMap[sourceTarget].length - 1}`
            ] = true;
          } else if (
            source === src &&
            sourceAnchor === srcAnchor &&
            target === dst &&
            targetAnchor === dstAnchor
          ) {
            edgeMap[sourceTarget].push(sedge);
            tags[j] = true;
          }
        }
      }
    }

    // eslint-disable-next-line
    for (const key in edgeMap) {
      const arcEdges = edgeMap[key];
      const { length } = arcEdges;
      for (let k = 0; k < length; k++) {
        const current = arcEdges[k];
        if (current.source === current.target) {
          if (loopEdgeType) current.type = loopEdgeType;
          // 超过8条自环边，则需要重新处理
          current.loopCfg = {
            position: loopPosition[k % 8],
            dist: Math.floor(k / 8) * 20 + 50,
          };
          continue;
        }
        if (
          length === 1 &&
          singleEdgeType &&
          (current.source !== current.target || current.sourceAnchor !== current.targetAnchor)
        ) {
          current.type = singleEdgeType;
          continue;
        }
        current.type = multiEdgeType;
        const sign =
          (k % 2 === 0 ? 1 : -1) *
          (reverses[
            `${current.source}|${current.sourceAnchor}|${current.target}|${current.targetAnchor}|${k}`
          ]
            ? -1
            : 1);
        if (length % 2 === 1) {
          current.curveOffset = sign * Math.ceil(k / 2) * cod;
        } else {
          current.curveOffset = sign * (Math.floor(k / 2) * cod + offsetDiff);
        }
      }
    }
    return edges;
  };
  // 判断两个节点之间是否有边
  const hasEdge = (source, target) => {
    const neighbors = source.getNeighbors();
    for (const node of neighbors) {
      // 新建边的时候，获取的 neighbors 的数据有问题，不全是 INode 类型，可能没有 getID 方法
      if (node.getID?.() === target.getID?.()) return true;
    }
    return false;
  };

  // 复制节点
  const cloneElement = (item) => {
    let data = graph.save();
    const nodeId = s8();
    data.nodes.push({
      ...item.getModel(),
      label: item.getModel().label + '-copy',
      x: item.getModel().x + 150,
      y: item.getModel().y,
      id: item.getModel().component_name + '-' + nodeId,
    });
    graph.changeData(data);
  };

  // 获取流水线详情
  const getFirstWorkflow = async (val) => {
    const [res] = await to(getWorkflowById(val));
    if (res && res.data) {
      setWorkflowInfo(res.data);
      const { global_param, dag } = res.data;
      setGlobalParam(global_param || []);
      if (dag) {
        getGraphData(parseJsonText(dag));
      }
    }
  };

  // 打开节点抽屉
  const openNodeDrawer = (node, validate = false) => {
    // 获取所有的上游节点
    const parentNodes = findAllParentNodes(graph, node);
    // 如果没有打开过全局参数抽屉，获取不到全局参数
    const globalParams =
      paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current;
    // 打开节点编辑抽屉
    propsRef.current.showDrawer(node.getModel(), globalParams, parentNodes, validate);
  };

  // 初始化图
  const initGraph = () => {
    const contextMenu = initMenu();
    G6.registerNode(
      'rect-node',
      {
        // draw anchor-point circles according to the anchorPoints in afterDraw
        getAnchorPoints(cfg) {
          return (
            cfg.anchorPoints || [
              // 四个，上下左右
              [0.5, 0],
              [0.5, 1],
              [0, 0.5],
              [1, 0.5],
            ]
          );
        },
        afterDraw(cfg, group) {
          group.addShape('image', {
            attrs: {
              x: -45,
              y: -10,
              width: 20,
              height: 20,
              img: cfg.img,
              cursor: 'pointer',
            },
            draggable: true,
          });
          if (cfg.label) {
            group.addShape('text', {
              attrs: {
                text: fittingString(cfg.label, 70, 10),
                x: -20,
                y: 0,
                fontSize: 10,
                textAlign: 'left',
                textBaseline: 'middle',
                fill: '#000',
                cursor: 'pointer',
              },
              name: 'text-shape',
              draggable: true,
            });
          }
          if (cfg.formError) {
            group.addShape('image', {
              attrs: {
                x: 43,
                y: -24,
                width: 18,
                height: 18,
                img: require('@/assets/img/pipeline-warning.png'),
                cursor: 'pointer',
              },
              draggable: false,
              capture: false,
            });
          }
          const bbox = group.getBBox();
          if (cfg.formError) {
            bbox.y += 6;
            bbox.width -= 6;
            bbox.height -= 6;
          }
          const anchorPoints = this.getAnchorPoints(cfg);
          anchorPoints.forEach((anchorPos, i) => {
            group.addShape('circle', {
              attrs: {
                r: 3,
                x: bbox.x + bbox.width * anchorPos[0],
                y: bbox.y + bbox.height * anchorPos[1],
                fill: '#fff',
                stroke: '#a4a4a5',
                cursor: 'crosshair',
                lineWidth: 1,
              },
              name: `anchor-point`, // the name, for searching by group.find(ele => ele.get('name') === 'anchor-point')
              anchorPointIdx: i, // flag the idx of the anchor-point circle
              links: 0, // cache the number of edges connected to this shape
              visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state
              draggable: true,
            });
          });
        },

        // response the state changes and show/hide the link-point circles
        setState(name, value, item) {
          const group = item.getContainer();
          const shape = group.get('children')[0];
          const anchorPoints = group.findAll((item) => item.get('name') === 'anchor-point');
          if (name === 'hover') {
            if (value) {
              shape.attr('stroke', themes['primaryColor']);
              anchorPoints.forEach((point) => {
                point.show();
              });
            } else {
              shape.attr('stroke', 'transparent');
              anchorPoints.forEach((point) => {
                point.hide();
              });
            }
          } else if (name === 'drag') {
            if (sourceAnchorIdx !== null && sourceAnchorIdx !== undefined) {
              const anchorPoint = anchorPoints[sourceAnchorIdx];
              anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5');
              anchorPoint.attr('lineWidth', value ? 2 : 1);
            }
          } else if (name === 'drop') {
            if (dropAnchorIdx !== null && dropAnchorIdx !== undefined) {
              const anchorPoint = anchorPoints[dropAnchorIdx];
              anchorPoint.attr('stroke', value ? themes['primaryColor'] : '#a4a4a5');
              anchorPoint.attr('lineWidth', value ? 2 : 1);
            }
          }
        },
      },
      'rect',
    );

    graph = new G6.Graph({
      container: graphRef.current,
      width: graphRef.current.clientWidth || 500,
      height: graphRef.current.clientHeight || '100%',
      animate: false,
      groupByTypes: true,
      fitView: true,
      plugins: [contextMenu],
      enabledStack: false,
      fitView: true,
      minZoom: 0.5,
      maxZoom: 5,
      fitViewPadding: 200,
      modes: {
        default: [
          // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles
          {
            type: 'drag-node',
            shouldBegin: (e) => {
              if (e.target.get('name') === 'anchor-point') return false;
              return true;
            },
          },
          // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles
          {
            type: 'create-edge',
            trigger: 'drag',
            shouldBegin: (e) => {
              // avoid beginning at other shapes on the node
              if (e.target && e.target.get('name') !== 'anchor-point') return false;
              sourceAnchorIdx = e.target.get('anchorPointIdx');
              e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
              dragSourceNode = e.item;
              return true;
            },
            shouldEnd: (e) => {
              // avoid ending at other shapes on the node
              if (e.target && e.target.get('name') !== 'anchor-point') return false;
              if (!dragSourceNode || !e.item) return false;
              // 不允许连接自己
              if (dragSourceNode.getID() === e.item.getID()) return false;
              // 两个节点不允许多条边
              if (hasEdge(dragSourceNode, e.item)) return false;
              if (e.target) {
                targetAnchorIdx = e.target.get('anchorPointIdx');
                e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
                return true;
              }
              targetAnchorIdx = undefined;
              return true;
            },
          },
          'drag-canvas',
          'zoom-canvas',
        ],
      },
      defaultNode: {
        type: 'rect-node',
        size: [110, 36],
        labelCfg: {
          style: {
            fill: 'transparent',
            fontSize: 0,
            boxShadow: '0px 0px 12px rgba(75, 84, 137, 0.05)',
            overflow: 'hidden',
            x: -20,
            y: 0,
            textAlign: 'left',
            textBaseline: 'middle',
          },
        },
        style: {
          fill: '#fff',
          stroke: 'transparent',
          cursor: 'pointer',
          radius: 8,
          shadowColor: 'rgba(75, 84, 137, 0.4)',
          shadowBlur: 6,
          shadowOffsetX: 0,
          shadowOffsetY: 0,
          overflow: 'hidden',
          lineWidth: 0.5,
        },
      },
      defaultEdge: {
        // type: 'cubic-vertical',
        style: {
          endArrow: {
            // 设置终点箭头
            path: G6.Arrow.triangle(3, 3, 3), // 使用内置箭头路径函数，参数为箭头的 宽度、长度、偏移量（默认为 0，与 d 对应）
            d: 4.5,
            fill: '#CDD0DC',
          },
          cursor: 'pointer',
          lineWidth: 1,
          lineAppendWidth: 4,
          opacity: 1,
          stroke: '#CDD0DC',
          radius: 1,
        },
        labelCfg: {
          autoRotate: true,
          style: {
            fontSize: 10,
            fill: '#FFF',
          },
        },
      },
    });

    // 修改历史数据样式问题
    graph.node((node) => {
      return {
        style: {
          stroke: 'transparent',
          radius: 8,
        },
      };
    });

    // 绑定事件
    bindEvents();
  };

  // 绑定事件
  const bindEvents = () => {
    graph.on('node:click', (e) => {
      if (e.target.get('name') !== 'anchor-point' && e.item) {
        openNodeDrawer(e.item);
      }
    });
    graph.on('aftercreateedge', (e) => {
      // update the sourceAnchor and targetAnchor for the newly added edge
      graph.updateItem(e.edge, {
        sourceAnchor: sourceAnchorIdx,
        targetAnchor: targetAnchorIdx,
        type:
          targetAnchorIdx === 0 || targetAnchorIdx === 1 ? 'cubic-vertical' : 'cubic-horizontal',
      });
    });
    // 删除边时，修改 anchor-point 的 links 值
    graph.on('afterremoveitem', (e) => {
      if (e.item && e.item.source && e.item.target) {
        const { source, target, sourceAnchor, targetAnchor } = e.item;
        const sourceNode = graph.findById(source);
        const targetNode = graph.findById(target);
        if (sourceNode && !isNaN(sourceAnchor)) {
          const sourceAnchorShape = sourceNode
            .getContainer()
            .find(
              (ele) =>
                ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === sourceAnchor,
            );
          sourceAnchorShape.set('links', sourceAnchorShape.get('links') - 1);
        }
        if (targetNode && !isNaN(targetAnchor)) {
          const targetAnchorShape = targetNode
            .getContainer()
            .find(
              (ele) =>
                ele.get('name') === 'anchor-point' && ele.get('anchorPointIdx') === targetAnchor,
            );
          targetAnchorShape.set('links', targetAnchorShape.get('links') - 1);
        }
      }
    });
    // after drag on the first node, the edge is created, update the sourceAnchor
    graph.on('afteradditem', (e) => {
      const sourceAnchor = e.item.getModel().sourceAnchor;
      if (e.item && e.item.getType() === 'edge' && !sourceAnchor) {
        graph.updateItem(e.item, {
          sourceAnchor: sourceAnchorIdx,
        });
      }
    });
    graph.on('node:mouseenter', (e) => {
      graph.setItemState(e.item, 'hover', true);
    });
    graph.on('node:mouseleave', (e) => {
      graph.setItemState(e.item, 'hover', false);
    });
    graph.on('node:dragstart', (e) => {
      graph.setItemState(e.item, 'hover', true);
      graph.setItemState(e.item, 'drag', true);
    });
    graph.on('node:dragend', (e) => {
      graph.setItemState(e.item, 'hover', false);
      graph.setItemState(e.item, 'drag', false);
    });
    graph.on('node:dragenter', (e) => {
      if (e.item?.getID() === dragSourceNode?.getID()) return;
      graph.setItemState(e.item, 'hover', true);
      if (e.target.get('name') === 'anchor-point') {
        dropAnchorIdx = e.target.get('anchorPointIdx');
        graph.setItemState(e.item, 'drop', true);
      } else {
        graph.setItemState(e.item, 'drop', false);
      }
    });
    graph.on('node:dragleave', (e) => {
      if (e.item?.getID() === dragSourceNode?.getID()) return;
      graph.setItemState(e.item, 'hover', false);
      graph.setItemState(e.item, 'drop', false);
      dropAnchorIdx = undefined;
    });
    graph.on('node:drop', (e) => {
      graph.setItemState(e.item, 'hover', false);
      graph.setItemState(e.item, 'drop', false);
      dropAnchorIdx = undefined;
    });
  };

  // 上下文菜单
  const initMenu = () => {
    const contextMenu = new G6.Menu({
      className: 'pipeline-context-menu',
      getContent(evt) {
        const type = evt.item.getType();
        const cloneDisplay = type === 'node' ? 'flex' : 'none';
        return `
            <div>
              <div class="pipeline-context-menu__item" style="display: ${cloneDisplay}" id="clone">
                <svg class="pipeline-context-menu__item__icon" id="clone-svg">
                 <use xlink:href="#icon-fuzhi1" />
                </svg>
                复制
              </div>
              <div class="pipeline-context-menu__item" id="delete">
                <svg class="pipeline-context-menu__item__icon" id="delete-svg">
                  <use xlink:href="#icon-shanchu1" />
                </svg>
                删除
               </div>
            </div>`;
      },
      handleMenuClick: (target, item) => {
        const id = target.id;
        if (id.startsWith('clone')) {
          cloneElement(item);
        } else if (id.startsWith('delete')) {
          graph.removeItem(item);
        }
      },
      // offsetX and offsetY include the padding of the parent container
      // 需要加上父级容器的 padding-left 16 与自身偏移量 10
      offsetX: 16 + 10,
      // 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10
      offsetY: 0,
      // the types of items that allow the menu show up
      // 在哪些类型的元素上响应
      itemTypes: ['node', 'edge'],
    });

    return contextMenu;
  };

  return (
    <div className={styles['pipeline-container']}>
      <ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu>
      <div className={styles['pipeline-container__workflow']}>
        <div className={styles['pipeline-container__workflow__top']}>
          <Button
            type="default"
            icon={<KFIcon type="icon-quanjucanshu" />}
            style={{ marginRight: '20px' }}
            onClick={openParamsDrawer}
          >
            全局参数
          </Button>
          <Button
            type="primary"
            icon={<KFIcon type="icon-baocun" />}
            style={{ marginRight: '20px' }}
            onClick={() => {
              savePipeline(false);
            }}
          >
            保存
          </Button>
          <Button
            type="primary"
            style={{
              border: '1px solid',
              borderColor: '#1664ff',
              background: '#fff',
              color: '#1664ff',
            }}
            icon={<KFIcon type="icon-baocunbingfanhui" />}
            onClick={() => {
              savePipeline(true);
            }}
          >
            保存并返回
          </Button>
        </div>
        <div className={styles['pipeline-container__workflow__graph']} ref={graphRef}></div>
      </div>
      <Props ref={propsRef} onFormChange={handleFormChange}></Props>
      <GlobalParamsDrawer
        ref={paramsDrawerRef}
        open={paramsDrawerOpen}
        globalParam={globalParam}
        onClose={closeParamsDrawer}
      ></GlobalParamsDrawer>
    </div>
  );
};
export default EditPipeline;
