import React, { useEffect, useRef } from 'react';
import * as d3 from 'd3';
import { ParseResult, MemberMetrics } from '../lib/parser';

interface NetworkGraphProps {
  data: ParseResult;
  onNodeClick: (username: string) => void;
  highlightedNode: string | null;
  minDegree: number;
  metrics?: Map<string, MemberMetrics>;
  path?: string[]; // Array of usernames representing a highlighted path
  filter?: 'all' | 'following' | 'follower';
}

interface Node extends d3.SimulationNodeDatum {
  id: string;
  group: number;
  radius: number;
  color: string;
}

interface Link extends d3.SimulationLinkDatum<Node> {
  source: string | Node;
  target: string | Node;
  value: number;
  type?: "following" | "follower";
}

const colors = ["#6366f1", "#0ea5e9", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899", "#14b8a6", "#f97316"];

export default function NetworkGraph({ data, onNodeClick, highlightedNode, minDegree, metrics, path, filter = 'all' }: NetworkGraphProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const svgRef = useRef<SVGSVGElement>(null);

  // Define zoom outside to re-use it
  const zoomRef = useRef<d3.ZoomBehavior<SVGSVGElement, unknown> | null>(null);

  useEffect(() => {
    if (!svgRef.current || !containerRef.current || data.relationships.size === 0) return;

    const width = containerRef.current.clientWidth;
    const height = containerRef.current.clientHeight;

    const nodes: Node[] = [];
    const links: Link[] = [];
    const nodeMap = new Map<string, Node>();
    
    // Assign colors to central subjects
    const centerColors = new Map<string, string>();
    let cIdx = 0;
    for (const sub of data.relationships.keys()) {
        centerColors.set(sub, colors[cIdx % colors.length]);
        cIdx++;
    }

    const getOrCreateNode = (username: string, isSubject: boolean) => {
      if (!nodeMap.has(username)) {
        let nColor = "#94a3b8"; // default
        if (isSubject && centerColors.has(username)) {
             nColor = centerColors.get(username)!;
        } else if (metrics && metrics.has(username)) {
             const pc = metrics.get(username)!.primaryCenter;
             if (pc && centerColors.has(pc)) nColor = centerColors.get(pc)!;
        }
        
        const node: Node = {
          id: username,
          group: isSubject ? 1 : 2,
          radius: isSubject ? 12 : 6,
          color: nColor,
        };
        nodeMap.set(username, node);
        nodes.push(node);
      }
      return nodeMap.get(username)!;
    };

    // Calculate degrees to apply minDegree filter and pick top nodes if graph is too large
    const degrees = new Map<string, number>();
    
    data.relationships.forEach((rels, subject) => {
      degrees.set(subject, (degrees.get(subject) || 0) + rels.following.length + rels.followers.length);
      rels.following.forEach(u => degrees.set(u.username, (degrees.get(u.username) || 0) + 1));
      rels.followers.forEach(u => degrees.set(u.username, (degrees.get(u.username) || 0) + 1));
    });

    const MAX_NODES = 800; // Increased max nodes since we render statically
    let allowedNodes = new Set<string>();
    
    // Filter nodes by minDegree
    const validDegrees = Array.from(degrees.entries()).filter(d => d[1] >= minDegree);

    if (validDegrees.length > MAX_NODES) {
        const sorted = validDegrees.sort((a, b) => b[1] - a[1]);
        allowedNodes = new Set(sorted.slice(0, MAX_NODES).map(s => s[0]));
    } else {
        allowedNodes = new Set(validDegrees.map(s => s[0]));
    }

    data.relationships.forEach((rels, subject) => {
      if (!allowedNodes.has(subject)) return;

      const sourceNode = getOrCreateNode(subject, true);
      sourceNode.group = 1;
      sourceNode.radius = 16; 

      rels.following.forEach((u) => {
        if (!allowedNodes.has(u.username)) return;
        if (filter === 'follower') return; // Skip following links if filtering for followers only
        getOrCreateNode(u.username, data.relationships.has(u.username));
        // subject follows u -> subject is source, u is target
        links.push({ source: subject, target: u.username, value: 1, type: "following" });
      });

      rels.followers.forEach((u) => {
        if (!allowedNodes.has(u.username)) return;
        if (filter === 'following') return; // Skip follower links if filtering for following only
        getOrCreateNode(u.username, data.relationships.has(u.username));
        // u follows subject -> u is source, subject is target
        links.push({ source: u.username, target: subject, value: 1, type: "follower" });
      });
    });

    const svg = d3.select(svgRef.current);
    svg.selectAll("*").remove(); 

    svg.attr("viewBox", [0, 0, width, height]);

    // Add arrow markers for directed edges
    const defs = svg.append("defs");
    ["default", "following", "follower", "path"].forEach(pattern => {
      // following = blue
      // follower = green
      // path = orange
      // default = gray
      const color = pattern === "following" ? "#3b82f6" : pattern === "follower" ? "#10b981" : pattern === "path" ? "#f59e0b" : "#cbd5e1";
      defs.append("marker")
        .attr("id", `arrow-${pattern}`)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 22) // padding for node radius
        .attr("refY", 0)
        .attr("markerWidth", 5)
        .attr("markerHeight", 5)
        .attr("orient", "auto")
        .append("path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr("fill", color);
    });

    const g = svg.append("g");

    const zoom = d3.zoom<SVGSVGElement, unknown>()
      .scaleExtent([0.1, 8])
      .on("zoom", (event) => {
        g.attr("transform", event.transform);
      });
      
    zoomRef.current = zoom;
    svg.call(zoom as any);

    // Make simulation static to prevent choppiness
    const simulation = d3.forceSimulation(nodes)
      .force("link", d3.forceLink(links).id((d: any) => d.id).distance(60))
      .force("charge", d3.forceManyBody().strength(-200))
      .force("center", d3.forceCenter(width / 2, height / 2))
      .force("collide", d3.forceCollide().radius(d => (d as Node).radius + 8));

    // Run simulation synchronously (pre-calculate graph layout)
    simulation.stop();
    for (let i = 0; i < 200; ++i) {
       simulation.tick();
    }

    const link = g.append("g")
      .attr("stroke", "#cbd5e1")
      .attr("stroke-opacity", 0.6)
      .selectAll("line")
      .data(links)
      .join("line")
      .attr("stroke-width", d => Math.min(Math.sqrt(d.value), 4))
      .attr("x1", d => (d.source as Node).x!)
      .attr("y1", d => (d.source as Node).y!)
      .attr("x2", d => (d.target as Node).x!)
      .attr("y2", d => (d.target as Node).y!)
      .attr("marker-end", d => d.type ? `url(#arrow-${d.type})` : "url(#arrow-default)")
      .attr("stroke", d => {
        if (d.type === "following") return "#3b82f6";
        if (d.type === "follower") return "#10b981";
        return "#cbd5e1";
      })
      .attr("stroke-opacity", 0.3);

    const node = g.append("g")
      .attr("stroke", "#fff")
      .attr("stroke-width", 1.5)
      .selectAll("circle")
      .data(nodes)
      .join("circle")
      .attr("r", d => d.radius)
      .attr("fill", d => d.color)
      .style("cursor", "pointer")
      .attr("cx", d => d.x!)
      .attr("cy", d => d.y!)
      .on("click", (event, d) => onNodeClick(d.id));

    node.append("title")
      .text(d => d.id);

    // Text labels for nodes
    const labels = g.append("g")
      .selectAll("text")
      .data(nodes)
      .join("text")
      .text(d => d.id)
      .attr("font-size", d => d.group === 1 ? "12px" : "8px")
      .attr("font-family", "sans-serif")
      .attr("fill", "#1e293b")
      .attr("dx", 12)
      .attr("dy", 4)
      .attr("x", d => d.x!)
      .attr("y", d => d.y!)
      .style("pointer-events", "none")
      // Only show text for prominent nodes to decrease clutter
      .attr("opacity", d => degrees.get(d.id)! > 3 || d.group === 1 ? 1 : 0.15);

  }, [data, onNodeClick, minDegree, filter]);

  useEffect(() => {
    if (!svgRef.current) return;
    const svg = d3.select(svgRef.current);
    const node = svg.selectAll("circle");
    const link = svg.selectAll("line");
    const text = svg.selectAll("text");

    if (path && path.length > 0) {
      // Highlight specific path
      const pathSet = new Set(path);
      const pathEdges = new Set<string>();
      for (let i = 0; i < path.length - 1; i++) {
         pathEdges.add(`${path[i]}->${path[i+1]}`);
         pathEdges.add(`${path[i+1]}->${path[i]}`); // Undirected match just in case
      }

      node.attr("opacity", (d: any) => pathSet.has(d.id) ? 1 : 0.05)
          .attr("stroke", (d: any) => pathSet.has(d.id) ? "#f59e0b" : "#fff")
          .attr("stroke-width", (d: any) => pathSet.has(d.id) ? 3 : 1.5);
          
      text.attr("opacity", (d: any) => pathSet.has(d.id) ? 1 : 0.02);
      
      link.attr("stroke-opacity", (d: any) => {
        const sourceId = typeof d.source === 'string' ? d.source : (d.source as any).id;
        const targetId = typeof d.target === 'string' ? d.target : (d.target as any).id;
        return pathEdges.has(`${sourceId}->${targetId}`) ? 1 : 0.02;
      }).attr("stroke", (d: any) => {
        const sourceId = typeof d.source === 'string' ? d.source : (d.source as any).id;
        const targetId = typeof d.target === 'string' ? d.target : (d.target as any).id;
        return pathEdges.has(`${sourceId}->${targetId}`) ? "#f59e0b" : "#cbd5e1";
      }).attr("marker-end", (d: any) => {
        const sourceId = typeof d.source === 'string' ? d.source : (d.source as any).id;
        const targetId = typeof d.target === 'string' ? d.target : (d.target as any).id;
        return pathEdges.has(`${sourceId}->${targetId}`) ? "url(#arrow-path)" : "none";
      }).attr("stroke-width", (d: any) => {
        const sourceId = typeof d.source === 'string' ? d.source : (d.source as any).id;
        const targetId = typeof d.target === 'string' ? d.target : (d.target as any).id;
        return pathEdges.has(`${sourceId}->${targetId}`) ? 3 : Math.min(Math.sqrt(d.value), 4);
      });

      return;
    }

    if (!highlightedNode) {
      node.attr("opacity", 1).attr("stroke", "#fff").attr("stroke-width", 1.5);
      link.attr("stroke-opacity", 0.3)
          .attr("stroke", (d: any) => {
            if (d.type === "following") return "#3b82f6";
            if (d.type === "follower") return "#10b981";
            return "#cbd5e1";
          })
          .attr("marker-end", (d: any) => d.type ? `url(#arrow-${d.type})` : "url(#arrow-default)")
          .attr("stroke-width", (d: any) => Math.min(Math.sqrt(d.value), 4));
      // reset text opacity based on logical rules from earlier
      text.attr("opacity", (d: any) => d.group === 1 || (d.radius > 6) ? 1 : 0.15);
      return;
    }

    const connectedSet = new Set<string>();
    connectedSet.add(highlightedNode);
        
    data.relationships.forEach((rels, subject) => {
      if (subject === highlightedNode) {
        rels.following.forEach(u => connectedSet.add(u.username));
        rels.followers.forEach(u => connectedSet.add(u.username));
      } else {
        rels.following.forEach(u => {
          if (u.username === highlightedNode) connectedSet.add(subject);
        });
        rels.followers.forEach(u => {
          if (u.username === highlightedNode) connectedSet.add(subject);
        });
      }
    });

    node.attr("opacity", (d: any) => connectedSet.has(d.id) ? 1 : 0.05)
        .attr("stroke", (d: any) => d.id === highlightedNode ? "#94a3b8" : "#fff")
        .attr("stroke-width", (d: any) => d.id === highlightedNode ? 3 : 1.5);
        
    text.attr("opacity", (d: any) => connectedSet.has(d.id) ? 1 : 0.02);
        
    link.attr("stroke-opacity", (d: any) => {
      const sourceId = typeof d.source === 'string' ? d.source : (d.source as any).id;
      const targetId = typeof d.target === 'string' ? d.target : (d.target as any).id;
      return (sourceId === highlightedNode || targetId === highlightedNode) ? 0.9 : 0.02;
    }).attr("stroke", (d: any) => {
      const sourceId = typeof d.source === 'string' ? d.source : (d.source as any).id;
      const targetId = typeof d.target === 'string' ? d.target : (d.target as any).id;
      if (sourceId === highlightedNode) return "#3b82f6"; // Following (blue)
      if (targetId === highlightedNode) return "#10b981"; // Follower (green)
      return "#cbd5e1";
    }).attr("marker-end", (d: any) => {
      const sourceId = typeof d.source === 'string' ? d.source : (d.source as any).id;
      const targetId = typeof d.target === 'string' ? d.target : (d.target as any).id;
      if (sourceId === highlightedNode) return "url(#arrow-following)";
      if (targetId === highlightedNode) return "url(#arrow-follower)";
      return "none";
    });

  }, [highlightedNode, path, data]);

  return (
    <div ref={containerRef} className="w-full h-full relative cursor-move">
      <svg ref={svgRef} className="w-full h-full" />
    </div>
  );
}
