import { useMemo, useCallback, useRef, useState, useEffect } from 'react';
import ForceGraph3D from 'react-force-graph-3d';
import { useConversation } from '../context/ConversationContext';
import { toGraphData } from '../utils/tree';
import BranchDialog from './BranchDialog';
import * as THREE from 'three';

export default function TreeView() {
  const {
    state,
    switchToBranch,
    setNodeAsMainBranch,
  } = useConversation();

  const fgRef = useRef(null);
  const containerRef = useRef(null);
  const treePaneRef = useRef(null);
  const prevNodeCountRef = useRef(0);
  const [dimensions, setDimensions] = useState({ width: 420, height: 600 });
  const [contextMenu, setContextMenu] = useState(null);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [branchNode, setBranchNode] = useState(null);
  const [showLegend, setShowLegend] = useState(true);

  const conv = state.activeConversationId
    ? state.conversations[state.activeConversationId]
    : null;

  // --- Stable graph structure fingerprint ---
  const structureFingerprint = useMemo(() => {
    if (!conv) return '';
    const nodeIds = Object.keys(conv.nodes).sort().join(',');
    const links = Object.values(conv.nodes)
      .filter((n) => n.parentId)
      .map((n) => `${n.parentId}->${n.id}`)
      .sort()
      .join(',');
    const mainPath = conv.mainBranchPath.join(',');
    const activePath = conv.activeBranchPath.join(',');
    const statuses = Object.values(conv.nodes)
      .map((n) => `${n.id}:${n.status}`)
      .join(',');
    return `${nodeIds}|${links}|${mainPath}|${activePath}|${statuses}`;
  }, [conv]);

  const graphData = useMemo(() => {
    if (!conv || Object.keys(conv.nodes).length === 0) {
      return { nodes: [], links: [] };
    }
    return toGraphData(conv.nodes, conv.mainBranchPath, conv.activeBranchPath);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [structureFingerprint]);

  // Auto-fit camera when new nodes are added
  useEffect(() => {
    const currentCount = graphData.nodes.length;
    if (currentCount > prevNodeCountRef.current && currentCount > 0 && fgRef.current) {
      setTimeout(() => {
        fgRef.current?.zoomToFit(600, 60);
      }, 300);
    }
    prevNodeCountRef.current = currentCount;
  }, [graphData.nodes.length]);

  // Resize observer
  useEffect(() => {
    if (!containerRef.current) return;
    const observer = new ResizeObserver((entries) => {
      const { width, height } = entries[0].contentRect;
      setDimensions({ width, height });
    });
    observer.observe(containerRef.current);
    return () => observer.disconnect();
  }, []);

  // Close context menu on click outside
  useEffect(() => {
    if (!contextMenu) return;
    const handler = () => setContextMenu(null);
    window.addEventListener('click', handler);
    return () => window.removeEventListener('click', handler);
  }, [contextMenu]);

  // Handle Escape to exit fullscreen
  useEffect(() => {
    if (!isFullscreen) return;
    const handler = (e) => {
      if (e.key === 'Escape') setIsFullscreen(false);
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [isFullscreen]);


  const handleNodeClick = useCallback(
    (node) => {
      if (!conv) return;
      switchToBranch(state.activeConversationId, node.id);
    },
    [conv, state.activeConversationId, switchToBranch]
  );

  const handleNodeRightClick = useCallback(
    (node, event) => {
      event.preventDefault();
      setContextMenu({
        x: event.clientX,
        y: event.clientY,
        nodeId: node.id,
        userMessage: node.userMessage || '',
      });
    },
    []
  );

  const handleSetMainBranch = useCallback(() => {
    if (!contextMenu || !conv) return;
    setNodeAsMainBranch(state.activeConversationId, contextMenu.nodeId);
    setContextMenu(null);
  }, [contextMenu, conv, state.activeConversationId, setNodeAsMainBranch]);

  const handleBranchFromGraph = useCallback(() => {
    if (!contextMenu || !conv) return;
    const node = conv.nodes[contextMenu.nodeId];
    if (!node) return;
    setBranchNode(node);
    setContextMenu(null);
  }, [contextMenu, conv]);

  const handleResetView = useCallback(() => {
    if (!fgRef.current) return;
    fgRef.current.zoomToFit(400, 40);
  }, []);

  const toggleFullscreen = useCallback(() => {
    setIsFullscreen((prev) => !prev);
    setTimeout(() => {
      if (fgRef.current) {
        fgRef.current.zoomToFit(400, 40);
      }
    }, 100);
  }, []);

  // --- Color helpers ---
  const getNodeColor = useCallback((node) => {
    if (node.status === 'generating') return '#f0c040';
    if (node.isActive && node.isMain) return '#00ffcc';
    if (node.isActive) return '#6c7aff';
    if (node.isMain) return '#00d4aa';
    return '#555566';
  }, []);

  const linkColor = useCallback((link) => {
    if (link.isActive && link.isMain) return 'rgba(0, 255, 204, 0.5)';
    if (link.isActive) return 'rgba(108, 122, 255, 0.4)';
    if (link.isMain) return 'rgba(0, 212, 170, 0.35)';
    return 'rgba(85, 85, 102, 0.2)';
  }, []);

  const linkWidth = useCallback((link) => {
    if (link.isMain || link.isActive) return 2.5;
    return 1;
  }, []);

  // --- Rich hover tooltip ---
  const nodeLabel = useCallback((node) => {
    const userPreview = node.userMessage
      ? node.userMessage.slice(0, 120) + (node.userMessage.length > 120 ? '…' : '')
      : '(start)';
    const aiPreview = node.assistantMessage
      ? node.assistantMessage.slice(0, 200) + (node.assistantMessage.length > 200 ? '…' : '')
      : node.status === 'generating'
      ? '⏳ Generating…'
      : '—';
    const statusBadge =
      node.status === 'generating'
        ? '<span style="background:#f0c040;color:#12121a;padding:2px 6px;border-radius:4px;font-size:9px;font-weight:700;margin-left:6px;">GENERATING</span>'
        : node.childCount > 1
        ? `<span style="background:rgba(108,122,255,0.2);color:#6c7aff;padding:2px 6px;border-radius:4px;font-size:9px;font-weight:600;margin-left:6px;">${node.childCount} branches</span>`
        : '';

    return `
      <div style="background:rgba(18,18,26,0.95);backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px 18px;max-width:360px;box-shadow:0 12px 40px rgba(0,0,0,0.5);font-family:Inter,sans-serif;">
        <div style="display:flex;align-items:center;margin-bottom:8px;">
          <span style="font-size:10px;text-transform:uppercase;letter-spacing:0.8px;font-weight:700;color:#6c7aff;">Turn ${node.depth + 1}</span>
          ${statusBadge}
        </div>
        <div style="font-size:10px;text-transform:uppercase;letter-spacing:0.8px;font-weight:600;color:rgba(108,122,255,0.6);margin-bottom:3px;">👤 User</div>
        <div style="font-size:12px;color:#e8e8f0;margin-bottom:12px;line-height:1.6;word-break:break-word;">${escapeHtml(userPreview)}</div>
        <div style="font-size:10px;text-transform:uppercase;letter-spacing:0.8px;font-weight:600;color:rgba(0,212,170,0.6);margin-bottom:3px;">🤖 Assistant</div>
        <div style="font-size:12px;color:#9898a8;line-height:1.6;word-break:break-word;">${escapeHtml(aiPreview)}</div>
      </div>
    `;
  }, []);

  // --- Custom 3D node with text label ---
  const nodeThreeObject = useCallback((node) => {
    const group = new THREE.Group();

    // Core sphere
    const isHighlighted = node.isMain || node.isActive;
    const size = isHighlighted ? 5 : 3;
    const color = getNodeColor(node);
    const geo = new THREE.SphereGeometry(size, 20, 20);
    const mat = new THREE.MeshPhongMaterial({
      color,
      emissive: color,
      emissiveIntensity: node.isActive ? 0.6 : 0.25,
      transparent: true,
      opacity: node.status === 'generating' ? 0.65 : 1,
      shininess: 60,
    });
    const mesh = new THREE.Mesh(geo, mat);
    group.add(mesh);

    // Outer glow ring for highlighted nodes
    if (isHighlighted) {
      const ringGeo = new THREE.RingGeometry(size * 1.4, size * 1.7, 32);
      const ringMat = new THREE.MeshBasicMaterial({
        color,
        transparent: true,
        opacity: 0.18,
        side: THREE.DoubleSide,
      });
      const ringMesh = new THREE.Mesh(ringGeo, ringMat);
      group.add(ringMesh);
    }

    // Branch indicator (diamond) for nodes with multiple children
    if (node.childCount > 1) {
      const diamondGeo = new THREE.OctahedronGeometry(2.5);
      const diamondMat = new THREE.MeshPhongMaterial({
        color: '#ff6c8a',
        emissive: '#ff6c8a',
        emissiveIntensity: 0.3,
        transparent: true,
        opacity: 0.7,
      });
      const diamond = new THREE.Mesh(diamondGeo, diamondMat);
      diamond.position.set(size + 4, 0, 0);
      diamond.rotation.set(0, 0, Math.PI / 4);
      group.add(diamond);
    }

    // Sprite label below the node (pure WebGL, no DOM elements)
    const label = createSpriteLabel(node, color);
    label.position.set(0, -(size + 8), 0);
    group.add(label);

    return group;
  }, [getNodeColor]);

  const nodeCount = graphData.nodes.length;

  return (
    <div
      className={`tree-pane ${isFullscreen ? 'tree-pane-fullscreen' : ''}`}
      ref={treePaneRef}
    >
      <div className="tree-header">
        <span>Graph View</span>
        <div className="tree-header-actions">
          <span style={{ fontSize: '11px', color: 'var(--text-tertiary)', marginRight: '8px' }}>
            {nodeCount} node{nodeCount !== 1 ? 's' : ''}
          </span>
          {nodeCount > 0 && (
            <>
              <button
                className="tree-action-btn"
                onClick={() => setShowLegend((v) => !v)}
                title="Toggle legend"
              >
                ℹ
              </button>
              <button
                className="tree-action-btn"
                onClick={handleResetView}
                title="Reset view (zoom to fit)"
              >
                ⟳
              </button>
            </>
          )}
          <button
            className="tree-action-btn"
            onClick={toggleFullscreen}
            title={isFullscreen ? 'Exit fullscreen (Esc)' : 'Fullscreen'}
          >
            {isFullscreen ? '⊗' : '⛶'}
          </button>
        </div>
      </div>

      <div className="tree-container" ref={containerRef}>
        {nodeCount === 0 ? (
          <div className="tree-empty">
            <p>Start chatting to see your conversation tree</p>
          </div>
        ) : (
          <ForceGraph3D
            ref={fgRef}
            graphData={graphData}
            width={dimensions.width}
            height={dimensions.height}
            backgroundColor="#0d0d14"
            dagMode="td"
            dagLevelDistance={80}
            nodeLabel={nodeLabel}
            nodeColor={getNodeColor}
            nodeVal={(node) => (node.isMain || node.isActive ? 3 : 1.5)}
            nodeThreeObject={nodeThreeObject}
            nodeThreeObjectExtend={false}
            linkColor={linkColor}
            linkWidth={linkWidth}
            linkCurvature={0.15}
            linkCurveRotation={0}
            linkDirectionalArrowLength={4}
            linkDirectionalArrowRelPos={0.85}
            linkDirectionalArrowColor={linkColor}
            linkDirectionalParticles={(link) =>
              link.isActive ? 3 : link.isMain ? 2 : 0
            }
            linkDirectionalParticleWidth={(link) =>
              link.isActive ? 2.5 : 1.5
            }
            linkDirectionalParticleColor={(link) =>
              link.isActive ? '#6c7aff' : '#00d4aa'
            }
            linkDirectionalParticleSpeed={0.004}
            linkOpacity={0.7}
            onNodeClick={handleNodeClick}
            onNodeRightClick={handleNodeRightClick}
            enableNodeDrag={false}
            cooldownTicks={100}
            warmupTicks={50}
            d3AlphaDecay={0.08}
            d3VelocityDecay={0.4}
            showNavInfo={false}
          />
        )}

        {/* Color Legend */}
        {showLegend && nodeCount > 0 && (
          <div className="graph-legend">
            <div className="graph-legend-title">Legend</div>
            <div className="graph-legend-item">
              <span className="graph-legend-dot" style={{ background: '#00ffcc' }} />
              Active + Main
            </div>
            <div className="graph-legend-item">
              <span className="graph-legend-dot" style={{ background: '#6c7aff' }} />
              Active branch
            </div>
            <div className="graph-legend-item">
              <span className="graph-legend-dot" style={{ background: '#00d4aa' }} />
              Main branch
            </div>
            <div className="graph-legend-item">
              <span className="graph-legend-dot" style={{ background: '#f0c040' }} />
              Generating
            </div>
            <div className="graph-legend-item">
              <span className="graph-legend-dot" style={{ background: '#555566' }} />
              Inactive
            </div>
            <div className="graph-legend-item">
              <span className="graph-legend-diamond" />
              Branch point
            </div>
          </div>
        )}
      </div>

      {contextMenu && (
        <div
          className="context-menu"
          style={{ left: contextMenu.x, top: contextMenu.y }}
          onClick={(e) => e.stopPropagation()}
        >
          <button className="context-menu-item" onClick={handleBranchFromGraph}>
            ⑂ Branch from here
          </button>
          <button className="context-menu-item" onClick={handleSetMainBranch}>
            ⭐ Set as Main Branch
          </button>
          <button
            className="context-menu-item"
            onClick={() => {
              handleNodeClick({ id: contextMenu.nodeId });
              setContextMenu(null);
            }}
          >
            👁 View this branch
          </button>
        </div>
      )}

      {branchNode && (
        <BranchDialog
          node={branchNode}
          onClose={() => setBranchNode(null)}
          onConfirm={(nodeId, newMessage) => {
            window.dispatchEvent(
              new CustomEvent('branchchat:branch', {
                detail: { nodeId, newMessage },
              })
            );
            setBranchNode(null);
          }}
        />
      )}
    </div>
  );
}

/**
 * Create a canvas-rendered sprite label for a graph node.
 * Uses THREE.Sprite + CanvasTexture instead of CSS2DObject to avoid
 * orphaned DOM elements (ghost text) when the graph re-renders.
 */
function createSpriteLabel(node, color) {
  const preview = node.userMessage
    ? node.userMessage.slice(0, 22) + (node.userMessage.length > 22 ? '…' : '')
    : 'Start';

  const turnNum = node.depth + 1;
  const statusIcon = node.status === 'generating' ? ' ⏳' : '';
  const turnText = `#${turnNum}${statusIcon}`;

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  // High-DPI canvas for crisp text
  const scale = 2;
  const canvasW = 200 * scale;
  const canvasH = 48 * scale;
  canvas.width = canvasW;
  canvas.height = canvasH;
  ctx.scale(scale, scale);

  // Turn number line
  ctx.font = 'bold 11px Inter, sans-serif';
  ctx.fillStyle = color;
  ctx.textAlign = 'center';
  ctx.textBaseline = 'top';
  ctx.fillText(turnText, 100, 4);

  // Preview line
  ctx.font = '9px Inter, sans-serif';
  ctx.fillStyle = 'rgba(232, 232, 240, 0.5)';
  ctx.fillText(preview, 100, 22);

  const texture = new THREE.CanvasTexture(canvas);
  texture.minFilter = THREE.LinearFilter;

  const spriteMat = new THREE.SpriteMaterial({
    map: texture,
    transparent: true,
    depthWrite: false,
  });
  const sprite = new THREE.Sprite(spriteMat);
  // Scale sprite to a reasonable world-space size
  sprite.scale.set(20, 20 * (canvasH / canvasW), 1);

  return sprite;
}

function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}
