import React, { useEffect, useRef, useState, useMemo } from 'react';
import * as d3 from 'd3';
import { motion, AnimatePresence } from 'motion/react';
import { Note, Carnet, Tag } from '../types';
import { 
  Network, 
  Search, 
  Sliders, 
  HelpCircle, 
  X, 
  Filter, 
  Compass, 
  BookOpen, 
  Eye, 
  Sparkles,
  RefreshCw,
  Plus,
  Minus,
  Maximize2,
  ChevronLeft,
  Calendar,
  Layers,
  FileText
} from 'lucide-react';

interface GraphKnowledgeMapProps {
  notes: Note[];
  carnets: Carnet[];
  onOpenNote: (noteId: string) => void;
  onClose?: () => void;
}

// 7 Gorgeous colors corresponding to the carnets palette
const CARNET_COLOR_PALETTE: { [key: string]: string } = {
  '1': '#D97706', // Daily Notes - Warm Amber
  '2': '#059669', // Project: Neo - Soft Emerald
  '3': '#4F46E5', // Shared Docs - Rich Indigo
  '4': '#0891B2', // Architecture Research - Clean Cyan
  '5': '#EA580C', // History of Architecture - Deep Orange
  '6': '#DB2777', // Modernism - Vibrant Rose
  '7': '#65A30D', // Sustainable Design - Cool Lime
};

const DEFAULT_CARNET_COLOR = '#71717A'; // Zinc

interface D3Node extends d3.SimulationNodeDatum {
  id: string;
  title: string;
  carnetId: string;
  carnetName: string;
  color: string;
  date: string;
  snippet: string;
  tags: Tag[];
  degree: number;
}

interface D3Link extends d3.SimulationLinkDatum<D3Node> {
  source: string | D3Node;
  target: string | D3Node;
  type: 'wikilink' | 'semantic';
  strength: number;
}

export const GraphKnowledgeMap: React.FC<GraphKnowledgeMapProps> = ({
  notes,
  carnets,
  onOpenNote,
  onClose
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const svgRef = useRef<SVGSVGElement>(null);

  // Settings & Toggles
  const [showSemanticLinks, setShowSemanticLinks] = useState(true);
  const [minSemanticStrength, setMinSemanticStrength] = useState(0.40); // threshold
  const [selectedCarnetIds, setSelectedCarnetIds] = useState<string[]>([]);
  
  // Interaction States
  const [searchQuery, setSearchQuery] = useState('');
  const [hoveredNode, setHoveredNode] = useState<D3Node | null>(null);
  const [activeLocalNode, setActiveLocalNode] = useState<D3Node | null>(null);
  const [nodeConnections, setNodeConnections] = useState<Set<string>>(new Set());
  
  // D3 Zoom controller ref to trigger programmatically
  const d3ZoomRef = useRef<d3.ZoomBehavior<SVGSVGElement, unknown> | null>(null);

  // Initialize carnet filters with all carnets on mount
  useEffect(() => {
    setSelectedCarnetIds(carnets.map(c => c.id));
  }, [carnets]);

  // Static list of explicit links (Wikilinks)
  const explicitWikiLinks = useMemo(() => {
    return [
      { source: 'n1', target: 'n1-b' },
      { source: 'n3', target: 'n3-b' },
      { source: 'bridge-1', target: 'n1' },
      { source: 'bridge-1', target: 'n2' },
    ];
  }, []);

  // Filter and process notes and carnets
  const filteredNotes = useMemo(() => {
    return notes.filter(n => {
      // Exclude trashed/deleted notes
      if (n.isDeleted) return false;
      // Filter by selected carnets
      return selectedCarnetIds.includes(n.carnetId);
    });
  }, [notes, selectedCarnetIds]);

  // Compute all links based on state (Wikilinks + Semantic if enabled)
  const graphData = useMemo(() => {
    const noteMap = new Map<string, Note>();
    filteredNotes.forEach(n => noteMap.set(n.id, n));

    const nodes: D3Node[] = filteredNotes.map(n => {
      const carnet = carnets.find(c => c.id === n.carnetId);
      return {
        id: n.id,
        title: n.title,
        carnetId: n.carnetId,
        carnetName: carnet?.name || 'Carnet Inconnu',
        color: CARNET_COLOR_PALETTE[n.carnetId] || DEFAULT_CARNET_COLOR,
        date: n.date,
        snippet: n.content.split('.').slice(0, 3).join('.') + '.',
        tags: n.tags || [],
        degree: 0, // calculated below
        x: undefined,
        y: undefined
      };
    });

    const links: D3Link[] = [];

    // 1. Add Explicit Wikilinks if both target and source are inside filtered list
    explicitWikiLinks.forEach(link => {
      if (noteMap.has(link.source) && noteMap.has(link.target)) {
        links.push({
          source: link.source,
          target: link.target,
          type: 'wikilink',
          strength: 1.0
        });
      }
    });

    // 2. Add Semantic Connections (Memory Echo) based on embedding similarities
    if (showSemanticLinks) {
      for (let i = 0; i < filteredNotes.length; i++) {
        for (let j = i + 1; j < filteredNotes.length; j++) {
          const ni = filteredNotes[i];
          const nj = filteredNotes[j];
          
          if (ni.embedding && nj.embedding) {
            // Cosine vector similarity approximation / Euclidean inverse mapping
            const dist = Math.sqrt(
              Math.pow(ni.embedding[0] - nj.embedding[0], 2) + 
              Math.pow(ni.embedding[1] - nj.embedding[1], 2)
            );
            // Translate distance into similarity standard (0.0 - 1.0)
            const similarity = Math.max(0, 1 - dist * 0.7);

            if (similarity >= minSemanticStrength) {
              // Avoid duplicate links with explicit ones to keep display clean
              const hasExplicit = explicitWikiLinks.some(
                ex => (ex.source === ni.id && ex.target === nj.id) || (ex.source === nj.id && ex.target === ni.id)
              );
              
              if (!hasExplicit) {
                links.push({
                  source: ni.id,
                  target: nj.id,
                  type: 'semantic',
                  strength: similarity
                });
              }
            }
          }
        }
      }
    }

    // Calculate node connectivity degrees
    nodes.forEach(node => {
      const connectionsCount = links.filter(l => 
        l.source === node.id || l.target === node.id || 
        (typeof l.source === 'object' && (l.source as any).id === node.id) ||
        (typeof l.target === 'object' && (l.target as any).id === node.id)
      ).length;
      node.degree = connectionsCount;
    });

    return { nodes, links };
  }, [filteredNotes, carnets, showSemanticLinks, minSemanticStrength, selectedCarnetIds, explicitWikiLinks]);

  // Handle Note connection highlights during hover
  useEffect(() => {
    if (!hoveredNode) {
      setNodeConnections(new Set());
      return;
    }

    const connected = new Set<string>();
    connected.add(hoveredNode.id);

    graphData.links.forEach((l: any) => {
      const srcId = typeof l.source === 'object' ? l.source.id : l.source;
      const tgtId = typeof l.target === 'object' ? l.target.id : l.target;

      if (srcId === hoveredNode.id) {
        connected.add(tgtId);
      } else if (tgtId === hoveredNode.id) {
        connected.add(srcId);
      }
    });

    setNodeConnections(connected);
  }, [hoveredNode, graphData.links]);

  // Main D3 force layout rendering loop
  useEffect(() => {
    if (!svgRef.current || !containerRef.current) return;

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

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

    // Base containment group
    const mainGroup = svg.append("g");

    // Configure zooming behaviors
    const zoomBehavior = d3.zoom<SVGSVGElement, unknown>()
      .scaleExtent([0.15, 5])
      .on("zoom", (event) => {
        mainGroup.attr("transform", event.transform);
      });

    d3ZoomRef.current = zoomBehavior;
    svg.call(zoomBehavior);

    // D3 nodes and links references mapped to copyable arrays
    const simulationNodes = JSON.parse(JSON.stringify(graphData.nodes)) as D3Node[];
    const simulationLinks = graphData.links.map(l => ({
      source: l.source,
      target: l.target,
      type: l.type,
      strength: l.strength
    })) as D3Link[];

    // Build the force simulation
    const simulation = d3.forceSimulation<D3Node>(simulationNodes)
      .force("link", d3.forceLink<D3Node, any>(simulationLinks)
        .id(d => d.id)
        .distance(d => d.type === 'wikilink' ? 100 : 140)
      )
      .force("charge", d3.forceManyBody().strength(-240))
      .force("center", d3.forceCenter(width / 2, height / 2))
      .force("collision", d3.forceCollide<D3Node>().radius(d => {
        // Size proportional to connections: min 8px, max 20px
        const rad = 8 + Math.min(d.degree * 2.5, 12);
        return rad + 24;
      }));

    // Draw Links
    const linkGroup = mainGroup.append("g")
      .attr("class", "links-layer");

    const link = linkGroup.selectAll("line")
      .data(simulationLinks)
      .enter()
      .append("line")
      .attr("stroke", d => d.type === 'semantic' ? '#4f46e5' : '#18181b')
      .attr("stroke-opacity", d => d.type === 'semantic' ? 0.35 : 0.18)
      .attr("stroke-width", d => d.type === 'semantic' ? 1.2 : 1.5)
      .attr("stroke-dasharray", d => d.type === 'semantic' ? '4,4' : 'none');

    // Draw Nodes
    const nodeGroup = mainGroup.append("g")
      .attr("class", "nodes-layer");

    const node = nodeGroup.selectAll(".node")
      .data(simulationNodes)
      .enter()
      .append("g")
      .attr("class", "node cursor-pointer")
      .on("click", (event, d) => {
        event.stopPropagation();
        handleSelectNode(d);
      })
      .on("mouseenter", (event, d) => {
        setHoveredNode(d);
      })
      .on("mouseleave", () => {
        setHoveredNode(null);
      })
      .call(d3.drag<SVGGElement, D3Node>()
        .on("start", dragStarted)
        .on("drag", dragged)
        .on("end", dragEnded) as any);

    // Create central circles
    node.append("circle")
      .attr("r", d => 6 + Math.min(d.degree * 1.5, 9))
      .attr("fill", d => d.color)
      .attr("stroke", "rgba(255,255,255,0.95)")
      .attr("stroke-width", 2)
      .attr("class", "transition-all duration-300 dark:stroke-zinc-950")
      .style("filter", "drop-shadow(0 2px 4px rgba(0,0,0,0.1))");

    // Text labels overlay
    node.append("text")
      .attr("dy", d => 14 + Math.min(d.degree * 1.5, 9) + 4)
      .attr("text-anchor", "middle")
      .attr("class", "text-[10px] sm:text-[11px] font-sans font-semibold tracking-tight fill-zinc-850 dark:fill-zinc-300 select-none pointer-events-none")
      .text(d => d.title.length > 22 ? d.title.substring(0, 20) + "..." : d.title)
      .style("opacity", d => (d.degree > 2 || d.title.toLowerCase().includes(searchQuery.toLowerCase()) && searchQuery) ? 1 : 0.65);

    // Search query search highlight ring
    if (searchQuery) {
      node.filter(d => d.title.toLowerCase().includes(searchQuery.toLowerCase()))
        .append("circle")
        .attr("r", d => 14 + Math.min(d.degree * 1.5, 9))
        .attr("fill", "none")
        .attr("stroke", "#06b6d4")
        .attr("stroke-width", 2)
        .attr("stroke-dasharray", "3,1")
        .attr("class", "animate-[spin_20s_linear_infinite]");
    }

    // Node active local neighbor rings
    if (activeLocalNode) {
      const activeConns = getLocalNodeNeighbors(activeLocalNode.id);
      
      node.style("opacity", d => {
        return activeConns.has(d.id) ? 1.0 : 0.15;
      });

      link.style("stroke-opacity", (l: any) => {
        const srcId = l.source.id;
        const tgtId = l.target.id;
        return (activeConns.has(srcId) && activeConns.has(tgtId)) ? 0.75 : 0.05;
      });

      // Highlight the focused local hub node with a neat accent circle
      node.filter(d => d.id === activeLocalNode.id)
        .append("circle")
        .attr("r", d => 16 + Math.min(d.degree * 1.5, 9))
        .attr("fill", "none")
        .attr("stroke", "rgba(79, 70, 229, 0.4)")
        .attr("stroke-width", 1.5)
        .attr("stroke-opacity", 0.9);
    } 
    // Node hover lighting state
    else if (hoveredNode) {
      const hoveredConns = new Set<string>();
      hoveredConns.add(hoveredNode.id);

      graphData.links.forEach((l: any) => {
        const srcId = typeof l.source === 'object' ? l.source.id : l.source;
        const tgtId = typeof l.target === 'object' ? l.target.id : l.target;

        if (srcId === hoveredNode.id) {
          hoveredConns.add(tgtId);
        } else if (tgtId === hoveredNode.id) {
          hoveredConns.add(srcId);
        }
      });

      // Subdue unconnected elements to 20% opacity
      node.style("opacity", d => hoveredConns.has(d.id) ? 1.0 : 0.20);
      link.style("stroke-opacity", (l: any) => {
        const srcId = l.source.id;
        const tgtId = l.target.id;
        return (srcId === hoveredNode.id || tgtId === hoveredNode.id) ? 0.8 : 0.05;
      });

      // Hover scale update for primary
      node.filter(d => d.id === hoveredNode.id)
        .select("circle")
        .attr("transform", "scale(1.3)");
    } 
    // Normal / Base state
    else {
      node.style("opacity", 1.0);
      link.style("stroke-opacity", d => d.type === 'semantic' ? 0.35 : 0.18);
    }

    // Run ticks
    simulation.on("tick", () => {
      link
        .attr("x1", d => (d.source as any).x)
        .attr("y1", d => (d.source as any).y)
        .attr("x2", d => (d.target as any).x)
        .attr("y2", d => (d.target as any).y);

      node
        .attr("transform", d => `translate(${d.x},${d.y})`);
    });

    // Zoom on local node view trigger
    if (activeLocalNode && width && height) {
      const targetNodeCopy = simulationNodes.find(n => n.id === activeLocalNode.id);
      if (targetNodeCopy) {
        // Step ticker synchronously to finalize force state layout
        for (let i = 0; i < 40; ++i) simulation.tick();

        const x = targetNodeCopy.x || width / 2;
        const y = targetNodeCopy.y || height / 2;

        svg.transition()
          .duration(850)
          .ease(d3.easeCubicOut)
          .call(
            zoomBehavior.transform,
            d3.zoomIdentity.translate(width / 2, height / 2).scale(1.65).translate(-x, -y)
          );
      }
    } else {
      // Re-center whole graph
      svg.transition()
        .duration(800)
        .ease(d3.easeCubicOut)
        .call(zoomBehavior.transform, d3.zoomIdentity);
    }

    function dragStarted(event: any, d: any) {
      if (!event.active) simulation.alphaTarget(0.25).restart();
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(event: any, d: any) {
      d.fx = event.x;
      d.fy = event.y;
    }

    function dragEnded(event: any, d: any) {
      if (!event.active) simulation.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }

    return () => {
      simulation.stop();
    };
  }, [graphData, showSemanticLinks, minSemanticStrength, searchQuery, activeLocalNode, hoveredNode]);

  // Compute local neighbors
  const getLocalNodeNeighbors = (nodeId: string): Set<string> => {
    const list = new Set<string>();
    list.add(nodeId);
    graphData.links.forEach(l => {
      if (l.source === nodeId) {
        list.add(typeof l.target === 'object' ? (l.target as any).id : l.target);
      } else if (l.target === nodeId) {
        list.add(typeof l.source === 'object' ? (l.source as any).id : l.source);
      }
    });
    return list;
  };

  const handleSelectNode = (node: D3Node) => {
    setActiveLocalNode(node);
  };

  const handleResetLocalView = () => {
    setActiveLocalNode(null);
  };

  const handleZoom = (direction: 'in' | 'out' | 'fit') => {
    if (!svgRef.current || !d3ZoomRef.current) return;
    const svg = d3.select(svgRef.current);
    
    if (direction === 'fit') {
      svg.transition().duration(500).call(d3ZoomRef.current.transform, d3.zoomIdentity);
    } else {
      const factor = direction === 'in' ? 1.3 : 1 / 1.3;
      svg.transition().duration(400).call(d3ZoomRef.current.scaleBy, factor);
    }
  };

  const toggleCarnetSelector = (carnetId: string) => {
    setSelectedCarnetIds(prev => 
      prev.includes(carnetId)
        ? prev.filter(id => id !== carnetId)
        : [...prev, carnetId]
    );
  };

  const selectAllCarnets = () => {
    setSelectedCarnetIds(carnets.map(c => c.id));
  };

  const clearAllCarnets = () => {
    setSelectedCarnetIds([]);
  };

  return (
    <div className="flex-1 h-full flex flex-row overflow-hidden relative">
      <div 
        ref={containerRef} 
        className="flex-1 h-full relative overflow-hidden bg-paper dark:bg-[#0E0E0E]"
        style={{
          backgroundImage: 'radial-gradient(rgba(120, 119, 198, 0.04) 1px, transparent 1.5px)',
          backgroundSize: '24px 24px'
        }}
      >
        {/* Dynamic Header Overlay */}
        <div className="absolute top-5 left-5 z-20 flex items-center gap-3">
          {activeLocalNode ? (
            <button 
              onClick={handleResetLocalView}
              className="flex items-center gap-2 px-3 py-2 bg-white/90 dark:bg-zinc-900/90 backdrop-blur border border-border/80 hover:border-accent text-accent rounded-xl text-xs font-bold uppercase tracking-wider transition-all shadow-md"
            >
              <ChevronLeft size={14} className="stroke-[2.5]" />
              Graphe Global
            </button>
          ) : onClose ? (
            <button 
              onClick={onClose}
              className="flex items-center gap-2 px-3 py-2 bg-white/90 dark:bg-zinc-900/90 backdrop-blur border border-border/80 hover:border-black text-ink rounded-xl text-xs font-bold uppercase tracking-wider transition-all shadow-md"
            >
              <BookOpen size={14} />
              Retour Notes
            </button>
          ) : (
            <div className="flex items-center gap-2 px-3 py-2 bg-white/90 dark:bg-zinc-900/90 backdrop-blur border border-border/60 rounded-xl">
              <Compass size={14} className="text-accent" />
              <span className="text-xs font-bold uppercase tracking-wider text-ink dark:text-dark-ink">Carte Sémantique</span>
            </div>
          )}

          <div className="hidden md:flex items-center bg-zinc-950/5 dark:bg-white/5 border border-border px-3 py-1.5 rounded-xl text-[11px] text-concrete font-medium gap-1.5 shadow-sm">
            <span className="font-bold text-ink dark:text-dark-ink">{graphData.nodes.length} Nœuds</span>
            <span className="opacity-30">|</span>
            <span>{graphData.links.length} Relations</span>
          </div>
        </div>

        {/* Global Hub Search Bar */}
        <div className="absolute top-5 left-1/2 -translate-x-1/2 z-20 w-[90%] max-w-[360px]">
          <div className="relative">
            <input 
              type="text"
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              placeholder="Chercher une note dans le graphe sémantique..."
              className="w-full text-xs pl-9 pr-8 py-2.5 rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white/95 dark:bg-zinc-950/95 placeholder-concrete/60 shadow-lg outline-none focus:border-accent focus:ring-1 focus:ring-accent/10 transition-all text-ink dark:text-dark-ink font-medium"
            />
            <Search size={14} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-concrete" />
            
            {searchQuery && (
              <button 
                onClick={() => setSearchQuery('')}
                className="absolute right-3 top-1/2 -translate-y-1/2 p-0.5 text-concrete hover:text-ink hover:bg-black/5 dark:hover:bg-white/5 rounded-full"
              >
                <X size={13} />
              </button>
            )}
          </div>
        </div>

        {/* Zoom controls (bottom right) */}
        <div className="absolute bottom-6 right-6 z-20 flex flex-col gap-1.5 bg-white/90 dark:bg-zinc-900/90 backdrop-blur p-1.5 rounded-xl border border-border/60 shadow-xl">
          <button 
            onClick={() => handleZoom('in')}
            className="p-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg text-concrete hover:text-ink transition-colors"
            title="Zoomer (+)"
          >
            <Plus size={15} />
          </button>
          
          <button 
            onClick={() => handleZoom('out')}
            className="p-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg text-concrete hover:text-ink transition-colors"
            title="Dézoomer (-)"
          >
            <Minus size={15} />
          </button>

          <div className="h-[1px] bg-border mx-1 my-0.5" />

          <button 
            onClick={() => handleZoom('fit')}
            className="p-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg text-concrete hover:text-accent transition-colors"
            title="Ajuster la vue"
          >
            <Maximize2 size={13} />
          </button>
        </div>

        {/* Floating Controls Panel (top right) */}
        <div className="absolute top-5 right-5 z-20 w-[300px] hidden lg:block">
          <div className="bg-white/95 dark:bg-zinc-950/95 backdrop-blur border border-neutral-200 dark:border-neutral-800 rounded-2xl shadow-xl overflow-hidden">
            <div className="px-4.5 py-3 border-b border-neutral-100 dark:border-neutral-800 bg-neutral-50/50 dark:bg-neutral-900/10 flex items-center justify-between">
              <span className="text-[10px] font-bold uppercase tracking-widest text-concrete flex items-center gap-1.5">
                <Sliders size={11} className="text-secondary" />
                Paramètres du Graphe
              </span>
              <button 
                onClick={() => {
                  setShowSemanticLinks(true);
                  setMinSemanticStrength(0.40);
                  selectAllCarnets();
                }}
                className="text-[9px] font-bold uppercase text-accent hover:text-accent/80 transition-colors"
                title="Rétablir par défaut"
              >
                Reset
              </button>
            </div>

            <div className="p-4 space-y-4">
              {/* Semantic Link Toggle Details */}
              <div className="space-y-2">
                <div className="flex items-center justify-between">
                  <label htmlFor="semantic-links-toggle" className="text-[11px] font-bold text-ink dark:text-dark-ink flex items-center gap-1.5">
                    <Sparkles size={12} className="text-indigo-500" />
                    Liens sémantiques
                  </label>
                  <input 
                    id="semantic-links-toggle"
                    type="checkbox"
                    checked={showSemanticLinks}
                    onChange={(e) => setShowSemanticLinks(e.target.checked)}
                    className="w-4 h-4 text-accent border-gray-300 rounded focus:ring-accent"
                  />
                </div>
                <p className="text-[10px] text-concrete leading-normal pl-5">
                  Visualiser la couche d'affinité IA générée par embeddings sémantiques (Memory Echo).
                </p>
              </div>

              {/* Slider for semantic filtering threshold - Displayed only if activated */}
              {showSemanticLinks && (
                <div className="pt-1.5 pb-0.5 space-y-2.5 border-t border-neutral-100 dark:border-neutral-800">
                  <div className="flex justify-between items-center text-[10px] font-bold text-concrete">
                    <span>Force minimum sémantique</span>
                    <span className="font-mono text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-950/40 px-1.5 py-0.5 rounded">
                      {(minSemanticStrength * 100).toFixed(0)}%
                    </span>
                  </div>
                  
                  <div className="flex items-center gap-2">
                    <span className="text-[9px] font-mono text-concrete">0.2</span>
                    <input 
                      type="range"
                      min="0.20"
                      max="0.85"
                      step="0.05"
                      value={minSemanticStrength}
                      onChange={(e) => setMinSemanticStrength(parseFloat(e.target.value))}
                      className="w-full h-1.5 bg-neutral-200 dark:bg-neutral-800 rounded-lg appearance-none cursor-pointer accent-indigo-600"
                    />
                    <span className="text-[9px] font-mono text-concrete font-bold">0.85</span>
                  </div>
                </div>
              )}

              {/* Filter by Carnets with Checkboxes */}
              <div className="pt-3 border-t border-neutral-100 dark:border-neutral-800 space-y-2.5">
                <div className="flex items-center justify-between text-[11px] font-bold text-ink dark:text-dark-ink">
                  <span className="flex items-center gap-1.5">
                    <Layers size={11} className="text-emerald-500" />
                    Filtrer par Carnet ({selectedCarnetIds.length})
                  </span>
                  <div className="flex items-center gap-2 text-[9px] text-concrete">
                    <button onClick={selectAllCarnets} className="hover:underline">Tous</button>
                    <span>•</span>
                    <button onClick={clearAllCarnets} className="hover:underline">Aucun</button>
                  </div>
                </div>

                <div className="space-y-1.5 max-h-[140px] overflow-y-auto pr-1">
                  {carnets.map(c => {
                    const isChecked = selectedCarnetIds.includes(c.id);
                    const carnetColor = CARNET_COLOR_PALETTE[c.id] || DEFAULT_CARNET_COLOR;
                    return (
                      <label 
                        key={c.id} 
                        className="flex items-center justify-between text-[10.5px] text-concrete hover:text-ink cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-900/40 py-1 px-1.5 rounded transition-colors"
                      >
                        <span className="flex items-center gap-2">
                          <span className="w-2 h-2 rounded-full shrink-0" style={{ backgroundColor: carnetColor }} />
                          <span className="truncate max-w-[150px]">{c.name}</span>
                        </span>
                        
                        <input 
                          type="checkbox"
                          checked={isChecked}
                          onChange={() => toggleCarnetSelector(c.id)}
                          className="w-3.5 h-3.5 text-accent border-gray-300 rounded focus:ring-accent"
                        />
                      </label>
                    );
                  })}
                </div>
              </div>
            </div>
          </div>
        </div>

        {/* Dynamic Tooltip Hover UI Card (In case of node hovering) */}
        <AnimatePresence>
          {hoveredNode && !activeLocalNode && (
            <motion.div
              initial={{ opacity: 0, scale: 0.95, y: 15 }}
              animate={{ opacity: 1, scale: 1, y: 0 }}
              exit={{ opacity: 0, scale: 0.95 }}
              className="absolute bottom-8 left-8 z-30 w-[280px] bg-zinc-950 text-white rounded-xl shadow-2xl p-4.5 border border-zinc-800 space-y-3.5"
            >
              <div className="space-y-1.5">
                <div className="flex items-center gap-2 justify-between">
                  <span className="text-[9px] font-bold uppercase tracking-wider px-2 py-0.5 rounded text-white font-mono" style={{ backgroundColor: hoveredNode.color }}>
                    {hoveredNode.carnetName}
                  </span>
                  
                  <span className="text-[9.5px] font-mono text-zinc-400">
                    Modifié le : {hoveredNode.date}
                  </span>
                </div>

                <h4 className="text-xs font-bold leading-tight line-clamp-2 text-zinc-100 font-serif">
                  {hoveredNode.title}
                </h4>
              </div>

              {/* Micro Metrics stats */}
              <div className="grid grid-cols-2 gap-2 border-t border-zinc-900 pt-3">
                <div className="bg-zinc-900/50 p-2 rounded-lg text-center">
                  <span className="text-[9px] block text-zinc-500 uppercase tracking-wider">Connexions</span>
                  <p className="text-xs font-black text-indigo-400">{hoveredNode.degree}</p>
                </div>
                
                <div className="bg-zinc-900/50 p-2 rounded-lg text-center">
                  <span className="text-[9px] block text-zinc-500 uppercase tracking-wider">Tags détectés</span>
                  <p className="text-xs font-black text-cyan-400">{hoveredNode.tags.length || 0}</p>
                </div>
              </div>

              <div className="text-[9.5px] text-zinc-400 font-medium italic flex items-center justify-center gap-1">
                <span>Cliquez pour isoler / modifier</span>
              </div>
            </motion.div>
          )}
        </AnimatePresence>

        {/* SVG Core Render canvas */}
        <svg ref={svgRef} className="w-full h-full" />
      </div>

      {/* State D: Note focus right panel slider (280px width) */}
      <AnimatePresence>
        {activeLocalNode && (
          <motion.div 
            initial={{ x: '100%', opacity: 0 }}
            animate={{ x: 0, opacity: 1 }}
            exit={{ x: '100%', opacity: 0 }}
            transition={{ type: 'spring', damping: 25, stiffness: 180 }}
            className="w-[320px] bg-white dark:bg-neutral-950 border-l border-neutral-200 dark:border-neutral-800 shadow-2xl z-20 flex flex-col justify-between"
          >
            {/* Panel header and close button */}
            <div className="p-5 border-b border-neutral-100 dark:border-neutral-800">
              <div className="flex items-center justify-between mb-4.5">
                <div className="flex items-center gap-2 text-[10px] uppercase font-bold tracking-widest text-[#4f46e5]">
                  <Sparkles size={12} className="text-indigo-500 animate-[pulse_3s_infinite]" />
                  Aperçu de Note
                </div>
                
                <button 
                  onClick={handleResetLocalView}
                  className="p-1 px-2.5 rounded hover:bg-neutral-50 dark:hover:bg-neutral-900 text-[10.5px] font-bold tracking-tight text-concrete hover:text-ink select-none border border-neutral-200 dark:border-neutral-800"
                >
                  Fermer
                </button>
              </div>

              {/* Note details */}
              <div className="space-y-2">
                <div className="flex items-center gap-1.5 text-[9.5px] font-bold text-zinc-400">
                  <span className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: activeLocalNode.color }} />
                  <span className="uppercase tracking-wider truncate max-w-[200px]">{activeLocalNode.carnetName}</span>
                </div>

                <h3 className="text-sm font-black text-ink dark:text-dark-ink font-serif leading-tight">
                  {activeLocalNode.title}
                </h3>
                
                <p className="text-[10px] text-concrete font-mono flex items-center gap-1">
                  <Calendar size={10} />
                  Dernier update : {activeLocalNode.date}
                </p>
              </div>
            </div>

            {/* Snippet body content */}
            <div className="flex-1 p-5 overflow-y-auto space-y-4">
              <div className="space-y-1">
                <span className="text-[9px] uppercase font-bold tracking-widest text-concrete block">Résumé / Extrait</span>
                <p className="text-xs text-ink/80 dark:text-dark-ink/80 italic leading-relaxed bg-[#FAF9F5]/40 dark:bg-neutral-900 p-3.5 rounded-xl border border-[#FAF9F5] dark:border-neutral-900 select-all">
                  "{activeLocalNode.snippet}"
                </p>
              </div>

              {/* Relationship listing */}
              <div className="space-y-2">
                <span className="text-[9px] uppercase font-bold tracking-widest text-concrete block">
                  Éléments connectés ({getLocalNodeNeighbors(activeLocalNode.id).size - 1})
                </span>
                
                <div className="space-y-1.5 max-h-[160px] overflow-y-auto pr-1">
                  {notes
                    .filter(n => n.id !== activeLocalNode.id && getLocalNodeNeighbors(activeLocalNode.id).has(n.id))
                    .map(neighbor => {
                      return (
                        <div 
                          key={neighbor.id}
                          onClick={() => {
                            const foundNode = graphData.nodes.find(v => v.id === neighbor.id);
                            if (foundNode) handleSelectNode(foundNode);
                          }}
                          className="flex items-center justify-between text-[10px] p-2 bg-neutral-50 dark:bg-neutral-900/60 rounded-xl hover:bg-neutral-100 cursor-pointer border border-transparent hover:border-border transition-colors group"
                        >
                          <span className="font-semibold text-ink dark:text-dark-ink truncate max-w-[170px] flex items-center gap-1.5">
                            <span className="w-1.5 h-1.5 rounded-full shrink-0" style={{ backgroundColor: CARNET_COLOR_PALETTE[neighbor.carnetId] || DEFAULT_CARNET_COLOR }} />
                            {neighbor.title}
                          </span>
                          <span className="text-[8px] font-bold uppercase tracking-wider text-concrete group-hover:text-accent group-hover:underline">
                            Séléctionner
                          </span>
                        </div>
                      );
                    })}
                </div>
              </div>

              {/* Tags panel detail */}
              {activeLocalNode.tags && activeLocalNode.tags.length > 0 && (
                <div className="space-y-2">
                  <span className="text-[9px] uppercase font-bold tracking-widest text-concrete block">Index de tags</span>
                  <div className="flex flex-wrap gap-1">
                    {activeLocalNode.tags.map((t, idx) => (
                      <span 
                        key={idx} 
                        className="text-[9px] font-semibold uppercase tracking-wider border border-border bg-neutral-50/40 text-concrete px-2 py-0.5 rounded-full"
                      >
                        {t.label}
                      </span>
                    ))}
                  </div>
                </div>
              )}
            </div>

            {/* CTA action bottom block */}
            <div className="p-5 border-t border-neutral-100 dark:border-neutral-800 bg-neutral-50/50 dark:bg-neutral-900/10 flex flex-col gap-2.5">
              <button
                onClick={() => onOpenNote(activeLocalNode.id)}
                className="w-full py-3.5 bg-ink text-paper dark:bg-neutral-50 dark:text-zinc-950 rounded-xl text-xs font-bold uppercase tracking-widest hover:opacity-95 transition-all text-center flex items-center justify-center gap-1.5 shadow-xl shadow-black/10 scale-100 active:scale-95"
              >
                <FileText size={13} />
                Ouvrir la note
              </button>
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
};
