import { useEffect, useId as useReactId, useRef, useState } from 'react'
import { geoOrthographic, geoPath, geoGraticule10 } from 'd3-geo'
import type { Feature, FeatureCollection, Geometry } from 'geojson'
import { cn } from '@/lib/utils'

/**
 * GlobeLoader — orbital globe spinner per /Users/zimin/Downloads/Globe loader
 * prototype. Real Natural Earth land outline через d3-geo orthographic
 * projection, rotating longitude continuously. Outer whirl arcs + pulsing
 * ring CSS-only.
 *
 * <p>Lazy-loads world-atlas (~55kb) — пока геометрия не доехала, видны
 * только sphere + graticule + orbital arcs (still looks like a loader).
 *
 * <p>Использование:
 *   <GlobeLoader />                          // 64px default
 *   <GlobeLoader size={120} />               // larger
 *   <GlobeLoader className="text-mute" />    // tint via currentColor
 */

export type GlobeLoaderProps = {
  /** Pixel size of the spinner square (default 64). */
  size?: number
  /** Optional label rendered below. */
  label?: React.ReactNode
  /** Extra wrapper class. */
  className?: string
}

// Cache загруженной land geometry между mount'ами loader'а (типично загрузка
// списка → переход в editor → ещё loader). Без cache каждый GlobeLoader
// триггерил бы import заново — Vite кеширует chunk, но parse cost есть.
let LAND_CACHE: Feature<Geometry> | null = null
let LAND_PROMISE: Promise<Feature<Geometry>> | null = null

async function loadLand(): Promise<Feature<Geometry>> {
  if (LAND_CACHE) return LAND_CACHE
  if (LAND_PROMISE) return LAND_PROMISE
  LAND_PROMISE = (async () => {
    const [{ feature }, world] = await Promise.all([
      import('topojson-client'),
      import('world-atlas/land-110m.json'),
    ])
    const data = (world as { default: unknown }).default ?? world
    // topojson-client expects Topology с objects.land mesh.
    const f = feature(
      data as Parameters<typeof feature>[0],
      (data as { objects: { land: Parameters<typeof feature>[1] } }).objects.land,
    ) as Feature<Geometry> | FeatureCollection<Geometry>
    const result = 'features' in f ? (f.features[0] as Feature<Geometry>) : f
    LAND_CACHE = result
    return result
  })()
  return LAND_PROMISE
}

export function GlobeLoader({ size = 64, label, className }: GlobeLoaderProps) {
  const id = useReactId().replace(/:/g, '')
  const tailA = `globe-tail-a-${id}`
  const tailB = `globe-tail-b-${id}`

  const sphereRef = useRef<SVGPathElement>(null)
  const gratRef = useRef<SVGPathElement>(null)
  const landRef = useRef<SVGPathElement>(null)
  const [landReady, setLandReady] = useState(false)

  useEffect(() => {
    // Setup projection: orthographic, R=70 (как в прототипе), tilt -15° (легкий
    // northern lean). viewBox 200x200 — fixed; scale projection относительно него.
    const projection = geoOrthographic()
      .scale(70)
      .translate([100, 100])
      .clipAngle(90)
      .rotate([0, -15, 0])

    const path = geoPath(projection)
    const graticule = geoGraticule10()

    // Initial render (без land — оно lazy).
    if (sphereRef.current) sphereRef.current.setAttribute('d', path({ type: 'Sphere' }) ?? '')
    if (gratRef.current) gratRef.current.setAttribute('d', path(graticule) ?? '')

    let landFeature: Feature<Geometry> | null = LAND_CACHE
    let cancelled = false
    if (!landFeature) {
      void loadLand().then((f) => {
        if (cancelled) return
        landFeature = f
        if (landRef.current) landRef.current.setAttribute('d', path(f) ?? '')
        setLandReady(true)
      })
    } else {
      if (landRef.current) landRef.current.setAttribute('d', path(landFeature) ?? '')
      setLandReady(true)
    }

    // Animate rotation — 8s per revolution per prototype.
    const start = performance.now()
    const period = 8000
    let raf = 0

    const frame = (now: number) => {
      if (cancelled) return
      const t = ((now - start) % period) / period
      const lambda = t * 360
      projection.rotate([lambda, -15, 0])
      if (gratRef.current) gratRef.current.setAttribute('d', path(graticule) ?? '')
      if (landFeature && landRef.current) {
        landRef.current.setAttribute('d', path(landFeature) ?? '')
      }
      raf = requestAnimationFrame(frame)
    }
    raf = requestAnimationFrame(frame)

    return () => {
      cancelled = true
      cancelAnimationFrame(raf)
    }
  }, [])

  return (
    <div
      role="status"
      aria-busy="true"
      aria-label="loading"
      className={cn('inline-flex flex-col items-center justify-center gap-2 text-ink', className)}
    >
      <div className="globe-loader relative" style={{ width: size, height: size }}>
        {/* Outer overlay svg: whirl arcs + pulse ring (CSS-animated). */}
        <svg viewBox="0 0 200 200" className="absolute inset-0 size-full overflow-visible">
          <defs>
            <linearGradient id={tailA} gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="200" y2="0">
              <stop offset="0%" stopColor="currentColor" stopOpacity="0" />
              <stop offset="60%" stopColor="currentColor" stopOpacity="0.18" />
              <stop offset="100%" stopColor="currentColor" stopOpacity="0.95" />
            </linearGradient>
            <linearGradient id={tailB} gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="200" y2="0">
              <stop offset="0%" stopColor="currentColor" stopOpacity="0" />
              <stop offset="70%" stopColor="currentColor" stopOpacity="0.12" />
              <stop offset="100%" stopColor="currentColor" stopOpacity="0.55" />
            </linearGradient>
          </defs>

          {/* Pulsing outermost dashed ring */}
          <g className="globe-pulse">
            <circle
              cx="100"
              cy="100"
              r="96"
              fill="none"
              stroke="currentColor"
              strokeWidth="0.6"
              strokeDasharray="1 4"
            />
          </g>

          {/* Outer whirl: 270° tail arc + leading dot + ghost arc */}
          <g className="globe-whirl">
            <path
              d="M 100 4 A 96 96 0 1 1 4 100"
              fill="none"
              stroke={`url(#${tailA})`}
              strokeWidth="2"
              strokeLinecap="round"
            />
            <circle cx="100" cy="4" r="2.4" fill="currentColor" />
            <path
              d="M 100 12 A 88 88 0 0 1 188 100"
              fill="none"
              stroke={`url(#${tailB})`}
              strokeWidth="1.2"
              strokeLinecap="round"
              opacity="0.7"
            />
          </g>

          {/* Inner whirl, counter-rotating */}
          <g className="globe-whirl-rev">
            <path
              d="M 100 18 A 82 82 0 1 1 18 100"
              fill="none"
              stroke={`url(#${tailA})`}
              strokeWidth="1.1"
              strokeLinecap="round"
              opacity="0.55"
            />
            <circle cx="100" cy="18" r="1.6" fill="currentColor" opacity="0.85" />
          </g>
        </svg>

        {/* Globe svg: real orthographic projection с rotating land. */}
        <svg viewBox="0 0 200 200" className="absolute inset-0 size-full overflow-visible">
          {/* Sphere — disk silhouette (fill = bg чтобы land не светилось через
            * прозрачное место). */}
          <path
            ref={sphereRef}
            fill="var(--color-bg, #f4f1ec)"
            stroke="currentColor"
            strokeWidth="1.25"
          />
          {/* Graticule — subtle lat/lon grid. */}
          <path
            ref={gratRef}
            fill="none"
            stroke="currentColor"
            strokeWidth="0.4"
            strokeOpacity="0.18"
          />
          {/* Land — Natural Earth outline, rotated continuously. fill-opacity
            * 0.88 чтобы slightly soften. */}
          <path
            ref={landRef}
            fill="currentColor"
            fillOpacity={landReady ? 0.88 : 0}
            stroke="var(--color-bg, #f4f1ec)"
            strokeWidth="0.35"
            strokeLinejoin="round"
            style={{ transition: 'fill-opacity 0.3s ease' }}
          />
        </svg>
      </div>
      {label && <span className="text-body text-mute">{label}</span>}
    </div>
  )
}
