'use client';

import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import styles from './case.module.css';
import type { ReplayStep, GraphNode, GraphEdge } from '@/lib/types';
import { scenarioToGraph, riskToColor, nodeSize } from '@/lib/dataTransform';



const NODE_SHAPES: Record<string, string> = {
  person: 'ellipse',
  company: 'rectangle',
  account: 'diamond',
  transaction: 'triangle',
  shell: 'hexagon',
  jurisdiction: 'pentagon',
  asset: 'barrel',
};

const EDGE_COLORS: Record<string, string> = {
  ownership: '#8B5CF6',
  transaction: '#EA580C',
  association: '#505055',
  suspicious: '#D4334A',
  director: '#8B5CF6',
};

const getIconSvg = (type: string, color: string) => {
  let path = '';
  switch (type) {
    case 'person': path = 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'; break;
    case 'company': path = 'M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z'; break;
    case 'shell': path = 'M21 16.5c0 .38-.21.71-.53.88l-7.9 4.44c-.16.12-.36.18-.57.18-.21 0-.41-.06-.57-.18l-7.9-4.44A.991.991 0 0 1 3 16.5v-9c0-.38.21-.71.53-.88l7.9-4.44c.16-.12.36-.18.57-.18.21 0 .41.06.57.18l7.9 4.44c.32.17.53.5.53.88v9zM12 4.15L6.04 7.5 12 10.85l5.96-3.35L12 4.15zM5 15.91l6 3.38v-6.71L5 9.21v6.7zM19 15.91v-6.7l-6 3.37v6.71l6-3.38z'; break;
    case 'account': path = 'M4 10h3v7H4zM10.5 10h3v7h-3zM2 19h20v3H2zM17 10h3v7h-3zM12 1L2 6v2h20V6z'; break;
    case 'transaction': path = 'M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46A7.93 7.93 0 0 0 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74A7.93 7.93 0 0 0 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z'; break;
    case 'asset': path = 'M12 3L2 12h3v8h6v-6h2v6h6v-8h3L12 3zm5 15h-2v-6H9v6H7v-7.81l5-4.5 5 4.5V18z'; break;
    default: path = 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z'; break;
  }
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="${color}"><path d="${path}"/></svg>`;
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
};

interface Props {
  graphData: { nodes: GraphNode[]; edges: GraphEdge[] };
}

export default function EntityGraph({ graphData }: Props) {
  const containerRef = useRef<HTMLDivElement>(null);
  const cyRef = useRef<any>(null);
  const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null);
  const [tooltipData, setTooltipData] = useState<{ type: 'node' | 'edge'; data: any; x: number; y: number } | null>(null);
  const prevNodeIdsRef = useRef<Set<string>>(new Set());

  // Cola layout config — tuned for spacing and stability
  const colaLayoutConfig = useMemo(() => ({
    name: 'cola',
    animate: true,
    animationDuration: 1000,
    animationEasing: 'ease-in-out-cubic' as any,
    fit: true,
    padding: 80,
    randomize: false,
    nodeSpacing: 80,
    edgeLength: 150,
    convergenceThreshold: 0.01,
    nodeRepulsion: 8000,
    idealEdgeLength: 120,
    avoidOverlap: true,
  }), []);

  useEffect(() => {
    if (!containerRef.current) return;
    let mounted = true;

    const initCytoscape = async () => {
      const cytoscape = (await import('cytoscape')).default;
      const cola = (await import('cytoscape-cola')).default;

      if (!mounted || !containerRef.current) return;

      try {
        cytoscape.use(cola);
      } catch { /* already registered */ }

      const elements = [
        ...graphData.nodes.map(n => ({
          data: { id: n.id, label: n.label, type: n.type, risk: n.risk, jurisdiction: n.jurisdiction, flagged: n.flagged, pep: n.pep },
        })),
        ...graphData.edges.map(e => ({
          data: { id: e.id, source: e.source, target: e.target, type: e.type, label: e.label, amount: e.amount, suspicious: e.suspicious },
        })),
      ];

      const cy = cytoscape({
        container: containerRef.current,
        elements,
        style: [
          {
            selector: 'node',
            style: {
              'background-color': '#1C1C1F',
              'width': (ele: any) => nodeSize(ele.data('risk') || 30),
              'height': (ele: any) => nodeSize(ele.data('risk') || 30),
              'shape': (ele: any) => NODE_SHAPES[ele.data('type')] || 'ellipse',
              'color': '#D4D4D4',
              'font-size': '9px',
              'font-family': 'JetBrains Mono, monospace',
              'text-valign': 'bottom',
              'text-margin-y': 6,
              'text-outline-width': 4,
              'text-outline-color': '#131316',
              'border-width': 2,
              'border-style': (ele: any) => ele.data('type') === 'shell' ? 'dashed' : 'solid',
              'border-color': (ele: any) => {
                if (ele.data('flagged')) return '#D4334A';
                return riskToColor(ele.data('risk') || 30);
              },
              'background-image': (ele: any) => getIconSvg(ele.data('type'), riskToColor(ele.data('risk') || 30)),
              'background-width': '50%',
              'background-height': '50%',
              'background-position-x': '50%',
              'background-position-y': '50%',
            } as any,
          },
          {
            selector: 'node:selected',
            style: {
              'border-width': 3,
              'border-color': '#EA580C',
              'overlay-color': '#EA580C',
              'overlay-opacity': 0.1,
            },
          },
          {
            selector: 'edge',
            style: {
              'width': (ele: any) => {
                const amt = ele.data('amount');
                if (amt) return Math.max(1, Math.min(4, Math.log10(amt / 100000)));
                return 1;
              },
              'line-color': (ele: any) => EDGE_COLORS[ele.data('type')] || '#505055',
              'target-arrow-color': (ele: any) => EDGE_COLORS[ele.data('type')] || '#505055',
              'target-arrow-shape': 'triangle',
              'curve-style': 'bezier',
              'font-size': '8px',
              'font-family': '"JetBrains Mono", monospace',
              'color': '#D4D4D4',
              'text-rotation': 'autorotate',
              'text-background-opacity': 1,
              'text-background-color': '#2A2A2D',
              'text-background-padding': 4,
              'text-background-shape': 'roundrectangle',
              'control-point-step-size': 80,
            } as any,
          },
          {
            selector: 'edge[?suspicious]',
            style: {
              'line-color': '#D4334A',
              'target-arrow-color': '#D4334A',
              'line-style': 'solid',
              'width': 2.5,
            },
          },
          {
            selector: '.dimmed',
            style: { 'opacity': 0.12 },
          },
        ],
        layout: colaLayoutConfig as any,
      });

      cyRef.current = cy;
      prevNodeIdsRef.current = new Set(graphData.nodes.map(n => n.id));

      const handleMouseOver = (type: 'node' | 'edge') => (evt: any) => {
        const ele = evt.target;
        cy.elements().addClass('dimmed');
        if (type === 'node') {
          ele.neighborhood().add(ele).removeClass('dimmed');
        } else {
          ele.connectedNodes().add(ele).removeClass('dimmed');
        }

        const data = ele.data();
        let pos;

        if (evt.originalEvent && containerRef.current) {
          const rect = containerRef.current.getBoundingClientRect();
          pos = {
            x: evt.originalEvent.clientX - rect.left,
            y: evt.originalEvent.clientY - rect.top
          };
        } else {
          pos = type === 'node' ? ele.renderedPosition() : ele.renderedMidpoint();
        }

        let x = pos.x + 16;
        let y = pos.y - 10;

        if (containerRef.current) {
          const rect = containerRef.current.getBoundingClientRect();
          const tooltipWidth = 220;
          const tooltipHeight = 120;

          if (x + tooltipWidth > rect.width) {
            x = pos.x - tooltipWidth - 16;
          }
          if (y + 40 + tooltipHeight > rect.height) {
            y = rect.height - tooltipHeight - 50;
          }
        }

        setTooltipData({ type, data, x, y });
      };

      cy.on('mouseover', 'node', handleMouseOver('node'));
      cy.on('mouseover', 'edge', handleMouseOver('edge'));

      cy.on('mouseout', 'node, edge', () => {
        cy.elements().removeClass('dimmed');
        setTooltipData(null);
      });

      cy.on('tap', 'node', (evt: any) => {
        const nodeData = evt.target.data() as GraphNode;
        setSelectedNode(prev => {
          if (prev?.id === nodeData.id) {
            evt.target.unselect();
            return null;
          }
          return nodeData;
        });
      });

      cy.on('tap', (evt: any) => {
        if (evt.target === cy) {
          setSelectedNode(null);
          cy.elements().unselect();
        }
      });
    };

    initCytoscape();

    return () => {
      mounted = false;
      if (cyRef.current) {
        cyRef.current.destroy();
        cyRef.current = null;
      }
    };
  }, []); // Initial load only

  const newElements = useMemo(() => [
    ...graphData.nodes.map(n => ({
      data: { id: n.id, label: n.label, type: n.type, risk: n.risk, jurisdiction: n.jurisdiction, flagged: n.flagged, pep: n.pep },
    })),
    ...graphData.edges.map(e => ({
      data: { id: e.id, source: e.source, target: e.target, type: e.type, label: e.label, amount: e.amount, suspicious: e.suspicious },
    })),
  ], [graphData]);

  const elementsSignature = useMemo(() => JSON.stringify(newElements), [newElements]);

  // Incremental layout update — only add new nodes/edges, don't scatter existing ones
  useEffect(() => {
    if (!cyRef.current) return;
    const cy = cyRef.current;
    if (!containerRef.current) return;

    const currentNodeIds = new Set(graphData.nodes.map(n => n.id));
    const currentEdgeIds = new Set(graphData.edges.map(e => e.id));
    const existingNodeIds = new Set<string>(cy.nodes().map((n: any) => n.id()));
    const existingEdgeIds = new Set<string>(cy.edges().map((e: any) => e.id()));

    // Find new elements to add
    const nodesToAdd = newElements.filter(
      el => el.data.id && !('source' in el.data) && !existingNodeIds.has(el.data.id)
    );
    const edgesToAdd = newElements.filter(
      el => 'source' in el.data && !existingEdgeIds.has(el.data.id)
    );

    // Find elements to remove
    const nodeIdsToRemove = [...existingNodeIds].filter(id => !currentNodeIds.has(id));
    const edgeIdsToRemove = [...existingEdgeIds].filter(id => !currentEdgeIds.has(id));

    let needsLayout = false;

    if (nodeIdsToRemove.length > 0 || edgeIdsToRemove.length > 0) {
      [...nodeIdsToRemove, ...edgeIdsToRemove].forEach(id => {
        const el = cy.getElementById(id);
        if (el.length) el.remove();
      });
      needsLayout = true;
    }

    if (nodesToAdd.length > 0 || edgesToAdd.length > 0) {
      cy.add([...nodesToAdd, ...edgesToAdd]);
      needsLayout = true;
    }

    if (needsLayout) {
      cy.layout({
        ...colaLayoutConfig,
        animate: true,
        animationDuration: 1000,
        fit: cy.nodes().length <= 8,
        randomize: false,
      } as any).run();
    }

    prevNodeIdsRef.current = currentNodeIds;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [elementsSignature]);

  return (
    <div className={styles.graphContainer}>
      <div ref={containerRef} className={styles.graphCanvas} />

      {/* Floating tooltip */}
      {tooltipData && tooltipData.type === 'node' && (
        <div className={styles.graphTooltip} style={{ left: tooltipData.x, top: tooltipData.y + 40 }}>
          <div className={styles.ttHeader}>
            <span className={styles.ttType}>{tooltipData.data.type?.toUpperCase()}</span>
            <span className={styles.ttRisk} style={{ color: riskToColor(tooltipData.data.risk || 0) }}>
              RISK {tooltipData.data.risk || 0}
            </span>
          </div>
          <div className={styles.ttName}>{tooltipData.data.label}</div>
          {tooltipData.data.jurisdiction && (
            <div className={styles.ttRow}><span>Jurisdiction</span><span>{tooltipData.data.jurisdiction}</span></div>
          )}
          {tooltipData.data.pep && (
            <div className={styles.ttFlag}>⚠ PEP — Politically Exposed Person</div>
          )}
          {tooltipData.data.flagged && !tooltipData.data.pep && (
            <div className={styles.ttFlag}>⚠ Flagged for review</div>
          )}
        </div>
      )}

      {tooltipData && tooltipData.type === 'edge' && (
        <div className={styles.graphTooltip} style={{ left: tooltipData.x, top: tooltipData.y + 40 }}>
          <div className={styles.ttHeader}>
            <span className={styles.ttType}>{tooltipData.data.type?.toUpperCase() || 'CONNECTION'}</span>
          </div>
          <div className={styles.ttName}>{tooltipData.data.label}</div>
          {tooltipData.data.amount && (
            <div className={styles.ttRow}><span>Amount</span><span>${(tooltipData.data.amount).toLocaleString()}</span></div>
          )}
          {tooltipData.data.suspicious && (
            <div className={styles.ttFlag}>⚠ Suspicious Pattern</div>
          )}
        </div>
      )}

      {/* Selected node detail tray */}
      {selectedNode && (
        <div className={styles.detailTray}>
          <div className={styles.detailHeader}>
            <span className={styles.detailTitle}>{selectedNode.label}</span>
            <button className="nx-btn" onClick={() => {
              setSelectedNode(null);
              cyRef.current?.elements().unselect();
            }}>✕</button>
          </div>
          <div className={styles.detailBody}>
            <div className={styles.detailRow}>
              <span className="nx-label">TYPE</span>
              <span>{selectedNode.type?.toUpperCase()}</span>
            </div>
            <div className={styles.detailRow}>
              <span className="nx-label">RISK SCORE</span>
              <span style={{ color: riskToColor(selectedNode.risk || 0) }}>{selectedNode.risk}</span>
            </div>
            {selectedNode.jurisdiction && (
              <div className={styles.detailRow}>
                <span className="nx-label">JURISDICTION</span>
                <span>{selectedNode.jurisdiction}</span>
              </div>
            )}
            {selectedNode.pep && (
              <div className={styles.detailFlag}>PEP — Politically Exposed Person</div>
            )}
            {selectedNode.flagged && (
              <div className={styles.detailFlag}>⚠ WATCHLIST HIT</div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}
