<script setup lang="ts">
import { reactive, onMounted, ref, onBeforeUnmount, useTemplateRef } from 'vue';
import { Graph, GraphData, Menu, Tooltip, Util } from '@antv/g6';
import { listAMFNbStatelist } from '@/api/neData/amf';
import { parseBasePath } from '@/plugins/file-static-url';
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
import useNeListStore from '@/store/modules/ne_list';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { stateNeInfo } from '@/api/ne/neInfo';
import { parseDateToStr } from '@/utils/date-utils';
import { useFullscreen } from '@vueuse/core';
import { listMMENbStatelist } from '@/api/neData/mme';
const { t } = useI18n();
const neListStore = useNeListStore();

/**图DOM节点实例对象 */
const graphG6Dom = useTemplateRef('graphG6Dom');

/**图数据 */
const graphData = reactive<Record<string, any>>({
  nodes: [
    {
      id: 'omc',
      label: 'OMC',
      img: parseBasePath('/svg/service_db.svg'),
    },
    {
      id: 'amf1',
      label: 'amf1',
      img: parseBasePath('/svg/service.svg'),
    },
    {
      id: 'amf2',
      label: 'amf2',
      img: parseBasePath('/svg/service.svg'),
    },
    {
      id: 'base1',
      label: 'base1',
      img: parseBasePath('/svg/base.svg'),
    },
    {
      id: 'base2',
      label: 'base2',
      img: parseBasePath('/svg/base.svg'),
    },
  ],
  edges: [
    {
      source: 'omc',
      target: 'amf1',
    },
    {
      source: 'omc',
      target: 'amf2',
    },
    {
      source: 'amf1',
      target: 'base1',
    },
    {
      source: 'amf2',
      target: 'base1',
    },
    {
      source: 'amf2',
      target: 'base2',
    },
  ],
});

/**图实例对象 */
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, nType, nInfo }: any = evt.item?.getModel();
    if (['GNB', 'ENB'].includes(nType)) {
      return `
      <div
        style="
          display: flex;
          flex-direction: column;
          width: 140px;
        "
      >
        <span>
        ${t('views.neData.baseStation.name')}:
        ${label ?? '--'}
        </span>
      </div>
      `;
    }
    return `
    <div
      style="
        display: flex;
        flex-direction: column;
        width: 140px;
      "
    >
      <span>
      ${t('views.monitor.topology.name')}:
      ${label ?? '--'}
      </span>
    </div>
    `;
  },
});

/**图节点展示 */
const graphNodeTooltip = new Tooltip({
  offsetX: 10,
  offsetY: 20,
  getContent(evt) {
    if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
    const { id, label, nType, nInfo }: any = evt.item?.getModel();
    if (['GNB', 'ENB'].includes(nType)) {
      return `
        <div
        style="
            display: flex;
            flex-direction: column;
            width: 256px;
        "
        >
        <div><strong>${t('views.neData.baseStation.state')}：</strong><span>
        ${nInfo.state === 'ON'
          ? t('views.neData.baseStation.online')
          : t('views.neData.baseStation.offline')
        }
        </span></div>
        <div><strong>${t('views.neData.baseStation.time')}：</strong><span>
        ${nInfo.state === 'ON' ? nInfo.onTime ?? '--' : nInfo.offTime ?? '--'}
        </span></div>
        <div>==============================</div>
        <div><strong>ID：</strong><span>${nInfo.index}</span></div>
        <div><strong>${t('views.neData.baseStation.address')}：</strong><span>
        ${nInfo.address ?? '--'}</span></div>
        <div><strong>${t('views.neData.baseStation.nbName')}：</strong><span>
        ${nInfo.nbName ?? '--'}</span></div>
        <div><strong>${t('views.neData.baseStation.nbId')}：</strong><span>
        ${nInfo.ranId ?? '--'}</span></div>
        <div><strong>${t('views.neData.baseStation.ueNum')}：</strong><span>
        ${nInfo.ueNum ?? '--'}</span></div>
        <div><strong>${t('views.neData.baseStation.name')}：</strong><span>
        ${nInfo.name ?? '--'}
        </span></div>
        <div><strong>${t(
          'views.neData.baseStation.position'
        )}：</strong><span style="word-wrap: break-word;">
        ${nInfo.position}
        </span></div>
        </div>
        `;
    }
    return `
        <div
        style="
            display: flex;
            flex-direction: column;
            width: 200px;
        "
        >
        <div><strong>${t('views.monitor.topology.state')}：</strong><span>
        ${nInfo.online
        ? t('views.monitor.topology.normalcy')
        : t('views.monitor.topology.exceptions')
      }
        </span></div>
        <div><strong>${t('views.monitor.topology.refreshTime')}：</strong><span>
        ${nInfo.refreshTime ?? '--'}
        </span></div>
        <div>========================</div>
        <div><strong>ID：</strong><span>${nInfo.neId}</span></div>
        <div><strong>${t('views.monitor.topology.name')}：</strong><span>
        ${nInfo.neName ?? '--'}
        </span></div>
        <div><strong>IP：</strong><span>${nInfo.neIP}</span></div>
        <div><strong>${t('views.monitor.topology.version')}：</strong><span>
        ${nInfo.version ?? '--'}
        </span></div>
        <div><strong>${t('views.monitor.topology.serialNum')}：</strong><span>
        ${nInfo.sn ?? '--'}
        </span></div>
        <div><strong>${t('views.monitor.topology.expiryDate')}：</strong><span>
        ${nInfo.expire ?? '--'}
        </span></div>
        </div>
        `;
  },
  itemTypes: ['node'],
});

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

/**
 * format the string
 * @param {string} str The origin string
 * @param {number} maxWidth max width
 * @param {number} fontSize font size
 * @return {string} the processed result
 */
function fittingString(str: string, maxWidth: number, fontSize: number) {
  let currentWidth = 0;
  let res = str;
  const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
  str.split('').forEach((letter, i) => {
    if (currentWidth > maxWidth) return;
    if (pattern.test(letter)) {
      // Chinese charactors
      currentWidth += fontSize;
    } else {
      // get the width of single letter according to the fontSize
      currentWidth += Util.getLetterWidth(letter, fontSize);
    }
    if (currentWidth > maxWidth) {
      res = `${str.substring(0, i)}\n${str.substring(i)}`;
    }
  });
  return res;
}

/**图事件 */
function graphEvent(graph: Graph) {
  graph.on('edge:mouseenter', evt => {
    const { item } = evt;
    if (!item) return;
    graph.setItemState(item, 'circle-move', '#b5d6fb');
  });
  graph.on('edge:mouseleave', evt => {
    const { item } = evt;
    if (!item) return;
    graph.setItemState(item, 'circle-move', false);
    graph.setItemState(item, 'circle-move:#b5d6fb', false);
  });
}

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

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

  const graph = new Graph({
    container: container,
    width: clientWidth,
    height: clientHeight,
    fitCenter: false,
    fitView: true,
    fitViewPadding: [40, 40, 40, 40],
    modes: {
      // default: ['drag-canvas', 'drag-node', 'zoom-canvas'],
      default: [
        'zoom-canvas',
        'drag-canvas',
        'drag-node',
        {
          type: 'drag-combo',
          onlyChangeComboSize: true, // 不改变层级关系
        },
        {
          type: 'collapse-expand-combo',
          trigger: 'dblclick',
          relayout: true, // 收缩展开后，不重新布局
        },
      ],
    },
    groupByTypes: false,
    plugins: [graphNodeMenu, graphNodeTooltip],
    layout: {
      type: 'dagre',
      rankdir: 'TB', // 布局的方向，TB：从上到下，BT：从下到上，LR：从左到右，RL：从右到左
      //align: 'UL', // 节点对齐方式 UL、UR、DL、DR
      controlPoints: true,
      nodesep: 20,
      ranksep: 40,
    },
    animate: true,
    defaultNode: {
      type: 'image-animate-state',
      labelCfg: {
        offset: 8,
        position: 'bottom',
        style: { fill: '#ffffff', fontSize: 14, fontWeight: 500 },
      },
      size: 48,
      img: parseBasePath('/svg/cloud.svg'),
      width: 48,
      height: 48,
    },
    defaultEdge: {
      type: 'line-animate-state',
      labelCfg: {
        autoRotate: true,
        refY: 10,
        refX: 40,
      },
      style: {
        stroke: '#fafafa',
        lineWidth: 1.5,
      },
    },
    defaultCombo: {
      labelCfg: {
        offset: 16,
        position: 'bottom',
        style: { fill: '#ffffff', fontSize: 14, fontWeight: 500 },
      },
      style: {
        stroke: '#BDEFDB',
        fill: '#BDEFDB',
        opacity: 0.25,
      },
      collapsedSubstituteIcon: {
        show: true,
        img: parseBasePath('/svg/service.svg'),
        width: 48,
        height: 48,
      },
    },
  });
  graph.data(data);
  graph.render();
  graphEvent(graph);

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

  return graph;
}

/**
 * 获取图组数据渲染到画布
 */
async function fnGraphDataLoad() {
  const dataNe = await fnGraphDataBase();
  Object.assign(graphData, dataNe);
  graphG6.value = handleRanderGraph(graphG6Dom.value, dataNe);
  // 添加基站
  const dataNb = await fnGraphDataNb(dataNe);
  Object.assign(graphData, dataNb);
  // graphG6.value.clear();
  graphG6.value.read(dataNb);
  // 添加状态
  interval.value = true;
  repeatFn();
}

/**图数据网元构建 */
async function fnGraphDataBase() {
  const data: GraphData = {
    nodes: [],
    edges: [],
  };
  // 添加基础网元
  for (const item of neListStore.getNeCascaderOptions) {
    if ('OMC' === item.value) {
      if (item.children?.length === 0) continue;
      // 是否存在OMC保证唯一
      const hasOMC = data.nodes?.findIndex(v => v.neType === 'OMC');
      if (hasOMC !== -1) continue;
      // 根网元
      const omcInfo = item.children[0];
      const node = {
        id: 'OMC',
        label: omcInfo.neName,
        img: parseBasePath('/svg/service_db.svg'),
        nInfo: { online: false, neId: omcInfo.neId, neType: omcInfo.neType },
        nType: 'OMC',
      };
      // 添加OMC节点
      data.nodes?.push(node);
      continue;
    }
    if (['AMF', 'MME'].includes(item.value)) {
      if (item.children?.length === 0) continue;
      for (const child of item.children) {
        const id = `${child.neType}_${child.neId}`;
        const node = {
          id: id,
          label: child.neName,
          img: parseBasePath('/svg/service.svg'),
          nInfo: { online: false, neId: child.neId, neType: child.neType },
          nType: item.value,
        };
        // 添加节点
        data.nodes?.push(node);
        data.edges?.push({
          source: 'OMC',
          target: id,
        });
      }
      item.children.forEach((v: any) => { });
      continue;
    }
  }
  return data;
}

/**图数据基站构建 */
async function fnGraphDataNb(data: GraphData) {
  const arr = data.nodes?.filter((v: any) => ['AMF', 'MME'].includes(v.nType));
  if (arr === undefined || arr.length === 0) return data;
  for (const item of arr) {
    if (item.nType === 'AMF') {
      const neId = (item.nInfo as any).neId;
      const res = await listAMFNbStatelist({ neId });
      if (res.code !== RESULT_CODE_SUCCESS || !Array.isArray(res.data)) {
        continue;
      }
      for (const nb of res.data) {
        const id = `${item.id}_${nb.index}`;
        data.nodes?.push({
          id: id,
          label: fittingString(`${nb.name}`, 80, 14),
          img: parseBasePath('/svg/base5G.svg'),
          nInfo: nb,
          nType: 'GNB',
        });
        data.edges?.push({
          source: item.id,
          target: id,
        });
      }
    }
    if (item.nType === 'MME') {
      const neId = (item.nInfo as any).neId;
      const res = await listMMENbStatelist({ neId });
      if (res.code !== RESULT_CODE_SUCCESS || !Array.isArray(res.data)) {
        continue;
      }
      for (const nb of res.data) {
        const id = `${item.id}_${nb.index}`;
        data.nodes?.push({
          id: id,
          label: fittingString(`${nb.name}`, 80, 14),
          img: parseBasePath('/svg/base4G.svg'),
          nInfo: nb,
          nType: 'ENB',
        });
        data.edges?.push({
          source: item.id,
          target: id,
        });
      }
    }
  }
  return data;
}

/**
 * 图状态构建
 * @param reload 是否重载状态
 */
async function fnGraphState(reload: boolean = false) {
  // 节点状态
  if (!Array.isArray(graphData.nodes)) return;

  const onc = graphData.nodes.find((v: any) => v.nType === 'OMC');
  if (onc) {
    const { id, nInfo } = onc as any;
    if (!nInfo) return;
    const res = await stateNeInfo(nInfo.neType, nInfo.neId);
    if (res.code === RESULT_CODE_SUCCESS) {
      Object.assign(nInfo, res.data, {
        refreshTime: parseDateToStr(res.data.refreshTime, 'HH:mm:ss'),
      });
    }
    const stateColor = nInfo.online ? '#52c41a' : '#f5222d'; // 状态颜色
    graphG6.value.setItemState(id, 'top-right-dot', stateColor);
  }

  graphData.nodes
    .filter((v: any) => ['AMF', 'MME'].includes(v.nType))
    .forEach(async (v: any) => {
      const { id, nInfo } = v;
      if (!nInfo) return;
      const res = await stateNeInfo(nInfo.neType, nInfo.neId);
      if (res.code === RESULT_CODE_SUCCESS) {
        Object.assign(nInfo, res.data, {
          refreshTime: parseDateToStr(res.data.refreshTime, 'HH:mm:ss'),
        });
      }
      const stateColor = nInfo.online ? '#52c41a' : '#f5222d'; // 状态颜色
      graphG6.value.setItemState(id, 'top-right-dot', stateColor);

      // 重载时更新下级基站状态
      if (reload && nInfo.neType === 'AMF') {
        const res = await listAMFNbStatelist({ neId: nInfo.neId });
        if (res.code == RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
          for (const nb of res.data) {
            const nbItem = graphData.nodes.find(
              (v: any) => v.id === `${id}_${nb.index}`
            );
            if (nbItem) {
              Object.assign(nbItem.nInfo, nb);
              const stateColor = nb.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
              graphG6.value.setItemState(
                nbItem.id,
                'top-right-dot',
                stateColor
              );
            }
          }
        }
      }
      if (reload && nInfo.neType === 'MME') {
        const res = await listMMENbStatelist({ neId: nInfo.neId });
        if (res.code == RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
          for (const nb of res.data) {
            const nbItem = graphData.nodes.find(
              (v: any) => v.id === `${id}_${nb.index}`
            );
            if (nbItem) {
              Object.assign(nbItem.nInfo, nb);
              const stateColor = nb.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
              graphG6.value.setItemState(
                nbItem.id,
                'top-right-dot',
                stateColor
              );
            }
          }
        }
      }
    });

  if (reload) {
    await new Promise(resolve => setTimeout(resolve, 15_000));
    return;
  }
  // 非重载时使用初始获取的状态
  graphData.nodes
    .filter((v: any) => ['GNB', 'ENB'].includes(v.nType))
    .forEach(async (v: any) => {
      const { id, nInfo } = v;
      if (!nInfo) return;
      const stateColor = nInfo.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
      graphG6.value.setItemState(id, 'top-right-dot', stateColor);
    });
}

/**递归调度器 */
const interval = ref<boolean>(false);

/**递归刷新图状态 */
function repeatFn(reload: boolean = false) {
  if (!interval.value || !graphG6Dom.value) {
    return;
  }
  fnGraphState(reload)
    .finally(() => {
      repeatFn(true); // 递归调用自己
    })
    .catch(error => {
      console.error(error);
    });
}

const viewportDom = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(viewportDom);
function fullscreen() {
  toggle();

  if (!graphG6Dom.value) return;
  if (isFullscreen.value) {
    graphG6Dom.value.style.height = 'calc(100vh - 300px)';
  } else {
    graphG6Dom.value.style.height = '100vh';
  }
  const { clientHeight, clientWidth } = graphG6Dom.value;
  graphG6.value.changeSize(clientHeight, clientWidth);
  graphG6.value.fitView(40);
}

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

onBeforeUnmount(() => {
  interval.value = false;
});
</script>

<template>
  <a-card :bordered="false" :body-style="{ padding: '0' }" ref="viewportDom">
    <!-- 插槽-卡片左侧侧 -->
    <template #title>
      <a-space :size="8" align="center">
        {{ t('views.neData.baseStation.topologyTitle') }}
      </a-space>
    </template>
    <!-- 插槽-卡片右侧 -->
    <template #extra>
      <a-button type="default" @click.prevent="fullscreen()">
        <template #icon>
          <FullscreenExitOutlined v-if="isFullscreen" />
          <FullscreenOutlined v-else />
        </template>
        {{ t('loayouts.rightContent.fullscreen') }}
      </a-button>
    </template>

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

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