import { useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { useQueries } from '@tanstack/react-query'
import {
  forceCenter,
  forceCollide,
  forceLink,
  forceManyBody,
  forceSimulation,
  type Simulation,
  type SimulationNodeDatum,
  type SimulationLinkDatum,
} from 'd3-force'
import { useDictionaries, dictionaryDependentsQuery } from '@/api/queries'
import type { DictionaryDefinition, SchemaDependent } from '@/api/client'
import { LoadingBlock } from '@/ui'
import { cn } from '@/lib/utils'

/**
 * Граф связей справочников — d3-force network diagram.
 *
 * Nodes: справочники (color по bundle, size по recordCount).
 * Edges: incoming FK refs (от useDictionaryDependents).
 *
 * Layout: d3-force simulation
 *  - forceLink — keeps connected nodes близко
 *  - forceManyBody (charge) — отталкивает несоседних узлов
 *  - forceCenter — anchored к (w/2, h/2)
 *  - forceCollide — prevent overlap node circles
 *
 * Interaction:
 *  - Hover node → highlight node + connected edges + neighbors
 *  - Click node → navigate /dictionaries/$name
 *  - Drag node — temp pin position, click empty space → unpin all
 *
 * Performance: 40+ nodes SVG rendering OK. Above ~200 — switch на canvas.
 */

type GraphNode = SimulationNodeDatum & {
  id: string
  name: string
  displayName: string
  bundle: string
  scope: string
  recordCount: number
}

type GraphLink = SimulationLinkDatum<GraphNode> & {
  sourceField: string
  targetField: string
}

const BUNDLE_COLORS: Record<string, string> = {
  cuod: '#b05a2e',     // accent (terracotta)
  geo: '#4f7038',      // green
  comms: '#a8762a',    // warn
  delivery: '#3a2818', // navy
  default: '#52483d',  // ink-2
}

function colorForBundle(bundle: string): string {
  return BUNDLE_COLORS[bundle] ?? BUNDLE_COLORS.default
}

export function DictionaryGraph() {
  const dictsQuery = useDictionaries()
  const dicts = dictsQuery.data ?? []

  // Fetch dependents для всех dicts параллельно (cached между mounts).
  const dependentsResults = useQueries({
    queries: dicts.map((d) => ({
      ...dictionaryDependentsQuery(d.name),
      enabled: Boolean(d.name),
    })),
  })

  const dependentsByDict = useMemo(() => {
    const m = new Map<string, SchemaDependent[]>()
    dicts.forEach((d, i) => {
      m.set(d.name, dependentsResults[i]?.data ?? [])
    })
    return m
  }, [dicts, dependentsResults])

  const allDependentsLoading = dependentsResults.some((r) => r.isLoading)

  if (dictsQuery.isLoading) {
    return <LoadingBlock size="lg" label="Загрузка справочников…" />
  }

  if (dictsQuery.error) {
    return <div className="text-body text-pink">Ошибка загрузки: {String(dictsQuery.error)}</div>
  }

  if (dicts.length === 0) {
    return <div className="text-body text-mute">Справочники не найдены.</div>
  }

  return (
    <GraphCanvas
      dicts={dicts}
      dependentsByDict={dependentsByDict}
      depsLoading={allDependentsLoading}
    />
  )
}

function GraphCanvas({
  dicts,
  dependentsByDict,
  depsLoading,
}: {
  dicts: DictionaryDefinition[]
  dependentsByDict: Map<string, SchemaDependent[]>
  depsLoading: boolean
}) {
  const navigate = useNavigate()
  const wrapperRef = useRef<HTMLDivElement>(null)
  const svgRef = useRef<SVGSVGElement>(null)
  const [size, setSize] = useState({ width: 800, height: 600 })
  const [hoveredId, setHoveredId] = useState<string | null>(null)
  // Force-rerender tick. Simulation .on('tick') увеличивает это значение,
  // React видит state change → перерисовывает SVG с новыми node.x/y. Не
  // читаем value напрямую, только инкрементируем — eslint complains, поэтому
  // используем void.
  const [, setTick] = useState(0)
  const simulationRef = useRef<Simulation<GraphNode, GraphLink> | null>(null)
  const nodesRef = useRef<GraphNode[]>([])
  const linksRef = useRef<GraphLink[]>([])

  // Build initial graph data.
  const { nodes, links } = useMemo(() => {
    const nodes: GraphNode[] = dicts.map((d) => ({
      id: d.name,
      name: d.name,
      displayName: d.displayName ?? d.name,
      bundle: d.bundle,
      scope: d.scope,
      recordCount: d.recordCount ?? 0,
    }))

    const links: GraphLink[] = []
    dicts.forEach((target) => {
      const deps = dependentsByDict.get(target.name) ?? []
      deps.forEach((dep) => {
        // Edge: source dict → target dict (source has FK к target).
        if (nodes.find((n) => n.id === dep.sourceDict)) {
          links.push({
            source: dep.sourceDict,
            target: target.name,
            sourceField: dep.sourceField,
            targetField: dep.targetField,
          })
        }
      })
    })

    return { nodes, links }
  }, [dicts, dependentsByDict])

  // Responsive sizing.
  useEffect(() => {
    const el = wrapperRef.current
    if (!el) return
    const ro = new ResizeObserver(() => {
      setSize({
        width: el.clientWidth,
        height: Math.max(el.clientHeight, 500),
      })
    })
    ro.observe(el)
    return () => ro.disconnect()
  }, [])

  // Init simulation when nodes/links change.
  useEffect(() => {
    if (nodes.length === 0) return
    nodesRef.current = nodes.map((n) => ({ ...n }))
    linksRef.current = links.map((l) => ({ ...l }))

    const sim = forceSimulation<GraphNode>(nodesRef.current)
      .force(
        'link',
        forceLink<GraphNode, GraphLink>(linksRef.current)
          .id((d) => d.id)
          .distance(90)
          .strength(0.4),
      )
      .force('charge', forceManyBody().strength(-280))
      .force('center', forceCenter(size.width / 2, size.height / 2))
      .force('collide', forceCollide<GraphNode>().radius(28).strength(0.85))
      .alpha(1)
      .alphaDecay(0.025)
      .on('tick', () => setTick((t) => (t + 1) % 1_000_000))

    simulationRef.current = sim
    return () => {
      sim.stop()
    }
  }, [nodes, links, size.width, size.height])

  // Update center force on resize without re-init.
  useEffect(() => {
    const sim = simulationRef.current
    if (!sim) return
    sim.force('center', forceCenter(size.width / 2, size.height / 2))
    sim.alpha(0.3).restart()
  }, [size])

  const connectedIds = useMemo(() => {
    if (!hoveredId) return new Set<string>()
    const out = new Set<string>([hoveredId])
    linksRef.current.forEach((l) => {
      const s = typeof l.source === 'string' ? l.source : (l.source as GraphNode).id
      const t = typeof l.target === 'string' ? l.target : (l.target as GraphNode).id
      if (s === hoveredId) out.add(t)
      if (t === hoveredId) out.add(s)
    })
    return out
  }, [hoveredId])

  return (
    <div ref={wrapperRef} className="relative w-full h-[calc(100vh-220px)] min-h-[500px] rounded-lg border border-line bg-surface overflow-hidden">
      {depsLoading && (
        <div className="text-mono absolute top-3 right-3 z-10 uppercase tracking-wider text-mute bg-surface/80 backdrop-blur px-2 py-1 rounded">
          Загрузка связей…
        </div>
      )}
      <svg
        ref={svgRef}
        width={size.width}
        height={size.height}
        viewBox={`0 0 ${size.width} ${size.height}`}
        className="block touch-none"
      >
        <defs>
          <marker
            id="arrow"
            viewBox="0 0 10 10"
            refX="20"
            refY="5"
            markerWidth="6"
            markerHeight="6"
            orient="auto"
          >
            <path d="M 0 0 L 10 5 L 0 10 z" fill="var(--color-mute)" opacity="0.5" />
          </marker>
          <marker
            id="arrow-hi"
            viewBox="0 0 10 10"
            refX="20"
            refY="5"
            markerWidth="7"
            markerHeight="7"
            orient="auto"
          >
            <path d="M 0 0 L 10 5 L 0 10 z" fill="var(--color-accent)" />
          </marker>
        </defs>

        {/* Edges (под nodes) */}
        {linksRef.current.map((l, i) => {
          const s = l.source as GraphNode
          const t = l.target as GraphNode
          if (!s.x || !s.y || !t.x || !t.y) return null
          const highlighted =
            hoveredId &&
            (s.id === hoveredId || t.id === hoveredId)
          return (
            <line
              key={i}
              x1={s.x}
              y1={s.y}
              x2={t.x}
              y2={t.y}
              stroke={highlighted ? 'var(--color-accent)' : 'var(--color-line)'}
              strokeWidth={highlighted ? 1.5 : 1}
              opacity={hoveredId && !highlighted ? 0.2 : 0.65}
              markerEnd={highlighted ? 'url(#arrow-hi)' : 'url(#arrow)'}
            />
          )
        })}

        {/* Nodes */}
        {nodesRef.current.map((n) => {
          const r = 10 + Math.min(12, Math.log10((n.recordCount ?? 1) + 1) * 4)
          const dim = hoveredId && !connectedIds.has(n.id)
          const focused = hoveredId === n.id
          return (
            <g
              key={n.id}
              transform={`translate(${n.x ?? 0},${n.y ?? 0})`}
              className="cursor-pointer"
              opacity={dim ? 0.3 : 1}
              onMouseEnter={() => setHoveredId(n.id)}
              onMouseLeave={() => setHoveredId(null)}
              onClick={() => navigate({ to: '/dictionaries/$name', params: { name: n.name } })}
            >
              <circle
                r={r}
                fill={colorForBundle(n.bundle)}
                stroke={focused ? 'var(--color-accent)' : 'var(--color-surface)'}
                strokeWidth={focused ? 3 : 2}
              />
              {/* Label под node */}
              <text
                y={r + 12}
                textAnchor="middle"
                fontSize={10}
                fontFamily="ui-sans-serif,system-ui,sans-serif"
                fill="var(--color-ink)"
                className={cn(focused && 'font-semibold')}
              >
                {n.displayName.length > 18 ? n.displayName.slice(0, 17) + '…' : n.displayName}
              </text>
            </g>
          )
        })}
      </svg>
      {/* Legend */}
      <div className="absolute bottom-3 left-3 flex flex-col gap-1 bg-surface/90 backdrop-blur px-3 py-2 rounded-md border border-line text-cell">
        <div className="font-display uppercase tracking-wider text-mute mb-0.5">Bundle</div>
        {Object.entries(BUNDLE_COLORS)
          .filter(([k]) => k !== 'default')
          .map(([bundle, color]) => (
            <div key={bundle} className="flex items-center gap-1.5">
              <span className="size-2 rounded-full" style={{ background: color }} />
              <span className="text-ink-2 font-mono">{bundle}</span>
            </div>
          ))}
      </div>
    </div>
  )
}
