<script lang="ts" setup>
import { traduction } from '@/utils/language';
import { Edge, Graph, Node, Shape } from '@antv/x6';
import { onMounted, ref } from 'vue';
import { getNetDataPort, getTx } from '../../net.server';
import { netFields, opticalModuleFieldsInfo } from './OpenRowDataFunctions';
import { initShowData } from '../../netFunctions';

const containerRef = ref<HTMLDivElement>();
const graph = ref<Graph>();
const selectedNode = ref<Node>();
const hiddenEdgesData = ref<Map<string, any>>(new Map());
const infoPanelNode = ref<Node | null>(null);
const portLaneMapping = ref<Map<number, [number, string, number][]>>(new Map());
const opticalLaneMapping = ref<Map<number, [number, string, number][]>>(new Map());

const props = defineProps<{
  adapterId: string;
  topoData: any;
  txData: any;
}>();

interface Position {
  x: number;
  y: number;
}

abstract class RoutedNode {
  protected rect: Node = null as unknown as Node;
  constructor(
    public width: number,
    public height: number,
    public label: string,
    public position: Position,
  ) { }

  public get nodeRect(): Node {
    return this.rect;
  }
}

function sizedNode(width: number, height: number) {
  return class extends RoutedNode {
    constructor(
      public label: string,
      public iconURL: string,
      public position: Position,
    ) {
      super(width, height, label, position);
    }
  };
}

const STROKE_WIDTH = 1;
const NODE_GAP = 16;
abstract class NodeManager {
  public nodes: RoutedNode[] = [];
  protected static instance: NodeManager;
  protected constructor(protected nodePerRow: number, protected gap: number) { }

  public static getInstance(): NodeManager {
    return NodeManager.instance;
  }

  public getNewPosition(): Position {
    const newRow = this.nodes.length % this.nodePerRow === 0;
    const lastNode = this.nodes[this.nodes.length - 1];
    const x = newRow ? this.nodes[0].position.x : lastNode.position.x + lastNode.width + this.gap + (STROKE_WIDTH * 2);
    const y = newRow ? lastNode.position.y + lastNode.height + this.gap + (STROKE_WIDTH * 2) : lastNode.position.y;
    return { x, y };
  }

  public addNode(node: RoutedNode): void {
    this.nodes.push(node);
    graph.value?.addNode(node.nodeRect);
  }
}

const PORT_NODE_PER_ROW = 8;
class PortNodeManager extends NodeManager {
  protected static instance: PortNodeManager;
  protected constructor(protected nodePerRow: number, protected gap: number) {
    super(nodePerRow, gap);
  }

  public static getInstance(): PortNodeManager {
    if (!PortNodeManager.instance) {
      PortNodeManager.instance = new PortNodeManager(PORT_NODE_PER_ROW, NODE_GAP);
    }
    return PortNodeManager.instance;
  }

  override getNewPosition(): Position {
    if (this.nodes.length === 0) {
      return { x: 162, y: 54 };
    }
    return super.getNewPosition();
  }

  public newNode(label: string, nodeId: number): PortNode {
    const node = new PortNode(label, nodeId);
    this.addNode(node);
    return node;
  }
}

const OPTICAL_NODE_PER_ROW = 4;
class OpticalNodeManager extends NodeManager {
  protected static instance: OpticalNodeManager;
  protected constructor(protected nodePerRow: number, protected gap: number) {
    super(nodePerRow, gap);
  }

  public static getInstance(): OpticalNodeManager {
    if (!OpticalNodeManager.instance) {
      OpticalNodeManager.instance = new OpticalNodeManager(OPTICAL_NODE_PER_ROW, NODE_GAP);
    }
    return OpticalNodeManager.instance;
  }

  override getNewPosition(): Position {
    if (this.nodes.length === 0) {
      return { x: 162, y: 324 };
    }
    return super.getNewPosition();
  }

  public newNode(label: string, nodeId: number): OpticalNode {
    const node = new OpticalNode(label, nodeId);
    this.addNode(node);
    return node;
  }
}

const PORT_NODE_WIDTH = 108;
const PORT_NODE_HEIGHT = 36;
const PORT_NODE_ICON_URL = '/UI/Static/src/assets/common/image/port.svg';
class PortNode extends sizedNode(PORT_NODE_WIDTH, PORT_NODE_HEIGHT) {
  constructor(label: string, nodeId: number) {
    const manager = PortNodeManager.getInstance();
    super(
      label,
      PORT_NODE_ICON_URL,
      manager.getNewPosition(),
    );

    this.rect = new Shape.Rect({
      ...this.position,
      width: this.width,
      height: this.height,
      markup: [
        { tagName: 'rect', selector: 'body' },
        {
          tagName: 'svg',
          selector: 'iconWrap',
          children: [
            { tagName: 'use', selector: 'icon' },
          ]
        },
        { tagName: 'text', selector: 'label' }
      ],
      attrs: {
        body: {
          rx: 4,
          ry: 4,
          fill: '#f4f6fa',
          stroke: '#c3cedf',
          strokeWidth: STROKE_WIDTH,
          cursor: 'pointer',
        },
        iconWrap: {
          width: 16,
          height: 16,
          x: 8,
          y: 10,
        },
        icon: {
          'xlink:href': PORT_NODE_ICON_URL,
          cursor: 'pointer',
          color: '#8d98aa',
        },
        label: {
          refX: 32,
          'text-anchor': 'start',
          fontSize: 12,
          fill: '#4e5865',
          cursor: 'pointer',
          text: this.label,
        }
      },
      ports: {
        groups: {
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                r: 4,
                magnet: false,
                stroke: '#7AB8FF',
                strokeWidth: 1,
                fill: '#7AB8FF',
                cursor: 'pointer',
              },
            },
          },
        },
        items: [
          { group: 'bottom' },
          { group: 'bottom' },
          { group: 'bottom' },
          { group: 'bottom' },
        ],
      },
      data: {
        type: 'port',
        nodeId,
      }
    });
  }
}

const OPTICAL_NODE_WIDTH = 234;
const OPTICAL_NODE_HEIGHT = 72;
const OPTICAL_NODE_ICON_URL = '/UI/Static/src/assets/common/image/optical.svg';
class OpticalNode extends sizedNode(OPTICAL_NODE_WIDTH, OPTICAL_NODE_HEIGHT) {
  constructor(label: string, nodeId: number) {
    const manager = OpticalNodeManager.getInstance();
    super(
      label,
      OPTICAL_NODE_ICON_URL,
      manager.getNewPosition(),
    );

    this.rect = new Shape.Rect({
      ...this.position,
      width: this.width,
      height: this.height,
      markup: [
        { tagName: 'rect', selector: 'body' },
        {
          tagName: 'svg', selector: 'iconWrap', children: [
            { tagName: 'use', selector: 'icon' },
          ]
        },
        { tagName: 'text', selector: 'label' },
        ...Array(4).fill(true).map((_, i) => ({
          tagName: 'rect',
          selector: `portGroup${i + 1}`,
        })),
        ...Array(8).fill(true).map((_, i) => ({
          tagName: 'text',
          selector: `portLabel${i + 1}`,
        })),
        ...Array(8).fill(true).map((_, i) => ({
          tagName: 'rect',
          selector: `actionHitArea${i + 1}`,
          attrs: {
            fill: '#fff',
            fillOpacity: 0,
            strokeOpacity: 0,
            width: 26,
            height: 28,
            x: 9 + (27 * i),
            y: 32,
            cursor: 'pointer',
            event: `node:${label}|${i + 1}:click`,
          }
        })),
      ],
      attrs: {
        body: {
          rx: 4,
          ry: 4,
          fill: '#f4f6fa',
          stroke: '#c3cedf',
          strokeWidth: STROKE_WIDTH,
          cursor: 'pointer',
        },
        iconWrap: {
          width: 16,
          height: 16,
          x: 8,
          y: 8,
          cursor: 'pointer',
        },
        icon: {
          'xlink:href': this.iconURL,
          color: '#8d98aa',
        },
        label: {
          refX: 32,
          refY: 16,
          'text-anchor': 'start',
          fontSize: 12,
          fill: '#4e5865',
          cursor: 'pointer',
          text: this.label,
        },
        ...Array(4).fill(true).map((_, i) => ({
          [`portGroup${i + 1}`]: {
            x: 9 + (54 * i),
            y: 32,
            width: 52,
            height: 29,
            fill: '#ffffff',
            stroke: '#c3cedf',
            strokeWidth: 1,
          },
        }),).reduce((acc, cur) => ({ ...acc, ...cur }), {}),
        ...Array(8).fill(true).map((_, i) => ({
          [`portLabel${i + 1}`]: {
            refX: 22 + (26 * i) + (Math.floor(i / 2) * 2),
            refY: 48,
            'text-anchor': 'middle',
            fontSize: 12,
            fill: '#4e5865',
            cursor: 'pointer',
            text: `${i + 1}`,
          }
        }),).reduce((acc, cur) => ({ ...acc, ...cur }), {}),
      },
      ports: {
        groups: {
          inner: {
            position: 'absolute',
            attrs: {
              circle: {
                r: 4,
                magnet: false,
                stroke: '#7AB8FF',
                strokeWidth: 1,
                fill: '#7AB8FF',
                cursor: 'pointer',
              },
            },
          },
        },
        items: Array(8).fill(true).map((_, i) => (
          { group: 'inner', args: { x: 22 + (26 * i) + (Math.floor(i / 2) * 2), y: 60 } })),
      },
      data: {
        type: 'optical',
        nodeId,
      }
    });
  }
}


function highlightPortNode(node: Node): void {
  node.attr('body/stroke', '#0077ff');
  node.attr('body/stroke-width', 2);
  node.attr('icon/color', '#0077ff');
  node.attr('label/fontWeight', 'bold');
  node.attr('label/fill', '#000000');
}

function dehighlightPortNode(node: Node): void {
  node.attr('body/stroke', '#c3cedf');
  node.attr('body/stroke-width', STROKE_WIDTH);
  node.attr('icon/color', '#8d98aa');
  node.attr('label/fontWeight', 'normal');
  node.attr('label/fill', '#4e5865');
}

function highlightOpticalNode(node: Node): void {
  node.attr('body/stroke', '#0077ff');
  node.attr('body/stroke-width', 2);
  node.attr('icon/color', '#0077ff');
  node.attr('label/fontWeight', 'bold');
  node.attr('label/fill', '#000000');
  for (let i = 1; i <= 4; i++) {
    node.attr(`portGroup${i}/stroke`, '#7AB8FF');
  }
  for (let i = 1; i <= 8; i++) {
    node.attr(`portLabel${i}/fill`, '#7AB8FF');
  }
}

function dehighlightOpticalNode(node: Node): void {
  node.attr('body/stroke', '#c3cedf');
  node.attr('body/stroke-width', STROKE_WIDTH);
  node.attr('icon/color', '#8d98aa');
  node.attr('label/fontWeight', 'normal');
  node.attr('label/fill', '#4e5865');
  for (let i = 1; i <= 4; i++) {
    node.attr(`portGroup${i}/stroke`, '#c3cedf');
  }
  for (let i = 1; i <= 8; i++) {
    node.attr(`portLabel${i}/fill`, '#4e5865');
    node.attr(`portLabel${i}/fontWeight`, 'normal');
  }
}

function getEdgePortInfo(edge: Edge) {
  const sourcePortId = edge.getSourcePortId();
  const targetPortId = edge.getTargetPortId();
  const sourceNode = edge.getSourceNode();
  const targetNode = edge.getTargetNode();
  
  return {
    sourceNode,
    targetNode,
    sourcePortId,
    targetPortId,
    sourcePort: sourceNode?.getPort(sourcePortId),
    targetPort: targetNode?.getPort(targetPortId),
  };
}

function highlightEdge(edge: Edge): void {
  edge.attr('line/stroke', '#0077ff');
  edge.attr('line/stroke-width', 2);
  const { sourceNode, targetNode, sourcePortId, targetPortId } = getEdgePortInfo(edge);
  sourceNode.setPortProp(sourcePortId, 'attrs/circle/fill', '#0077ff');
  sourceNode.setPortProp(sourcePortId, 'attrs/circle/stroke', '#0077ff');
  targetNode.setPortProp(targetPortId, 'attrs/circle/fill', '#0077ff');
  targetNode.setPortProp(targetPortId, 'attrs/circle/stroke', '#0077ff');
}

function dehighlightEdge(edge: Edge): void {
  edge.attr('line/stroke', '#7AB8FF');
  edge.attr('line/stroke-width', 1);
  const { sourceNode, targetNode, sourcePortId, targetPortId } = getEdgePortInfo(edge);
  sourceNode.setPortProp(sourcePortId, 'attrs/circle/fill', '#7AB8FF');
  sourceNode.setPortProp(sourcePortId, 'attrs/circle/stroke', '#7AB8FF');
  targetNode.setPortProp(targetPortId, 'attrs/circle/fill', '#7AB8FF');
  targetNode.setPortProp(targetPortId, 'attrs/circle/stroke', '#7AB8FF');
}

function getRelatedNodes(node: Node): Node[] {
  const edges = graph.value?.getConnectedEdges(node) || [];
  const relatedNodes: Node[] = [];
  edges.forEach(edge => {
    const sourceNode = edge.getSourceNode();
    const targetNode = edge.getTargetNode();
    if (sourceNode && sourceNode.id !== node.id) {
      relatedNodes.push(sourceNode);
    }
    if (targetNode && targetNode.id !== node.id) {
      relatedNodes.push(targetNode);
    }
  });
  return relatedNodes;
}

function getNodeEdges(node: Node): Edge[] {
  return graph.value?.getConnectedEdges(node) || [];
}

function hidePortsAndEdges(node: Node): void {
  node.getPorts().forEach(port => {
    node.setPortProp(port.id as string, 'attrs/circle/visibility', 'hidden');
  });
  
  clearSecondaryHighlight(node);

  getNodeEdges(node).forEach(edge => {
    const edgeData = {
      id: edge.id,
      source: edge.getSource(),
      target: edge.getTarget(),
      router: edge.getRouter(),
      attrs: edge.getAttrs(),
      zIndex: edge.getZIndex(),
      data: edge.getData(),
    };
    hiddenEdgesData.value.set(edge.id, edgeData);
    
    graph.value?.removeEdge(edge.id);
  });
}

function showPortsAndEdges(node: Node): void {
  const nodeEdgeIds = Array.from(hiddenEdgesData.value.keys()).filter(edgeId => {
    const edgeData = hiddenEdgesData.value.get(edgeId);
    return edgeData?.source?.cell === node.id || edgeData?.target?.cell === node.id;
  });
  
  nodeEdgeIds.forEach(edgeId => {
    const edgeData = hiddenEdgesData.value.get(edgeId)!;
    graph.value?.addEdge({
      id: edgeData.id,
      source: edgeData.source,
      target: edgeData.target,
      router: edgeData.router,
      attrs: edgeData.attrs,
      zIndex: edgeData.zIndex,
      data: edgeData.data,
    });
    hiddenEdgesData.value.delete(edgeId);
  });
  
  const connectedEdges = graph.value?.getConnectedEdges(node) || [];
  const connectedPorts = node.getPorts().filter(port => {
    return connectedEdges.some(edge => {
      const sourcePortId = edge.getSourcePortId();
      const targetPortId = edge.getTargetPortId();
      return port.id === sourcePortId || port.id === targetPortId;
    });
  });
  
  connectedPorts.forEach(port => {
    node.setPortProp(port.id as string, 'attrs/circle/visibility', 'visible');
  });
}

function getEdgesBetweenNodes(node1: Node, node2: Node): Edge[] {
  const edges1 = graph.value?.getConnectedEdges(node1) || [];
  const edges2 = graph.value?.getConnectedEdges(node2) || [];
  const edgesBetween: Edge[] = [];
  edges1.forEach(edge1 => {
    edges2.forEach(edge2 => {
      if (edge1.id === edge2.id) {
        edgesBetween.push(edge1);
      }
    });
  });
  return edgesBetween;
}

function secondaryHighlightRelatedNodes(node: Node): void {
  const relatedNodes = getRelatedNodes(node);
  relatedNodes.forEach(relatedNode => {
    relatedNode.attr('body/stroke', '#7AB8FF');
    const edges = getEdgesBetweenNodes(node, relatedNode);
    setTimeout(() => {
      edges.forEach(edge => {
        const info = getEdgePortInfo(edge);
        if (info.sourceNode && info.sourcePortId) {
          info.sourceNode.setPortProp(info.sourcePortId, 'attrs/circle/visibility', 'visible');
        }
        if (info.targetNode && info.targetPortId) {
          info.targetNode.setPortProp(info.targetPortId, 'attrs/circle/visibility', 'visible');
          if (info.targetNode.getData()?.type === 'optical') {
            const targetPortIndex = info.targetNode.getPorts().findIndex(port => port.id === info.targetPortId);
            info.targetNode.attr(`portLabel${(Math.floor(targetPortIndex / 2) * 2) + 1}/fill`, '#0077ff');
            info.targetNode.attr(`portLabel${(Math.floor(targetPortIndex / 2) * 2) + 2}/fill`, '#0077ff');
            info.targetNode.attr(`portLabel${targetPortIndex + 1}/stroke-width`, 2);
            info.targetNode.attr(`portGroup${Math.ceil((targetPortIndex + 1) / 2)}/stroke`, '#0077ff');
          }
        }
      });
    });
  });
}

function clearSecondaryHighlight(node: Node): void {
  const relatedNodes = getRelatedNodes(node);
  relatedNodes.forEach(relatedNode => {
    dehighlightNode(relatedNode, false);
  });
}

function highlightNode(node: Node): void {
  showPortsAndEdges(node);
  const type = node.getData()?.type;
  if (type === 'port') {
    highlightPortNode(node);
  } else if (type === 'optical') {
    highlightOpticalNode(node);
  } else {
    // do nothing
  }
  secondaryHighlightRelatedNodes(node);
}

function dehighlightNode(node: Node, recursive = true): void {
  if (recursive) {
    hidePortsAndEdges(node);
  } else {
    node.getPorts().forEach(port => {
    node.setPortProp(port.id as string, 'attrs/circle/visibility', 'hidden');
  });
  }
  const type = node.getData()?.type;
  if (type === 'port') {
    dehighlightPortNode(node);
  } else if (type === 'optical') {
    dehighlightOpticalNode(node);
  } else {
    // do nothing
  }
}

function dehighlightAllEdges(): void {
  graph.value?.getEdges().forEach(edge => {
    dehighlightEdge(edge);
  });
}

function getPanelHeight(type: 'port' | 'optical'): number {
  if (type === 'port') {
    return 374;
  } else if (type === 'optical') {
    return 518;
  } else {
    return 180;
  }
}

async function showInfoPanel(node: Node, type: 'port' | 'optical'): Promise<void> {
  if (infoPanelNode.value) {
    graph.value?.removeNode(infoPanelNode.value.id);
  }

  const nodePosition = node.position();
  const nodeSize = node.size();
  const portId = node.getData().nodeId;
  let infoData;
  if (type === 'port') {
    infoData = await getNetDataPort(props.adapterId, portId + 1);
  } else {
    const url = props.txData.filter((item: any) => item.OpticalID === `${portId}`)[0]?.url;
    infoData = await getTx(url);
  }

  const panel = graph.value?.addNode({
    shape: 'info-panel',
    x: nodePosition.x + nodeSize.width + 20,
    y: nodePosition.y,
    width: 342,
    height: getPanelHeight(type),
    zIndex: 1000,
    data: {
      type,
      nodeData: {
        label: node.attr('label/text'),
        id: node.getData().nodeId,
        ports: node.getPorts().length,
        connections: getNodeEdges(node).length,
      },
      infoData,
    }
  });

  infoPanelNode.value = panel ?? null;
}

function closeInfoPanel() {
  if (infoPanelNode.value) {
    graph.value?.removeNode(infoPanelNode.value.id);
    infoPanelNode.value = null;
  }
}

function portInfoTemplate(infoData: any): string {
  const list: string[] = [];
  const processedData: any[] = [];
  initShowData(infoData.NetworkAttribute, netFields, processedData);
  processedData.forEach(item => {
    list.push(`
      <div class="infoTemplate">
        <span class="infoTemplateLabel">${item.label}</span>
        <span>${item.value}</span>
      </div>
    `);
  });
  return list.join('');
}

function opticalInfoTemplate(infoData: any): string {
  const list: string[] = [];
  const processedData: any[] = [];
  initShowData(infoData, opticalModuleFieldsInfo, processedData);
  processedData.forEach(item => {
    list.push(`
      <div class="infoTemplate">
        <span class="infoTemplateLabel">${item.label}</span>
        <span>${item.value}</span>
      </div>
    `);
  });
  return list.join('');
}

function associatedLane(type: 'port' | 'optical', id: number): string {
  const list: string[] = [];
  const mapping = type === 'port' ? portLaneMapping : opticalLaneMapping;
  mapping.value.get(id)?.forEach(([sourceLane, targetNode, targetLane]) => {
    list.push(`
      <div class="infoTemplate">
        <span class="infoTemplateLabel">Lane&nbsp;${sourceLane}</span>
        <div class="infoTemplateTag">
          <div>
            <span class="infoTemplateTagSpan">${targetNode}</span>
          </div>
          <span>Lane&nbsp;${targetLane}</span>
        </div>
      </div>
    `);
  });
  return list.join('');
}

onMounted(() => {
  Shape.HTML.register({
      shape: 'info-panel',
      inherit: 'html',
      width: 342,
      height: 200,
      html(cell) {
        const data = cell.getData();
        const type = data.type;
        const id = data.nodeData.id;
        const container = document.createElement('div');
        container.className = 'x6-info-panel';
        container.style.background = '#FDFEFFB3';
        container.style.backdropFilter = 'blur(10px)';
        container.style.boxShadow = '0px 2px 20px 6px #0000001a';
        container.style.height = '100%';
        container.style.borderRadius = '4px';
        container.style.overflowY = 'auto';
        const title = type === 'port' ? traduction('NET_NIC_PORT') : traduction('NET_OPTICAL');
        container.innerHTML = `
          <div class="infoPanel">
            <h3 class="infoPanelTitle">${title}</h3>
            <button class="close-btn">×</button>
          </div>
          <div class="detailInfo">
            ${type === 'port' ? portInfoTemplate(data.infoData) : opticalInfoTemplate(data.infoData)}
          </div>
          <div class="associateInfoTitle">
            <h3 class="infoPanelTitle">${traduction(type === 'port' ? 'NET_OPTICAL_RELATED' : 'NET_PORT_RELATED')}</h3>
          </div>
          <div class="associateInfo">
            ${associatedLane(type, id)}
          </div>
        `;
        const closeBtn = container.querySelector('.close-btn');
        closeBtn?.addEventListener('click', () => {
          graph.value?.removeNode(cell.id);
        });

        return container;
      }
    },
  );
  graph.value = new Graph({
    panning: true,
    container: containerRef.value!,
    mousewheel: {
      enabled: true,
      zoomAtMousePosition: true,
      modifiers: 'ctrl',
    },
    interacting: {
      nodeMovable: false,
      edgeMovable: false,
      edgeLabelMovable: false,
      vertexAddable: false,
      vertexMovable: false,
      arrowheadMovable: false,
      useEdgeTools: false,
      vertexDeletable: false,
    },
    connecting: {
      createEdge() {
        return this.createEdge({
          zIndex: 0,
        });
      },
    },
    grid: {
      size: 18,
      visible: true,
      type: 'mesh',
      args: [
        {
          color: '#ccc',
          thickness: 1,
        },
      ],
    },
  });

  graph.value.addNode({
    markup: [
      {
        tagName: 'text',
        selector: 'label1',
      },
      {
        tagName: 'text',
        selector: 'label2',
      },
    ], 
    attrs: {
      label1: {
        refX: 18,
        refY: 26,
        text: traduction('NET_NIC_PORT'),
        fontSize: 16,
        'text-anchor': 'start',
        fill: '#4e5865',
      },
      label2: {
        refX: 18,
        refY: 296,
        text: traduction('NET_OPTICAL'),
        fontSize: 16,
        'text-anchor': 'start',
        fill: '#4e5865',
      },
    }
  });

  graph.value.on('node:click', ({ node }) => {
    const nodeType = node.getData()?.type;
    if (node.shape === 'info-panel' || !nodeType) {
      return;
    }
    if (selectedNode.value && selectedNode.value !== node) {
      dehighlightNode(selectedNode.value);
    }
    clearSecondaryHighlight(selectedNode.value!);
    highlightNode(node);
    dehighlightAllEdges();
    selectedNode.value = node;

    showInfoPanel(node, nodeType);
  });

  graph.value.on('node:mouseenter', ({ node }) => {
    if (node !== selectedNode.value) {
      node.attr('body/stroke', '#7AB8FF');
    }
  });

  graph.value.on('node:mouseleave', ({ node }) => {
    if (node !== selectedNode.value) {
      node.attr('body/stroke', '#c3cedf');
    }
  });

  graph.value.on('edge:click', ({ edge }) => {
    dehighlightAllEdges();
    highlightEdge(edge);
    const info = getEdgePortInfo(edge);
    const portIndex = info.targetNode?.getPorts().findIndex(port => port.id === info.targetPortId);
    if (info.targetNode && portIndex !== undefined && portIndex >= 0) {
      info.targetNode.attr(`portLabel${portIndex + 1}/fontWeight`, 'bold');
    }
  });

  graph.value.on('blank:click', () => {
    if (selectedNode.value) {
      dehighlightNode(selectedNode.value);
      clearSecondaryHighlight(selectedNode.value);
      selectedNode.value = null as unknown as Node;
    }
    closeInfoPanel();
  });

  const portNodeManager = PortNodeManager.getInstance();
  const opticalNodeManager = OpticalNodeManager.getInstance();
  const opticalTransceiverNameMap = new Map<number, string>();

  props.topoData.forEach((item: any) => {
    portNodeManager.newNode(`Port ${item.PortId + 1}`, item.PortId);
    item.LaneMap.forEach((mapping: any) => {
      if (!opticalTransceiverNameMap.has(mapping.Transceiver.Id)) {
        opticalTransceiverNameMap.set(mapping.Transceiver.Id, mapping.Transceiver.SilkText);
      }
    });
  });

  const transceviers = [...opticalTransceiverNameMap.entries()]
    .sort((a, b) => a[0] - b[0]);
  transceviers.forEach(item => opticalNodeManager.newNode(item[1], item[0]));

  props.topoData.forEach((item: any) => {
    if (!portLaneMapping.value.has(item.PortId)) {
      portLaneMapping.value.set(item.PortId, []);
    }
    item.LaneMap.forEach((mapping: any) => {
      const sourcePortNodeRect = portNodeManager.nodes[item.PortId].nodeRect;
      const sourceNodeId = sourcePortNodeRect.id;
      const sourcePortId = sourcePortNodeRect.getPorts()[mapping.PortLaneId - 1].id;
      const targetOpticalNodeRect = opticalNodeManager.nodes.find(node => 
        node.nodeRect.getData().nodeId === mapping.Transceiver.Id)!.nodeRect;
      const targetNodeId = targetOpticalNodeRect.id;
      const targetPortId = targetOpticalNodeRect.getPorts()[mapping.Transceiver.LaneId - 1]?.id;
      
      if (!opticalLaneMapping.value.has(mapping.Transceiver.Id)) {
        opticalLaneMapping.value.set(mapping.Transceiver.Id, []);
      }

      portLaneMapping.value.get(item.PortId)!.push([mapping.PortLaneId, mapping.Transceiver.SilkText, mapping.Transceiver.LaneId]);
      opticalLaneMapping.value.get(mapping.Transceiver.Id)!.push([mapping.Transceiver.LaneId, `Port ${item.PortId + 1}`, mapping.PortLaneId]);
      
      graph.value?.addEdge({
        source: { 
          cell: sourceNodeId, 
          port: sourcePortId,
        },
        target: { 
          cell: targetNodeId, 
          port: targetPortId,
        },
        router: {
          name: 'oneSide',
          args: {
            padding: 20,
          }
        },
        attrs: {
          line: {
            stroke: '#7AB8FF',
            strokeWidth: 2,
            targetMarker: null,
          }
        }
      });
    });
  });

  portNodeManager.nodes.forEach(node => {
    hidePortsAndEdges(node.nodeRect);
  });

  opticalNodeManager.nodes.forEach(node => {
    hidePortsAndEdges(node.nodeRect);
    Array(8).fill(true).forEach((_, i) => {
      graph.value?.on(`node:${node.label}|${i + 1}:click`, ({ node: clickedNode, e }: any) => {
        const portIndex = e.target.getAttribute('event').split('|')[1].split(':')[0];
        const targetPort = clickedNode.getPorts()[parseInt(portIndex) - 1];
        showPortsAndEdges(clickedNode);
        const foundEdge = graph.value?.getConnectedEdges(clickedNode)?.find(edge => edge.getTargetPortId() === targetPort.id);
        if (foundEdge) {
          setTimeout(() => {
            clickedNode.attr(`portLabel${portIndex}/fontWeight`, 'bold');
            highlightEdge(foundEdge);
          }, 100);
        }
      });
    });
  });
});

</script>

<template>
  <div ref="containerRef" class="x6-container"></div>
</template>

<style lang="scss">
.x6-container {
  margin-top: 16px;
  width: 100% !important;
  height: calc(100% - 16px) !important;
  border: 1px solid #C3CEDF;

  body {
    min-width: unset !important;
  }
}

.infoPanel {
  padding: 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.infoPanelTitle {
  margin: 0;
  font-size: 16px;
  color: #000;
}
.close-btn {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
  color: #999;
  padding: 0;
  width: 24px;
  height: 24px;
  line-height: 24px;
}
.detailInfo {
  padding: 0 16px !important;
  display: flex;
  flex-direction: column;
  gap: 8px; 
}
.associateInfo {
  padding: 0 16px;
  margin-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.associateInfoTitle {
  padding: 0 16px;
  margin-top: 24px;
}

.infoTemplate {
  display: flex;
  gap: 24px;
  align-items: center;
  font-size: 12px;
  .infoTemplateLabel {
    width: 102px;
    color: #4e5865;
  }
  .infoTemplateTag {
    display: flex;
    align-items: center;
    gap: 8px;
    & > div {
      width: 64px;
    }
    .infoTemplateTagSpan {
      padding: 0 8px;
      height: 16px;
      border-radius: 10px;
      background: #dfe5ef;
    }
  }
}
</style>