import { useMemo, useCallback } from 'react';
import {
  ReactFlow,
  Background,
  Controls,
  MiniMap,
  type Node,
  type Edge,
} from '@xyflow/react';
import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
  type SimulationNodeDatum,
  type SimulationLinkDatum,
} from 'd3-force';
import '@xyflow/react/dist/style.css';
import { Spin, Empty } from 'antd';
import { SocialUserNode, type SocialUserNodeData } from './SocialUserNode';

const ROLE_COLORS: Record<string, string> = {
  SUPER_ADMIN: '#f5222d',
  INFLUENCE_ADMIN: '#1890ff',
  MAP_ADMIN: '#52c41a',
  USER: '#722ed1',
  TEMP: '#faad14',
};

const nodeTypes = { socialUser: SocialUserNode };

export interface GraphNodeData {
  id: string;
  name: string | null;
  email: string;
  role: string;
  isOnline: boolean;
  friendCount: number;
  achievementCount: number;
  groups: string[];
}

export interface GraphEdgeData {
  source: string;
  target: string;
  status: string;
  acceptedAt: string | null;
}

export interface GraphData {
  nodes: GraphNodeData[];
  edges: GraphEdgeData[];
}

type LayoutMode = 'force' | 'radial';

interface SocialNetworkGraphProps {
  graphData: GraphData | null;
  loading: boolean;
  layoutMode: LayoutMode;
  centerUserId?: string;
  onNodeClick: (userId: string) => void;
}

/** Force-directed layout using d3-force (synchronous tick) */
function computeForceLayout(
  nodes: GraphNodeData[],
  edges: GraphEdgeData[],
): Map<string, { x: number; y: number }> {
  interface SimNode extends SimulationNodeDatum {
    id: string;
  }

  const simNodes: SimNode[] = nodes.map((n) => ({ id: n.id }));
  const nodeIndex = new Map(simNodes.map((n, i) => [n.id, i]));

  const simLinks: SimulationLinkDatum<SimNode>[] = edges
    .filter((e) => e.status === 'accepted' && nodeIndex.has(e.source) && nodeIndex.has(e.target))
    .map((e) => ({ source: nodeIndex.get(e.source)!, target: nodeIndex.get(e.target)! }));

  const simulation = forceSimulation<SimNode>(simNodes)
    .force('link', forceLink<SimNode, SimulationLinkDatum<SimNode>>(simLinks).distance(120))
    .force('charge', forceManyBody().strength(-300))
    .force('center', forceCenter(0, 0))
    .force('collide', forceCollide(60))
    .stop();

  // Run 300 ticks synchronously
  for (let i = 0; i < 300; i++) simulation.tick();

  const positions = new Map<string, { x: number; y: number }>();
  for (const node of simNodes) {
    positions.set(node.id, { x: node.x ?? 0, y: node.y ?? 0 });
  }
  return positions;
}

/** Radial layout centered on a user using BFS */
function computeRadialLayout(
  nodes: GraphNodeData[],
  edges: GraphEdgeData[],
  centerId: string,
): Map<string, { x: number; y: number }> {
  // Build adjacency (accepted edges only)
  const adjacency = new Map<string, string[]>();
  for (const edge of edges) {
    if (edge.status !== 'accepted') continue;
    adjacency.set(edge.source, [...(adjacency.get(edge.source) ?? []), edge.target]);
    adjacency.set(edge.target, [...(adjacency.get(edge.target) ?? []), edge.source]);
  }

  // BFS from center
  const distances = new Map<string, number>();
  const queue: string[] = [centerId];
  distances.set(centerId, 0);

  while (queue.length > 0) {
    const current = queue.shift()!;
    const dist = distances.get(current)!;
    const neighbors = adjacency.get(current) ?? [];
    for (const n of neighbors) {
      if (!distances.has(n)) {
        distances.set(n, dist + 1);
        queue.push(n);
      }
    }
  }

  // Group nodes by ring distance
  const rings = new Map<number, string[]>();
  const unconnected: string[] = [];
  for (const node of nodes) {
    const dist = distances.get(node.id);
    if (dist !== undefined) {
      const ring = rings.get(dist) ?? [];
      ring.push(node.id);
      rings.set(dist, ring);
    } else {
      unconnected.push(node.id);
    }
  }

  const positions = new Map<string, { x: number; y: number }>();
  const ringRadius = 200;

  // Place center
  positions.set(centerId, { x: 0, y: 0 });

  // Place each ring
  const maxRing = Math.max(...Array.from(rings.keys()));
  for (let r = 1; r <= maxRing; r++) {
    const nodesInRing = rings.get(r) ?? [];
    const radius = r * ringRadius;
    nodesInRing.forEach((nodeId, i) => {
      const angle = (2 * Math.PI * i) / nodesInRing.length - Math.PI / 2;
      positions.set(nodeId, {
        x: Math.cos(angle) * radius,
        y: Math.sin(angle) * radius,
      });
    });
  }

  // Place unconnected nodes in outer ring
  if (unconnected.length > 0) {
    const outerRadius = (maxRing + 1) * ringRadius;
    unconnected.forEach((nodeId, i) => {
      const angle = (2 * Math.PI * i) / unconnected.length - Math.PI / 2;
      positions.set(nodeId, {
        x: Math.cos(angle) * outerRadius,
        y: Math.sin(angle) * outerRadius,
      });
    });
  }

  return positions;
}

export default function SocialNetworkGraph({
  graphData,
  loading,
  layoutMode,
  centerUserId,
  onNodeClick,
}: SocialNetworkGraphProps) {
  // Compute layout positions
  const { flowNodes, flowEdges } = useMemo(() => {
    if (!graphData || graphData.nodes.length === 0) return { flowNodes: [], flowEdges: [] };

    let positions: Map<string, { x: number; y: number }>;
    if (layoutMode === 'radial' && centerUserId) {
      positions = computeRadialLayout(graphData.nodes, graphData.edges, centerUserId);
    } else {
      positions = computeForceLayout(graphData.nodes, graphData.edges);
    }

    const nodes: Node[] = graphData.nodes.map((n) => {
      const pos = positions.get(n.id) ?? { x: 0, y: 0 };
      return {
        id: n.id,
        type: 'socialUser',
        position: pos,
        data: {
          name: n.name,
          email: n.email,
          role: n.role,
          isOnline: n.isOnline,
          friendCount: n.friendCount,
          achievementCount: n.achievementCount,
          groups: n.groups,
        } satisfies SocialUserNodeData,
      };
    });

    const nodeSet = new Set(graphData.nodes.map((n) => n.id));
    const edges: Edge[] = graphData.edges
      .filter((e) => nodeSet.has(e.source) && nodeSet.has(e.target))
      .map((e, i) => ({
        id: `edge-${i}`,
        source: e.source,
        target: e.target,
        style: {
          stroke: e.status === 'accepted' ? 'rgba(255,255,255,0.3)' : 'rgba(255,255,255,0.1)',
          strokeWidth: e.status === 'accepted' ? 1.5 : 1,
          strokeDasharray: e.status === 'pending' ? '5,5' : undefined,
        },
      }));

    return { flowNodes: nodes, flowEdges: edges };
  }, [graphData, layoutMode, centerUserId]);

  const handleNodeClick = useCallback(
    (_event: React.MouseEvent, node: Node) => {
      onNodeClick(node.id);
    },
    [onNodeClick],
  );

  if (loading && !graphData) {
    return (
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
        <Spin size="large" />
      </div>
    );
  }

  if (!graphData || graphData.nodes.length === 0) {
    return (
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
        <Empty description="No users found." image={Empty.PRESENTED_IMAGE_SIMPLE} />
      </div>
    );
  }

  return (
    <div style={{ width: '100%', height: '100%' }}>
      <ReactFlow
        nodes={flowNodes}
        edges={flowEdges}
        onNodeClick={handleNodeClick}
        nodeTypes={nodeTypes}
        fitView
        minZoom={0.1}
        maxZoom={3}
        proOptions={{ hideAttribution: true }}
      >
        <Background color="rgba(255,255,255,0.03)" gap={30} />
        <Controls
          showInteractive={false}
          style={{ background: 'rgba(30,20,50,0.9)', borderRadius: 6, border: '1px solid rgba(255,255,255,0.1)' }}
        />
        <MiniMap
          nodeColor={(node) => {
            const role = (node.data as SocialUserNodeData)?.role;
            return ROLE_COLORS[role] || '#722ed1';
          }}
          maskColor="rgba(0,0,0,0.6)"
          style={{ background: 'rgba(20,15,40,0.9)', borderRadius: 6 }}
        />
      </ReactFlow>
    </div>
  );
}
