'use client'

import { useEffect, useState, useMemo, useCallback, useRef } from 'react'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/navigation'
import { useNotebooks } from '@/context/notebooks-context'
import { openNotePath } from '@/lib/navigation/open-note'
import {
  Loader2,
  Network,
  Filter,
  X,
  ExternalLink,
  Maximize2,
  Calendar,
  Clock,
  Link2,
  FileText,
  Check,
  Tag,
  Sparkles,
  ChevronRight,
  BookOpen
} from 'lucide-react'
import DOMPurify from 'isomorphic-dompurify'
import { markdownToHtml } from '@/lib/markdown-to-html'
import { useLanguage } from '@/lib/i18n'
import { LabelBadge } from './label-badge'
import { NoteChecklist } from './note-checklist'

const ForceGraph2D = dynamic(() => import('react-force-graph-2d'), { ssr: false })
const MarkdownContent = dynamic(() => import('./markdown-content').then(m => ({ default: m.MarkdownContent })), {
  ssr: false,
  loading: () => <div className="h-20 w-full animate-pulse bg-concrete/5 rounded" />
})

interface GraphNode { id: string; title: string; notebookId: string | null; createdAt: string; degree: number }
interface GraphEdge { source: string; target: string; weight: number; type: string }
interface Cluster { id: string; name: string }
interface RawData { nodes: GraphNode[]; edges: GraphEdge[]; clusters: Cluster[] }
interface NotePreview {
  id: string
  title: string | null
  content: string
  createdAt: string | Date
  updatedAt?: string | Date
  labels?: string[] | null
  type?: 'text' | 'markdown' | 'richtext' | 'checklist'
  checkItems?: { id: string; text: string; checked: boolean }[] | null
  isMarkdown?: boolean
}

const PALETTE = ['#6366f1', '#10b981', '#f59e0b', '#ec4899', '#14b8a6', '#8b5cf6', '#ef4444', '#3b82f6', '#84cc16', '#A47148']

type EdgeTypeKey = 'explicit_link' | 'semantic_echo' | 'title_mention' | 'shared_label' | 'jaccard'

const DEFAULT_EDGE_FILTERS: Record<EdgeTypeKey, boolean> = {
  explicit_link: true,
  semantic_echo: true,
  title_mention: true,
  shared_label: true,
  jaccard: false,
}

export function NoteGraphView({ embedded = false }: { embedded?: boolean }) {
  const router = useRouter()
  const { notebooks } = useNotebooks()
  const containerRef = useRef<HTMLDivElement>(null)
  const graphRef = useRef<any>(null)
  const existingNodesRef = useRef<Map<string, any>>(new Map())
  const [dimensions, setDimensions] = useState({ width: 800, height: 600 })
  const [rawData, setRawData] = useState<RawData | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [searchFilter, setSearchFilter] = useState('')
  const [selectedNode, setSelectedNode] = useState<GraphNode | null>(null)
  const [notePreview, setNotePreview] = useState<NotePreview | null>(null)
  const [previewLoading, setPreviewLoading] = useState(false)
  const [selectedNotebookId, setSelectedNotebookId] = useState<string | null>(null)
  const [edgeFilters, setEdgeFilters] = useState(DEFAULT_EDGE_FILTERS)
  const [semanticMinWeight, setSemanticMinWeight] = useState(0.45)
  const [focusNodeId, setFocusNodeId] = useState<string | null>(null)
  const [controlsOpen, setControlsOpen] = useState(!embedded)

  const { t, language } = useLanguage()

  const plainText = useCallback((html: string | null | undefined) =>
    (html ?? '')
      .replace(/<[^>]+>/g, ' ')
      .replace(/#{1,6}\s/g, '')
      .replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1')
      .replace(/_{1,2}([^_]+)_{1,2}/g, '$1')
      .replace(/`[^`]+`/g, '')
      .replace(/!?\[[^\]]*\]\([^)]*\)/g, '')
      .replace(/\s+/g, ' ').trim().slice(0, 400), [])

  const htmlContent = useMemo(() => {
    if (!notePreview?.content) return ''
    const isMarkdown = notePreview.type === 'markdown' || notePreview.isMarkdown || (!notePreview.content.includes('<') && !notePreview.content.includes('</'))
    let rawHtml = notePreview.content
    if (isMarkdown) {
      rawHtml = markdownToHtml(notePreview.content)
    }
    return DOMPurify.sanitize(rawHtml)
  }, [notePreview])

  const wordCount = useMemo(() => {
    if (!notePreview?.content) return 0
    const text = plainText(notePreview.content)
    return text.split(/\s+/).filter(Boolean).length
  }, [notePreview, plainText])

  const charCount = useMemo(() => {
    if (!notePreview?.content) return 0
    return plainText(notePreview.content).length
  }, [notePreview, plainText])

  const isRtl = useMemo(() => {
    if (!notePreview?.content) return false
    const sample = plainText(notePreview.content).replace(/\s+/g, '').slice(0, 400)
    const rtlChars = /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/
    let rtl = 0
    let ltr = 0
    for (const ch of sample) {
      if (rtlChars.test(ch)) rtl++
      else if (/[A-Za-z]/.test(ch)) ltr++
    }
    return rtl > 0 && rtl >= ltr
  }, [notePreview, plainText])

  // ─── Resize ───────────────────────────────────────────────────────────────
  useEffect(() => {
    const el = containerRef.current
    if (!el) return
    const ro = new ResizeObserver(entries => {
      const { width, height } = entries[0].contentRect
      setDimensions({ width: Math.floor(width), height: Math.floor(height) })
    })
    ro.observe(el)
    return () => ro.disconnect()
  }, [])

  // ─── Fetch data ───────────────────────────────────────────────────────────
  useEffect(() => {
    setLoading(true)
    fetch('/api/graph')
      .then(r => { if (!r.ok) throw new Error('Erreur réseau'); return r.json() })
      .then(d => setRawData(d))
      .catch(e => setError(e.message))
      .finally(() => setLoading(false))
  }, [])

  // ─── Configure forces once graph is mounted ───────────────────────────────
  const forcesConfigured = useRef(false)
  useEffect(() => {
    if (!rawData || forcesConfigured.current) return
    // Wait for the ForceGraph to mount
    const timer = setTimeout(() => {
      const fg = graphRef.current
      if (!fg) return
      fg.d3Force('charge')?.strength(-120)
      fg.d3Force('link')?.distance(55)
      fg.d3Force('center')?.strength(0.05)
      forcesConfigured.current = true
    }, 200)
    return () => clearTimeout(timer)
  }, [rawData])

  // ─── Note preview ─────────────────────────────────────────────────────────
  useEffect(() => {
    if (!selectedNode) { setNotePreview(null); return }
    setPreviewLoading(true)
    fetch(`/api/notes/${selectedNode.id}`)
      .then(r => r.ok ? r.json() : null)
      .then(res => setNotePreview(res?.data ?? null))
      .catch(() => setNotePreview(null))
      .finally(() => setPreviewLoading(false))
  }, [selectedNode])

  // ─── Color map ────────────────────────────────────────────────────────────
  const colorMap = useMemo(() => {
    if (!rawData) return new Map<string | null, string>()
    const map = new Map<string | null, string>()
    const ids = [...new Set(rawData.nodes.map(n => n.notebookId).filter(Boolean))] as string[]
    ids.forEach((id, i) => {
      const nb = notebooks.find(n => n.id === id)
      map.set(id, nb?.color || PALETTE[i % PALETTE.length])
    })
    return map
  }, [rawData, notebooks])

  const neighborIds = useMemo(() => {
    if (!focusNodeId || !rawData) return null
    const ids = new Set<string>([focusNodeId])
    for (const edge of rawData.edges) {
      if (edge.source === focusNodeId) ids.add(edge.target)
      if (edge.target === focusNodeId) ids.add(edge.source)
    }
    return ids
  }, [focusNodeId, rawData])

  // ─── Graph data ───────────────────────────────────────────────────────────
  const graphData = useMemo(() => {
    if (!rawData) return { nodes: [], links: [] }

    let filtered = selectedNotebookId
      ? rawData.nodes.filter(n => n.notebookId === selectedNotebookId)
      : rawData.nodes

    if (neighborIds) {
      filtered = filtered.filter(n => neighborIds.has(n.id))
    }

    filtered = searchFilter.trim()
      ? filtered.filter(n => n.title.toLowerCase().includes(searchFilter.toLowerCase()))
      : filtered

    const filteredIds = new Set(filtered.map(n => n.id))
    const visibleEdges = rawData.edges.filter(e => {
      const type = e.type as EdgeTypeKey
      if (!(type in edgeFilters) || !edgeFilters[type]) return false
      if (type === 'semantic_echo' && e.weight < semanticMinWeight) return false
      return filteredIds.has(e.source) && filteredIds.has(e.target)
    })

    return {
      nodes: filtered.map(n => {
        const existing = existingNodesRef.current.get(n.id)
        if (existing) {
          existing.name = n.title
          existing.val = 1 + Math.min(n.degree, 8) * 0.5
          existing.color = colorMap.get(n.notebookId) ?? '#94a3b8'
          existing.notebookId = n.notebookId
          existing.degree = n.degree
          return existing
        }
        const newNode = {
          id: n.id,
          name: n.title,
          val: 1 + Math.min(n.degree, 8) * 0.5,
          color: colorMap.get(n.notebookId) ?? '#94a3b8',
          notebookId: n.notebookId,
          degree: n.degree,
        }
        existingNodesRef.current.set(n.id, newNode)
        return newNode
      }),
      links: visibleEdges.map(e => {
        let color = '#cbd5e1'
        let width = 2.5
        let dash = false

        if (e.type === 'explicit_link') {
          color = '#10b981'
          width = 4.5
        } else if (e.type === 'semantic_echo') {
          color = '#8b5cf6'
          width = 3.5
          dash = true
        } else if (e.type === 'title_mention') {
          color = '#f59e0b'
          width = 3.2
        } else if (e.type === 'shared_label') {
          color = '#3b82f6'
          width = 2.8
        }

        return {
          source: e.source,
          target: e.target,
          color,
          width,
          dash,
          type: e.type,
        }
      }),
    }
  }, [rawData, searchFilter, colorMap, selectedNotebookId, edgeFilters, semanticMinWeight, neighborIds])

  const selectedNotebookName = useMemo(() => {
    if (!selectedNode || !rawData) return null
    return rawData.clusters.find(c => c.id === selectedNode.notebookId)?.name ?? null
  }, [selectedNode, rawData])

  // ─── Handlers (double-click via timer) ──────────────────────────────────
  const lastClickRef = useRef<{ id: string; time: number } | null>(null)

  const handleNodeClick = useCallback((node: any) => {
    if (!rawData) return
    const now = Date.now()
    const last = lastClickRef.current
    if (last && last.id === node.id && now - last.time < 350) {
      lastClickRef.current = null
      router.push(openNotePath(node.id))
      return
    }
    lastClickRef.current = { id: node.id, time: now }
    setSelectedNode(rawData.nodes.find(n => n.id === node.id) ?? null)
  }, [rawData, router])

  const handleZoomToFit = useCallback(() => {
    graphRef.current?.zoomToFit(400, 50)
  }, [])

  const toggleEdgeFilter = useCallback((key: EdgeTypeKey) => {
    setEdgeFilters(prev => ({ ...prev, [key]: !prev[key] }))
  }, [])

  // Zoom vers le premier nœud correspondant à la recherche
  useEffect(() => {
    if (!searchFilter.trim() || graphData.nodes.length === 0) return
    const timer = window.setTimeout(() => {
      const fg = graphRef.current
      if (!fg) return
      const match = fg.graphData()?.nodes?.find((n: { id: string; name?: string }) =>
        (n.name ?? '').toLowerCase().includes(searchFilter.toLowerCase())
      )
      if (match?.x != null && match?.y != null) {
        fg.centerAt(match.x, match.y, 500)
        fg.zoom(2.2, 500)
      }
    }, 600)
    return () => window.clearTimeout(timer)
  }, [searchFilter, graphData.nodes.length])



  // ─── Cluster painting (stable ref, no deps) ──────────────────────────────
  const dataRef = useRef<{ nodes: any[]; colorMap: Map<string|null,string>; clusters: Cluster[] }>({ nodes: [], colorMap: new Map(), clusters: [] })
  dataRef.current = { nodes: graphData.nodes, colorMap, clusters: rawData?.clusters ?? [] }

  const paintClusters = useRef((ctx: CanvasRenderingContext2D, globalScale: number) => {
    const { nodes, colorMap: cm, clusters } = dataRef.current
    if (!nodes || nodes.length === 0) return

    const groups = new Map<string, { x: number; y: number }[]>()
    for (const node of nodes) {
      if (!node.notebookId || node.x === undefined || node.y === undefined) continue
      if (!groups.has(node.notebookId)) groups.set(node.notebookId, [])
      groups.get(node.notebookId)!.push({ x: node.x, y: node.y })
    }

    for (const [nbId, pts] of groups) {
      if (pts.length < 3) continue
      const color = cm.get(nbId) ?? '#94a3b8'
      const cx = pts.reduce((s, p) => s + p.x, 0) / pts.length
      const cy = pts.reduce((s, p) => s + p.y, 0) / pts.length
      let maxR = 0
      for (const p of pts) {
        const d = Math.sqrt((p.x - cx) ** 2 + (p.y - cy) ** 2)
        if (d > maxR) maxR = d
      }
      const r = maxR + 30

      ctx.beginPath()
      ctx.arc(cx, cy, r, 0, 2 * Math.PI)
      ctx.fillStyle = color + '0A'
      ctx.fill()
      ctx.strokeStyle = color + '30'
      ctx.lineWidth = 1.5 / globalScale
      ctx.setLineDash([5 / globalScale, 5 / globalScale])
      ctx.stroke()
      ctx.setLineDash([])

      // Cluster name
      if (globalScale > 0.4) {
        const name = clusters.find(c => c.id === nbId)?.name ?? ''
        if (name) {
          const fs = Math.min(12, 9 / globalScale)
          ctx.font = `600 ${fs}px -apple-system, sans-serif`
          ctx.fillStyle = color + 'BB'
          ctx.textAlign = 'center'
          ctx.textBaseline = 'bottom'
          ctx.fillText(name, cx, cy - r + 4)
        }
      }
    }
  }).current

  // ─── Render ───────────────────────────────────────────────────────────────
  return (
    <div className={`flex flex-col h-full ${embedded ? 'bg-transparent' : 'bg-[#FAFAF9]'}`}>
      {!embedded && (
      <div className="px-5 py-3 flex items-center gap-4 shrink-0 border-b border-border/40 bg-white">
        <Network size={16} className="text-indigo-500" />
        <h1 className="text-sm font-semibold text-ink">{t('graphView.title')}</h1>
        {rawData && (
          <span className="text-[10px] text-concrete/50 font-medium">
            {t('graphView.notesCount', { count: rawData.nodes.length })} · {t('graphView.connectionsCount', { count: rawData.edges.length })}
            {graphData.links.length !== rawData.edges.length && (
              <> · {t('graphView.visibleConnections', { count: graphData.links.length })}</>
            )}
          </span>
        )}
        <div className="flex-1" />
        <div className="relative">
          <Filter size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-concrete/40" />
          <input
            type="text"
            placeholder={t('graphView.searchPlaceholder')}
            value={searchFilter}
            onChange={e => setSearchFilter(e.target.value)}
            className="pl-7 pr-7 py-1.5 bg-white border border-border/60 rounded-md text-xs text-ink outline-none focus:border-indigo-400 w-44 placeholder:text-concrete/40"
          />
          {searchFilter && (
            <button onClick={() => setSearchFilter('')} className="absolute right-2 top-1/2 -translate-y-1/2 text-concrete/40 hover:text-ink">
              <X size={12} />
            </button>
          )}
        </div>
      </div>
      )}

      {/* Canvas */}
      <div ref={containerRef} className="flex-1 relative overflow-hidden">
        {loading && (
          <div className="absolute inset-0 flex items-center justify-center bg-[#FAFAF9]">
            <Loader2 size={24} className="animate-spin text-concrete/40" />
          </div>
        )}
        {error && (
          <div className="absolute inset-0 flex items-center justify-center">
            <p className="text-sm text-rose-500">{error}</p>
          </div>
        )}
        {!loading && !error && graphData.nodes.length > 0 && (
          <ForceGraph2D
            ref={graphRef}
            graphData={graphData}
            width={dimensions.width}
            height={dimensions.height}
            backgroundColor="#FAFAF9"
            nodeRelSize={5}
            nodeVal="val"
            nodeColor="color"
            nodeLabel="name"
            linkColor="color"
            linkWidth="width"
            linkOpacity={0.92}
            linkDirectionalParticles={2}
            linkDirectionalParticleWidth={2.5}
            linkLineDash={(link: any) => link.dash ? [6, 4] : null}
            onNodeClick={handleNodeClick}
            onNodeHover={(node: any) => {
              if (containerRef.current) containerRef.current.style.cursor = node ? 'pointer' : 'default'
            }}
            onRenderFramePre={paintClusters}
            nodeCanvasObjectMode={() => 'after'}
            nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
              const n = node as any
              if (globalScale < 0.7) return
              const name: string = n.name ?? ''
              const label = name.length > 20 ? name.slice(0, 18) + '…' : name
              const fontSize = 11 / globalScale
              if (fontSize > 18) return
              ctx.font = `${fontSize}px -apple-system, sans-serif`
              ctx.textAlign = 'center'
              ctx.textBaseline = 'top'
              const r = Math.sqrt(n.val ?? 1) * 5
              // White background behind label
              const tw = ctx.measureText(label).width
              const lx = n.x - tw / 2 - 2
              const ly = n.y + r + 2
              ctx.fillStyle = 'rgba(250,250,249,0.85)'
              ctx.fillRect(lx, ly, tw + 4, fontSize + 2)
              // Label text
              ctx.fillStyle = '#334155'
              ctx.fillText(label, n.x, ly + 1)
            }}
            cooldownTicks={80}
            d3AlphaDecay={0.03}
            d3VelocityDecay={0.4}
          />
        )}
        {!loading && !error && graphData.nodes.length === 0 && (
          <div className="absolute inset-0 flex flex-col items-center justify-center gap-3 text-concrete/40">
            <Network size={32} />
            <p className="text-xs">{t('graphView.noNotesFound')}</p>
          </div>
        )}

        {/* Zoom to fit */}
        {!loading && graphData.nodes.length > 0 && (
          <button
            onClick={handleZoomToFit}
            className="absolute top-4 right-4 z-10 flex items-center gap-1.5 px-3 py-1.5 bg-white border border-border/50 rounded-md text-[11px] text-ink font-medium shadow-sm hover:bg-gray-50 transition-colors animate-fade-in"
          >
            <Maximize2 size={12} />
            {t('graphView.globalView')}
          </button>
        )}

        {/* Cluster legend (Interactive Notebook Filter) */}
        {rawData && rawData.clusters && rawData.clusters.length > 0 && (
          <div className="absolute top-4 left-4 z-10 flex flex-col gap-2 max-h-[42vh] overflow-y-auto pr-1">
            <span className="text-[9px] font-bold text-slate-800 uppercase tracking-wider pl-1 select-none">{t('graphView.notebooks')}</span>
            {(selectedNotebookId || focusNodeId) && (
              <div className="flex flex-col gap-1">
                {selectedNotebookId && (
                  <button
                    onClick={() => setSelectedNotebookId(null)}
                    className="flex items-center gap-1.5 px-2.5 py-1 bg-white border border-rose-200 text-rose-600 rounded-full shadow-sm hover:bg-rose-50 transition-all text-[9px] font-semibold w-fit"
                  >
                    <X size={10} />
                    {t('graphView.resetFilter')}
                  </button>
                )}
                {focusNodeId && (
                  <button
                    onClick={() => setFocusNodeId(null)}
                    className="flex items-center gap-1.5 px-2.5 py-1 bg-white border border-indigo-200 text-indigo-600 rounded-full shadow-sm hover:bg-indigo-50 transition-all text-[9px] font-semibold w-fit"
                  >
                    <X size={10} />
                    {t('graphView.resetFocus')}
                  </button>
                )}
              </div>
            )}
            <div className="flex flex-col gap-1.5">
              {rawData.clusters.map(c => {
                const isSelected = selectedNotebookId === c.id
                const isAnySelected = selectedNotebookId !== null
                const color = colorMap.get(c.id) ?? '#94a3b8'
                return (
                  <button
                    key={c.id}
                    onClick={() => setSelectedNotebookId(isSelected ? null : c.id)}
                    className={`flex items-center gap-2 px-3 py-1 bg-white border rounded-full shadow-sm transition-all duration-200 hover:scale-105 w-fit text-left ${
                      isSelected
                        ? 'border-indigo-500 ring-2 ring-indigo-500/20 font-semibold'
                        : isAnySelected
                        ? 'border-border/30 opacity-40 hover:opacity-100'
                        : 'border-border/30 hover:border-concrete/40'
                    }`}
                  >
                    <span className="w-2.5 h-2.5 rounded-full shrink-0" style={{ backgroundColor: color }} />
                    <span className="text-[10px] text-concrete/80 whitespace-nowrap">{c.name}</span>
                    {isSelected && <X size={10} className="text-concrete/40 ml-0.5 shrink-0" />}
                  </button>
                )
              })}
            </div>
          </div>
        )}

        {/* Filtres de liens + seuil sémantique */}
        {!loading && !error && rawData && (
          <div className="absolute bottom-4 left-4 z-10 flex flex-col gap-2 max-w-[220px]">
            <button
              type="button"
              onClick={() => setControlsOpen(v => !v)}
              className="flex items-center gap-2 px-3 py-1.5 bg-white/95 border border-border/40 rounded-lg shadow-sm text-[10px] font-semibold text-slate-700 w-fit"
            >
              <Filter size={12} />
              {t('graphView.linkFilters')}
            </button>
            {controlsOpen && (
              <div className="p-3 bg-white/95 border border-border/40 rounded-lg shadow-sm space-y-2.5 select-none">
                <h3 className="text-[9px] font-bold text-slate-800 uppercase tracking-wider">{t('graphView.relationshipTypes')}</h3>
                {([
                  ['explicit_link', t('graphView.edgeTypes.explicitLink')],
                  ['semantic_echo', t('graphView.edgeTypes.semanticEcho')],
                  ['title_mention', t('graphView.edgeTypes.titleMention')],
                  ['shared_label', t('graphView.edgeTypes.sharedLabel')],
                  ['jaccard', t('graphView.edgeTypes.jaccard')],
                ] as [EdgeTypeKey, string][]).map(([key, label]) => (
                  <label key={key} className="flex items-center gap-2.5 cursor-pointer text-[10px] text-slate-700">
                    <input
                      type="checkbox"
                      checked={edgeFilters[key]}
                      onChange={() => toggleEdgeFilter(key)}
                      className="w-3.5 h-3.5 shrink-0 rounded border-2 border-slate-300 accent-indigo-600 cursor-pointer"
                    />
                    <span>{label}</span>
                  </label>
                ))}
                {edgeFilters.semantic_echo && (
                  <div className="pt-1 border-t border-border/30 space-y-1">
                    <div className="flex items-center justify-between text-[9px] text-concrete/70">
                      <span>{t('graphView.semanticThreshold')}</span>
                      <span className="font-mono">{Math.round(semanticMinWeight * 100)}%</span>
                    </div>
                    <input
                      type="range"
                      min={0.3}
                      max={0.9}
                      step={0.05}
                      value={semanticMinWeight}
                      onChange={e => setSemanticMinWeight(Number(e.target.value))}
                      className="w-full h-1 accent-indigo-500"
                    />
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {/* Legend of relationship types (compact) */}
        {!loading && !error && graphData.nodes.length > 0 && controlsOpen && (
          <div className="absolute bottom-4 right-[21rem] z-10 hidden xl:flex flex-col gap-1.5 p-2.5 bg-white/90 border border-border/40 rounded-lg shadow-sm max-w-xs select-none pointer-events-none opacity-80">
            <div className="flex items-center gap-2">
              <span className="w-5 h-0.5 rounded shrink-0 bg-[#10b981]" />
              <span className="text-[9px] font-medium text-concrete/70">{t('graphView.edgeTypes.explicitLink')}</span>
            </div>
            <div className="flex items-center gap-2">
              <span className="w-5 border-t-2 border-dashed shrink-0 border-[#a78bfa]" />
              <span className="text-[9px] font-medium text-concrete/70">{t('graphView.edgeTypes.semanticEcho')}</span>
            </div>
          </div>
        )}

        {/* Note detail panel */}
        {selectedNode && (
          <div className="absolute inset-y-0 right-0 w-80 backdrop-blur-md bg-white/95 dark:bg-stone-900/95 border-l border-border/40 flex flex-col shadow-[0_8px_30px_rgb(0,0,0,0.06)] z-20 transition-all duration-300">
            {/* Header */}
            <div className="flex items-start justify-between p-5 border-b border-border/40">
              <div className="flex-1 min-w-0 pr-3">
                {selectedNotebookName && (
                  <button
                    onClick={() => setSelectedNotebookId(selectedNode.notebookId === selectedNotebookId ? null : selectedNode.notebookId)}
                    className="mb-2 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[9px] font-bold uppercase tracking-wider bg-slate-100 hover:bg-slate-200/80 text-concrete transition-all border border-border/20 hover:scale-105"
                  >
                    <span className="w-1.5 h-1.5 rounded-full shrink-0" style={{ backgroundColor: colorMap.get(selectedNode.notebookId) ?? '#94a3b8' }} />
                    {selectedNotebookName}
                  </button>
                )}
                <h2 
                  dir={isRtl ? 'rtl' : 'ltr'} 
                  className={`text-sm font-semibold text-slate-800 dark:text-slate-100 leading-snug tracking-tight select-all ${isRtl ? 'text-right font-persian font-semibold' : 'text-left font-sans'}`}
                >
                  {selectedNode.title || <span className="italic text-concrete/40">{t('notes.untitled')}</span>}
                </h2>
              </div>
              <button
                onClick={() => setSelectedNode(null)}
                className="p-1.5 rounded-full text-concrete/40 hover:text-slate-800 dark:hover:text-slate-100 hover:bg-slate-100 dark:hover:bg-stone-800 transition-all"
              >
                <X size={14} />
              </button>
            </div>

            {/* Quick Metadata Info */}
            <div className="px-5 py-3.5 bg-slate-50/50 dark:bg-stone-950/20 border-b border-border/30 grid grid-cols-2 gap-y-2 gap-x-4 text-[10px] text-concrete/60 select-none">
              <div className="flex items-center gap-1.5">
                <Calendar size={11} className="text-concrete/40 shrink-0" />
                <span className="truncate" title={new Date(selectedNode.createdAt).toLocaleString(language === 'fa' ? 'fa-IR' : language)}>
                  {new Date(selectedNode.createdAt).toLocaleDateString(language === 'fa' ? 'fa-IR' : language, { day: '2-digit', month: 'short', year: 'numeric' })}
                </span>
              </div>
              <div className="flex items-center gap-1.5">
                <Link2 size={11} className="text-concrete/40 shrink-0" />
                <span>
                  {t(selectedNode.degree === 1 ? 'graphView.connections' : 'graphView.connectionsPlural', { count: selectedNode.degree })}
                </span>
              </div>
              {!previewLoading && notePreview && (
                <>
                  <div className="flex items-center gap-1.5">
                    <FileText size={11} className="text-concrete/40 shrink-0" />
                    <span>{t('graphView.preview.words', { count: wordCount })}</span>
                  </div>
                  {notePreview.updatedAt && (
                    <div className="flex items-center gap-1.5 col-span-2 border-t border-border/20 pt-1.5 mt-0.5">
                      <Clock size={11} className="text-concrete/40 shrink-0" />
                      <span className="truncate">
                        {t('graphView.preview.updated')}{' '}
                        {new Date(notePreview.updatedAt).toLocaleDateString(language === 'fa' ? 'fa-IR' : language, { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}
                      </span>
                    </div>
                  )}
                </>
              )}
            </div>

            {/* Scrollable Content */}
            <div className="flex-1 overflow-y-auto p-5 space-y-4">
              {previewLoading ? (
                /* Sleek Skeleton Loader */
                <div className="space-y-4 animate-pulse">
                  <div className="h-4 bg-stone-200/60 dark:bg-stone-800/60 rounded w-3/4" />
                  <div className="space-y-2">
                    <div className="h-3 bg-stone-200/50 dark:bg-stone-800/50 rounded w-full" />
                    <div className="h-3 bg-stone-200/50 dark:bg-stone-800/50 rounded w-11/12" />
                    <div className="h-3 bg-stone-200/50 dark:bg-stone-800/50 rounded w-4/5" />
                  </div>
                  <div className="space-y-2 pt-2">
                    <div className="h-3 bg-stone-200/50 dark:bg-stone-800/50 rounded w-full" />
                    <div className="h-3 bg-stone-200/50 dark:bg-stone-800/50 rounded w-5/6" />
                  </div>
                </div>
              ) : !notePreview || (!notePreview.content && (!notePreview.checkItems || notePreview.checkItems.length === 0)) ? (
                <div className="flex flex-col items-center justify-center py-16 text-concrete/30 gap-2.5">
                  <FileText size={32} className="stroke-[1.2] text-concrete/20" />
                  <p className="text-xs italic font-medium">{t('graphView.preview.emptyNote')}</p>
                </div>
              ) : (
                <>
                  {/* Note Content Renderer */}
                  {notePreview.type === 'checklist' && notePreview.checkItems && notePreview.checkItems.length > 0 ? (
                    <div className="space-y-2 select-none" dir={isRtl ? 'rtl' : 'ltr'}>
                      <NoteChecklist 
                        items={notePreview.checkItems} 
                        onToggleItem={() => {}} 
                      />
                    </div>
                  ) : notePreview.type === 'markdown' || notePreview.isMarkdown ? (
                    <div className="text-xs text-slate-600 dark:text-stone-300" dir={isRtl ? 'rtl' : 'ltr'}>
                      <MarkdownContent 
                        content={notePreview.content}
                        className={`prose-h1:text-sm prose-h1:font-bold prose-h1:text-slate-800 dark:prose-h1:text-stone-100 prose-h1:mt-3 prose-h1:mb-1 prose-h2:text-xs prose-h2:font-bold prose-h2:text-slate-700 dark:prose-h2:text-stone-200 prose-h2:mt-2 prose-h2:mb-1 prose-p:text-xs prose-p:leading-relaxed prose-p:mb-2 prose-ul:list-disc prose-ul:pl-4 prose-ol:list-decimal prose-ol:pl-4 ${isRtl ? 'text-right font-persian' : 'text-left font-sans'}`}
                      />
                    </div>
                  ) : (
                    <div 
                      dir={isRtl ? 'rtl' : 'ltr'}
                      className={`text-xs text-slate-600 dark:text-stone-300 space-y-2 leading-relaxed break-words ${isRtl ? 'text-right font-persian' : 'text-left font-sans'}
                        [&_h1]:text-sm [&_h1]:font-bold [&_h1]:text-slate-800 dark:[&_h1]:text-stone-100 [&_h1]:mt-4 [&_h1]:mb-1.5 [&_h1]:first:mt-0
                        [&_h2]:text-xs [&_h2]:font-bold [&_h2]:text-slate-700 dark:[&_h2]:text-stone-200 [&_h2]:mt-3 [&_h2]:mb-1 [&_h2]:first:mt-0
                        [&_h3]:text-xs [&_h3]:font-semibold [&_h3]:text-slate-600 dark:[&_h3]:text-stone-300 [&_h3]:mt-2 [&_h3]:mb-1 [&_h3]:first:mt-0
                        [&_p]:mb-2 [&_p]:last:mb-0
                        [&_ul]:list-disc [&_ul]:pl-4 [&_ul]:mb-2
                        [&_ol]:list-decimal [&_ol]:pl-4 [&_ol]:mb-2
                        [&_li]:mb-0.5
                        [&_strong]:font-semibold [&_strong]:text-slate-800 dark:[&_strong]:text-stone-100
                        [&_em]:italic
                        [&_code]:px-1 [&_code]:py-0.5 [&_code]:bg-slate-100 dark:[&_code]:bg-stone-850 [&_code]:rounded [&_code]:font-mono [&_code]:text-[10px]
                        [&_pre]:p-2.5 [&_pre]:bg-slate-900 [&_pre]:text-slate-100 [&_pre]:rounded-lg [&_pre]:overflow-x-auto [&_pre]:font-mono [&_pre]:text-[10px] [&_pre]:my-2
                        [&_blockquote]:border-l-2 [&_blockquote]:border-slate-300 dark:[&_blockquote]:border-stone-700 [&_blockquote]:pl-3 [&_blockquote]:italic [&_blockquote]:text-slate-500 [&_blockquote]:my-2
                        [&_a]:text-indigo-600 dark:[&_a]:text-indigo-400 [&_a]:underline [&_a]:hover:text-indigo-500`}
                      dangerouslySetInnerHTML={{ __html: htmlContent }} 
                    />
                  )}

                  {/* Refined Tags list */}
                  {Array.isArray(notePreview.labels) && notePreview.labels.length > 0 && (
                    <div className="border-t border-border/20 pt-4 mt-4 select-none">
                      <div className="flex items-center gap-1 text-[10px] font-bold text-slate-800 dark:text-stone-300 uppercase tracking-wider mb-2">
                        <Tag size={10} className="text-concrete/40 shrink-0" />
                        <span>{t('graphView.preview.tags')}</span>
                      </div>
                      <div className="flex flex-wrap gap-1.5">
                        {notePreview.labels.map((label: string) => (
                          <LabelBadge key={label} label={label} variant="default" />
                        ))}
                      </div>
                    </div>
                  )}
                </>
              )}
            </div>

            {/* Premium Action Footer */}
            <div className="p-4 border-t border-border/40 bg-slate-50/50 dark:bg-stone-950/20 space-y-2">
              <button
                type="button"
                onClick={() => setFocusNodeId(prev => prev === selectedNode.id ? null : selectedNode.id)}
                className="w-full flex items-center justify-center gap-2 py-2 px-4 bg-white dark:bg-stone-900 border border-border/50 hover:border-indigo-400 text-xs font-medium rounded-lg transition-colors"
              >
                <Sparkles size={12} className="text-indigo-500" />
                <span>{focusNodeId === selectedNode.id ? t('graphView.resetFocus') : t('graphView.exploreFromNode')}</span>
              </button>
              <button
                onClick={() => router.push(openNotePath(selectedNode.id))}
                className="group w-full flex items-center justify-center gap-2 py-2.5 px-4 bg-brand-accent hover:bg-brand-accent/90 text-white active:scale-[0.98] text-xs font-semibold rounded-lg shadow-sm transition-all duration-200"
              >
                <BookOpen size={12} className="group-hover:scale-110 transition-transform" />
                <span>{t('graphView.preview.openNote')}</span>
                <ChevronRight size={12} className="group-hover:translate-x-0.5 transition-transform ml-0.5" />
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  )
}
