import { useState, useEffect, useCallback, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { ResolvedArtifact, DependencyResolutionResponse, Dependency } from '../types';
import { resolveDependencies, getArtifactDependencies } from '../api';
import './DependencyGraph.css';

interface DependencyGraphProps {
  projectName: string;
  packageName: string;
  tagName: string;
  onClose: () => void;
}

interface GraphNode {
  id: string;
  project: string;
  package: string;
  version: string | null;
  size: number;
  depth: number;
  children: GraphNode[];
  isRoot?: boolean;
}

function formatBytes(bytes: number): string {
  if (bytes === 0) return '0 B';
  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}

function DependencyGraph({ projectName, packageName, tagName, onClose }: DependencyGraphProps) {
  const navigate = useNavigate();
  const containerRef = useRef<HTMLDivElement>(null);

  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [resolution, setResolution] = useState<DependencyResolutionResponse | null>(null);
  const [graphRoot, setGraphRoot] = useState<GraphNode | null>(null);
  const [hoveredNode, setHoveredNode] = useState<GraphNode | null>(null);
  const [zoom, setZoom] = useState(1);
  const [pan, setPan] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);
  const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
  const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set());

  // Build graph structure from resolution data
  const buildGraph = useCallback(async (resolutionData: DependencyResolutionResponse) => {
    const artifactMap = new Map<string, ResolvedArtifact>();
    resolutionData.resolved.forEach(artifact => {
      artifactMap.set(artifact.artifact_id, artifact);
    });

    // Fetch dependencies for each artifact to build the tree
    const depsMap = new Map<string, Dependency[]>();

    for (const artifact of resolutionData.resolved) {
      try {
        const deps = await getArtifactDependencies(artifact.artifact_id);
        depsMap.set(artifact.artifact_id, deps.dependencies);
      } catch {
        depsMap.set(artifact.artifact_id, []);
      }
    }

    // Find the root artifact (the requested one)
    const rootArtifact = resolutionData.resolved.find(
      a => a.project === resolutionData.requested.project &&
           a.package === resolutionData.requested.package
    );

    if (!rootArtifact) {
      return null;
    }

    // Build tree recursively
    const visited = new Set<string>();

    const buildNode = (artifact: ResolvedArtifact, depth: number): GraphNode => {
      const nodeId = `${artifact.project}/${artifact.package}`;
      visited.add(artifact.artifact_id);

      const deps = depsMap.get(artifact.artifact_id) || [];
      const children: GraphNode[] = [];

      for (const dep of deps) {
        // Find the resolved artifact for this dependency
        const childArtifact = resolutionData.resolved.find(
          a => a.project === dep.project && a.package === dep.package
        );

        if (childArtifact && !visited.has(childArtifact.artifact_id)) {
          children.push(buildNode(childArtifact, depth + 1));
        }
      }

      return {
        id: nodeId,
        project: artifact.project,
        package: artifact.package,
        version: artifact.version || artifact.tag,
        size: artifact.size,
        depth,
        children,
        isRoot: depth === 0,
      };
    };

    return buildNode(rootArtifact, 0);
  }, []);

  useEffect(() => {
    async function loadData() {
      setLoading(true);
      setError(null);

      try {
        const result = await resolveDependencies(projectName, packageName, tagName);
        setResolution(result);

        const graph = await buildGraph(result);
        setGraphRoot(graph);
      } catch (err) {
        if (err instanceof Error) {
          // Check if it's a resolution error
          try {
            const errorData = JSON.parse(err.message);
            if (errorData.error === 'circular_dependency') {
              setError(`Circular dependency detected: ${errorData.cycle?.join(' → ')}`);
            } else if (errorData.error === 'dependency_conflict') {
              setError(`Dependency conflict: ${errorData.message}`);
            } else {
              setError(err.message);
            }
          } catch {
            setError(err.message);
          }
        } else {
          setError('Failed to load dependency graph');
        }
      } finally {
        setLoading(false);
      }
    }

    loadData();
  }, [projectName, packageName, tagName, buildGraph]);

  const handleNodeClick = (node: GraphNode) => {
    navigate(`/project/${node.project}/${node.package}`);
    onClose();
  };

  const handleNodeToggle = (node: GraphNode, e: React.MouseEvent) => {
    e.stopPropagation();
    setCollapsedNodes(prev => {
      const next = new Set(prev);
      if (next.has(node.id)) {
        next.delete(node.id);
      } else {
        next.add(node.id);
      }
      return next;
    });
  };

  const handleWheel = (e: React.WheelEvent) => {
    e.preventDefault();
    const delta = e.deltaY > 0 ? -0.1 : 0.1;
    setZoom(z => Math.max(0.25, Math.min(2, z + delta)));
  };

  const handleMouseDown = (e: React.MouseEvent) => {
    if (e.target === containerRef.current || (e.target as HTMLElement).classList.contains('graph-canvas')) {
      setIsDragging(true);
      setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
    }
  };

  const handleMouseMove = (e: React.MouseEvent) => {
    if (isDragging) {
      setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
    }
  };

  const handleMouseUp = () => {
    setIsDragging(false);
  };

  const resetView = () => {
    setZoom(1);
    setPan({ x: 0, y: 0 });
  };

  const renderNode = (node: GraphNode, index: number = 0): JSX.Element => {
    const isCollapsed = collapsedNodes.has(node.id);
    const hasChildren = node.children.length > 0;

    return (
      <div key={`${node.id}-${index}`} className="graph-node-container">
        <div
          className={`graph-node ${node.isRoot ? 'graph-node--root' : ''} ${hoveredNode?.id === node.id ? 'graph-node--hovered' : ''}`}
          onClick={() => handleNodeClick(node)}
          onMouseEnter={() => setHoveredNode(node)}
          onMouseLeave={() => setHoveredNode(null)}
        >
          <div className="graph-node__header">
            <span className="graph-node__name">{node.project}/{node.package}</span>
            {hasChildren && (
              <button
                className="graph-node__toggle"
                onClick={(e) => handleNodeToggle(node, e)}
                title={isCollapsed ? 'Expand' : 'Collapse'}
              >
                {isCollapsed ? '+' : '-'}
              </button>
            )}
          </div>
          <div className="graph-node__details">
            {node.version && <span className="graph-node__version">@ {node.version}</span>}
            <span className="graph-node__size">{formatBytes(node.size)}</span>
          </div>
        </div>

        {hasChildren && !isCollapsed && (
          <div className="graph-children">
            <div className="graph-connector"></div>
            <div className="graph-children-list">
              {node.children.map((child, i) => renderNode(child, i))}
            </div>
          </div>
        )}
      </div>
    );
  };

  return (
    <div className="dependency-graph-modal" onClick={onClose}>
      <div className="dependency-graph-content" onClick={e => e.stopPropagation()}>
        <div className="dependency-graph-header">
          <h2>Dependency Graph</h2>
          <div className="dependency-graph-info">
            <span>{projectName}/{packageName} @ {tagName}</span>
            {resolution && (
              <span className="graph-stats">
                {resolution.artifact_count} packages • {formatBytes(resolution.total_size)} total
              </span>
            )}
          </div>
          <button className="close-btn" onClick={onClose} title="Close">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <line x1="18" y1="6" x2="6" y2="18"></line>
              <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
          </button>
        </div>

        <div className="dependency-graph-toolbar">
          <button className="btn btn-secondary btn-small" onClick={() => setZoom(z => Math.min(2, z + 0.25))}>
            Zoom In
          </button>
          <button className="btn btn-secondary btn-small" onClick={() => setZoom(z => Math.max(0.25, z - 0.25))}>
            Zoom Out
          </button>
          <button className="btn btn-secondary btn-small" onClick={resetView}>
            Reset View
          </button>
          <span className="zoom-level">{Math.round(zoom * 100)}%</span>
        </div>

        <div
          ref={containerRef}
          className="dependency-graph-container"
          onWheel={handleWheel}
          onMouseDown={handleMouseDown}
          onMouseMove={handleMouseMove}
          onMouseUp={handleMouseUp}
          onMouseLeave={handleMouseUp}
        >
          {loading ? (
            <div className="graph-loading">
              <div className="spinner"></div>
              <span>Resolving dependencies...</span>
            </div>
          ) : error ? (
            <div className="graph-error">
              <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <circle cx="12" cy="12" r="10"></circle>
                <line x1="12" y1="8" x2="12" y2="12"></line>
                <line x1="12" y1="16" x2="12.01" y2="16"></line>
              </svg>
              <p>{error}</p>
            </div>
          ) : graphRoot ? (
            <div
              className="graph-canvas"
              style={{
                transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
                cursor: isDragging ? 'grabbing' : 'grab',
              }}
            >
              {renderNode(graphRoot)}
            </div>
          ) : (
            <div className="graph-empty">No dependencies to display</div>
          )}
        </div>

        {hoveredNode && (
          <div className="graph-tooltip">
            <strong>{hoveredNode.project}/{hoveredNode.package}</strong>
            {hoveredNode.version && <div>Version: {hoveredNode.version}</div>}
            <div>Size: {formatBytes(hoveredNode.size)}</div>
            <div className="tooltip-hint">Click to navigate</div>
          </div>
        )}
      </div>
    </div>
  );
}

export default DependencyGraph;
