<script setup lang="ts">
import { reactive, onMounted, ref, onBeforeUnmount } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import {
  RESULT_CODE_ERROR,
  RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import { listAllNeInfo } from '@/api/ne/neInfo';
import { message } from 'ant-design-vue/es';
import { getGraphData } from '@/api/monitor/topology';
import { parseDateToStr } from '@/utils/date-utils';
import { Graph, GraphData, Menu, Tooltip } from '@antv/g6';
import {
  edgeCubicAnimateCircleMove,
  edgeCubicAnimateLineDash,
  edgeLineAnimateState,
} from '../topologyBuild/hooks/registerEdge';
import {
  nodeCircleAnimateShapeR,
  nodeCircleAnimateShapeStroke,
  nodeImageAnimateState,
  nodeRectAnimateState,
} from '../topologyBuild/hooks/registerNode';
import useNeOptions from '@/views/ne/neInfo/hooks/useNeOptions';
import useI18n from '@/hooks/useI18n';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import { parseBasePath } from '@/plugins/file-static-url';
const { t } = useI18n();
const { fnNeRestart, fnNeStop, fnNeLogFile } = useNeOptions();
const ws = new WS();

/**图DOM节点实例对象 */
const graphG6Dom = ref<HTMLElement | undefined>(undefined);

/**图状态 */
const graphState = reactive<Record<string, any>>({
  /**当前图组名 */
  group: '5GC System Architecture',
  /**图数据 */
  data: {
    combos: [],
    edges: [],
    nodes: [],
  },
});

/**非网元元素 */
const notNeNodes = [
  '5GC',
  'DN',
  'UE',
  'Base',
  'lan',
  'lan1',
  'lan2',
  'lan3',
  'lan4',
  'lan5',
  'lan6',
  'lan7',
  'LAN',
  'NR',
];

/**图实例对象 */
const graphG6 = ref<any>(null);

/**图节点右击菜单 */
const graphNodeMenu = new Menu({
  offsetX: 6,
  offseY: 10,
  itemTypes: ['node'],
  getContent(evt) {
    if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
    const { id, label, neState }: any = evt.item?.getModel();
    if (notNeNodes.includes(id)) {
      return `<div><span>${label || id}</span></div>`;
    }
    if (!neState) {
      return `<div><span>${label || id}</span></div>`;
    }
    return `
    <div
      style="
        display: flex;
        flex-direction: column;
        width: 140px;
      "
    >
      <h3 style="margin-bottom: 8px">
      ${t('views.monitor.topology.name')}:
      ${neState.neName ?? '--'}
      </h3>
      <div id="restart" style="cursor: pointer; margin-bottom: 4px">
        > ${t('views.ne.common.restart')}
      </div>
      <div id="stop" style="cursor: pointer; margin-bottom: 4px;">
        > ${t('views.ne.common.stop')}
      </div>
      <div id="log" style="cursor: pointer; margin-bottom: 4px;">
        > ${t('views.ne.common.log')}
      </div>
    </div>
    `;
  },
  handleMenuClick(target, item) {
    const { neInfo }: any = item?.getModel();
    const { neName, neType, neId } = neInfo;
    const targetId = target.id;
    switch (targetId) {
      case 'restart':
        fnNeRestart({ neName, neType, neId });
        break;
      case 'stop':
        fnNeStop({ neName, neType, neId });
        break;
      case 'log':
        fnNeLogFile({ neType, neId });
        break;
    }
  },
});

/**图节点展示 */
const graphNodeTooltip = new Tooltip({
  offsetX: 10,
  offsetY: 20,
  getContent(evt) {
    if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
    const { id, label, neState }: any = evt.item?.getModel();
    if (notNeNodes.includes(id)) {
      return `<div><span>${label || id}</span></div>`;
    }
    if (!neState) {
      return `<div><span>${label || id}</span></div>`;
    }
    return `
        <div
        style="
            display: flex;
            flex-direction: column;
            width: 200px;
        "
        >
        <div><strong>${t('views.monitor.topology.state')}：</strong><span>
        ${
          neState.online
            ? t('views.monitor.topology.normalcy')
            : t('views.monitor.topology.exceptions')
        }
        </span></div>
        <div><strong>${t('views.monitor.topology.refreshTime')}：</strong><span>
        ${neState.refreshTime ?? '--'}
        </span></div>
        <div>========================</div>
        <div><strong>ID：</strong><span>${neState.neId}</span></div>
        <div><strong>${t('views.monitor.topology.name')}：</strong><span>
        ${neState.neName ?? '--'}
        </span></div>
        <div><strong>IP：</strong><span>${neState.neIP}</span></div>
        <div><strong>${t('views.monitor.topology.version')}：</strong><span>
        ${neState.version ?? '--'}
        </span></div>
        <div><strong>${t('views.monitor.topology.serialNum')}：</strong><span>
        ${neState.sn ?? '--'}
        </span></div>
        <div><strong>${t('views.monitor.topology.expiryDate')}：</strong><span>
        ${neState.expire ?? '--'}
        </span></div>
        </div>
        `;
  },
  itemTypes: ['node'],
});

/**注册自定义边或节点 */
function registerEdgeNode() {
  // 边
  edgeCubicAnimateLineDash();
  edgeCubicAnimateCircleMove();
  edgeLineAnimateState();
  // 节点
  nodeCircleAnimateShapeR();
  nodeCircleAnimateShapeStroke();
  nodeRectAnimateState();
  nodeImageAnimateState();
}

/**图数据渲染 */
function handleRanderGraph(
  container: HTMLElement | undefined,
  data: GraphData
) {
  if (!container) return;
  const { clientHeight, clientWidth } = container;

  // 注册自定义边或节点
  registerEdgeNode();

  const graph = new Graph({
    container: container,
    width: clientWidth,
    height: clientHeight,
    fitCenter: true,
    fitView: true,
    fitViewPadding: [40],
    autoPaint: true,
    modes: {
      default: [
        'drag-combo',
        'drag-canvas',
        'zoom-canvas',
        'collapse-expand-combo',
      ],
    },
    groupByTypes: false,
    nodeStateStyles: {
      selected: {
        fill: 'transparent',
      },
    },
    plugins: [graphNodeMenu, graphNodeTooltip],
    animate: true, // 是否使用动画过度，默认为 false
    animateCfg: {
      duration: 500, // Number，一次动画的时长
      easing: 'linearEasing', // String，动画函数
    },
  });
  graph.data(data);
  graph.render();

  graphG6.value = graph;

  // 创建 ResizeObserver 实例
  var observer = new ResizeObserver(function (entries) {
    // 当元素大小发生变化时触发回调函数
    entries.forEach(function (entry) {
      if (!graphG6.value) {
        return;
      }
      graphG6.value.changeSize(
        entry.contentRect.width,
        entry.contentRect.height - 30
      );
      graphG6.value.fitCenter();
    });
  });
  // 监听元素大小变化
  observer.observe(container);

  return graph;
}

/**
 * 获取图组数据渲染到画布
 * @param reload 是否重载数据
 */
function fnGraphDataLoad(reload: boolean = false) {
  Promise.all([
    getGraphData(graphState.group),
    listAllNeInfo({
      bandStatus: false,
    }),
  ])
    .then(resArr => {
      const graphRes = resArr[0];
      const neRes = resArr[1];
      if (
        graphRes.code === RESULT_CODE_SUCCESS &&
        Array.isArray(graphRes.data.nodes) &&
        graphRes.data.nodes.length > 0 &&
        neRes.code === RESULT_CODE_SUCCESS &&
        Array.isArray(neRes.data) &&
        neRes.data.length > 0
      ) {
        return {
          graphData: graphRes.data,
          neList: neRes.data,
        };
      } else {
        message.warning({
          content: t('views.monitor.topology.noData'),
          duration: 5,
        });
      }
    })
    .then(res => {
      if (!res) return;
      const { combos, edges, nodes } = res.graphData;

      // 节点过滤
      const nf: Record<string, any>[] = nodes.filter(
        (node: Record<string, any>) => {
          Reflect.set(node, 'neState', { online: false });
          // 图片路径处理
          if (node.img) node.img = parseBasePath(node.img);
          if (node.icon.show && node.icon?.img)
            node.icon.img = parseBasePath(node.icon.img);
          // 遍历是否有网元数据
          const nodeID: string = node.id;
          const hasNe = res.neList.some(ne => {
            Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {});
            return ne.neType === nodeID;
          });
          if (hasNe) {
            return true;
          }
          if (notNeNodes.includes(nodeID)) {
            return true;
          }
          return false;
        }
      );

      // 边过滤
      const ef: Record<string, any>[] = edges.filter(
        (edge: Record<string, any>) => {
          const edgeSource: string = edge.source;
          const edgeTarget: string = edge.target;
          const hasNeS = nf.some(n => n.id === edgeSource);
          const hasNeT = nf.some(n => n.id === edgeTarget);
          // console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
          if (hasNeS && hasNeT) {
            return true;
          }
          if (hasNeS && notNeNodes.includes(edgeTarget)) {
            return true;
          }
          if (hasNeT && notNeNodes.includes(edgeSource)) {
            return true;
          }
          return false;
        }
      );

      // 分组过滤
      combos.forEach((combo: Record<string, any>) => {
        const comboChildren: Record<string, any>[] = combo.children;
        combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
        return combo;
      });

      // 图数据
      graphState.data = { combos, edges: ef, nodes: nf };
    })
    .finally(() => {
      if (graphState.data.length < 0) return;
      // 重载数据
      if (reload) {
        graphG6.value.read(graphState.data);
      } else {
        handleRanderGraph(graphG6Dom.value, graphState.data);
      }
      clearInterval(interval10s.value);
      interval10s.value = null;
      fnGetState();
      interval10s.value = setInterval(async () => {
        if (!interval10s.value) return;
        fnGetState(); // 获取网元状态
      }, 20_000);
    });
}

/**网元状态调度器 */
const interval10s = ref<any>(null);

/**查询网元状态 */
function fnGetState() {
  // 获取节点状态
  for (const node of graphState.data.nodes) {
    if (notNeNodes.includes(node.id)) continue;
    const { neType, neId } = node.neInfo;
    if (!neType || !neId) continue;
    ws.send({
      requestId: `${neType}_${neId}`,
      type: 'ne_state',
      data: {
        neType: neType,
        neId: neId,
      },
    });
  }
}

/**接收数据后回调 */
function wsError(ev: any) {
  // 接收数据后回调
  console.error(ev);
}

/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
  const { code, requestId, data } = res;
  if (code === RESULT_CODE_ERROR) {
    console.warn(res.msg);
    return;
  }
  if (!requestId) return;
  const [neType, neId] = requestId.split('_');
  const { combos, edges, nodes } = graphState.data;
  const node = nodes.find((item: Record<string, any>) => item.id === neType);

  // 更新网元状态
  const newNeState = Object.assign(node.neState, data, {
    refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
    online: !!data.cpu,
  });

  // 通过 ID 查询节点实例
  const item = graphG6.value.findById(node.id);
  if (item) {
    const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色
    // 图片类型不能填充
    if (node.type.startsWith('image')) {
      // 更新节点
      if (node.label !== newNeState.neName) {
        graphG6.value.updateItem(item, {
          label: newNeState.neName,
        });
      }
      // 设置状态
      graphG6.value.setItemState(item, 'top-right-dot', stateColor);
    } else {
      // 更新节点
      graphG6.value.updateItem(item, {
        label: newNeState.neName,
        // neState: newNeState,
        style: {
          fill: stateColor, // 填充色
          stroke: stateColor, // 填充色
        },
        // labelCfg: {
        //   style: {
        //     fill: '#ffffff', // 标签文本色
        //   },
        // },
      });
      // 设置状态
      graphG6.value.setItemState(item, 'stroke', newNeState.online);
    }
  }

  // 设置边状态
  for (const edge of edges) {
    const edgeSource: string = edge.source;
    const edgeTarget: string = edge.target;
    const neS = nodes.find((n: any) => n.id === edgeSource);
    const neT = nodes.find((n: any) => n.id === edgeTarget);
    // console.log(neS, edgeSource, neT, edgeTarget);

    if (neS && neT) {
      // 通过 ID 查询节点实例
      // const item = graphG6.value.findById(edge.id);
      // console.log(
      //   `${edgeSource} - ${edgeTarget}`,
      //   neS.neState.online && neT.neState.online
      // );
      // const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
      // 更新边
      // graphG6.value.updateItem(item, {
      //   label: `${edgeSource} - ${edgeTarget}`,
      //   style: {
      //     stroke: stateColor, // 填充色
      //   },
      //   labelCfg: {
      //     style: {
      //       fill: '#ffffff', // 标签文本色
      //     },
      //   },
      // });
      // 设置状态
      graphG6.value.setItemState(
        edge.id,
        'circle-move',
        neS.neState.online && neT.neState.online
      );
    }
    if (neS && notNeNodes.includes(edgeTarget)) {
      graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
    }
    if (neT && notNeNodes.includes(edgeSource)) {
      graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
    }
  }
}

onMounted(() => {
  fnGraphDataLoad(false);

  // 建立链接
  const options: OptionsType = {
    url: '/ws',
    onmessage: wsMessage,
    onerror: wsError,
  };
  ws.connect(options);
});

onBeforeUnmount(() => {
  ws.close();
  clearInterval(interval10s.value);
  interval10s.value = null;
});
</script>

<template>
  <PageContainer>
    <a-card
      :bordered="false"
      :body-style="{ marginBottom: '24px' }"
      size="small"
    >
      <!-- 插槽-卡片左侧侧 -->
      <template #title>
        <a-space :size="8" align="center">
          <span>
            {{ t('views.monitor.topologyBuild.graphGroup') }}：
            {{ graphState.group }}
          </span>
        </a-space>
      </template>
      <!-- 插槽-卡片右侧 -->
      <template #extra>
        <a-button
          type="default"
          size="small"
          @click.prevent="fnGraphDataLoad(true)"
        >
          <template #icon><ReloadOutlined /></template>
          {{ t('common.reloadText') }}
        </a-button>
      </template>

      <div ref="graphG6Dom" class="chart"></div>
    </a-card>
  </PageContainer>
</template>

<style lang="less" scoped>
.chart {
  width: 100%;
  height: calc(100vh - 300px);
  background-color: rgb(43, 47, 51);
}
</style>
