// Rotating wireframe globe with projected country outlines + location dots.
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// 3D rotation helpers
function rotateY(p, a) {
  const c = Math.cos(a), s = Math.sin(a);
  return { x: p.x * c + p.z * s, y: p.y, z: -p.x * s + p.z * c };
}
function rotateX(p, a) {
  const c = Math.cos(a), s = Math.sin(a);
  return { x: p.x, y: p.y * c - p.z * s, z: p.y * s + p.z * c };
}
function latLngTo3D(lat, lng) {
  const phi = (90 - lat) * Math.PI / 180;
  const theta = (lng + 180) * Math.PI / 180;
  return {
    x: -Math.sin(phi) * Math.cos(theta),
    y:  Math.cos(phi),
    z:  Math.sin(phi) * Math.sin(theta),
  };
}

// Ocean labels (rough centroids)
const OCEANS = [
  { name: 'PACIFIC OCEAN',  lat: 0,   lng: -150 },
  { name: 'PACIFIC OCEAN',  lat: -20, lng: 160 },
  { name: 'ATLANTIC OCEAN', lat: 15,  lng: -40 },
  { name: 'ATLANTIC OCEAN', lat: -25, lng: -20 },
  { name: 'INDIAN OCEAN',   lat: -20, lng: 80 },
  { name: 'ARCTIC OCEAN',   lat: 80,  lng: 0 },
  { name: 'SOUTHERN OCEAN', lat: -65, lng: 0 },
];

// Reference cities/regions for orientation
const REF_LABELS = [
  { name: 'N. AMERICA',  lat: 45,  lng: -100, major: true },
  { name: 'S. AMERICA',  lat: -15, lng: -60,  major: true },
  { name: 'EUROPE',      lat: 50,  lng: 15,   major: true },
  { name: 'AFRICA',      lat: 5,   lng: 20,   major: true },
  { name: 'ASIA',        lat: 45,  lng: 95,   major: true },
  { name: 'AUSTRALIA',   lat: -25, lng: 133,  major: true },
  { name: 'London',      lat: 51.5, lng: -0.1 },
  { name: 'New York',    lat: 40.7, lng: -74  },
  { name: 'Los Angeles', lat: 34.0, lng: -118.2 },
  { name: 'Moscow',      lat: 55.7, lng: 37.6 },
  { name: 'Beijing',     lat: 39.9, lng: 116.4 },
  { name: 'Sydney',      lat: -33.8, lng: 151.2 },
  { name: 'Rio',         lat: -22.9, lng: -43.2 },
  { name: 'Cairo',       lat: 30.0, lng: 31.2 },
  { name: 'Delhi',       lat: 28.6, lng: 77.2 },
  { name: 'Johannesburg',lat: -26.2, lng: 28.0 },
];

function Globe({
  locations, size = 560, selectedId, favoritedIds,
  onSelect, onHover, hoveredId,
  filterResultIds,
  countryPolygons,
}) {
  const [rotY, setRotY] = useState(-0.4);
  const [rotX, setRotX] = useState(-0.25);
  const [zoom, setZoom] = useState(1);
  const [dragging, setDragging] = useState(false);
  const autoSpin = useRef(true);
  const rafRef = useRef(null);

  // Physics state held in refs so RAF doesn't stutter from re-renders
  const state = useRef({
    rotX: -0.25, rotY: -0.4, zoom: 1,
    targetZoom: 1,
    velX: 0, velY: 0,
    dragging: false, lastX: 0, lastY: 0, lastT: 0,
  });

  // Unified RAF loop: applies velocity, zoom lerp, auto-spin
  useEffect(() => {
    let last = performance.now();
    const tick = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000);
      last = now;
      const s = state.current;

      // zoom lerp
      s.zoom += (s.targetZoom - s.zoom) * Math.min(1, dt * 12);

      if (s.dragging) {
        // while dragging, velocity is set by mousemove; no inertia applied
      } else {
        // apply velocity
        s.rotY += s.velY * dt;
        s.rotX += s.velX * dt;
        // damping
        const damp = Math.pow(0.0025, dt); // ~99.75% per frame at 60fps feel
        s.velY *= damp;
        s.velX *= damp;
        // auto-spin when truly idle
        if (autoSpin.current && Math.abs(s.velY) < 0.02 && Math.abs(s.velX) < 0.02) {
          s.rotY += dt * 0.03;
        }
        // cutoff tiny velocities
        if (Math.abs(s.velX) < 0.001) s.velX = 0;
        if (Math.abs(s.velY) < 0.001) s.velY = 0;
      }
      // clamp rotX
      if (s.rotX > 1.3) { s.rotX = 1.3; s.velX = 0; }
      if (s.rotX < -1.3) { s.rotX = -1.3; s.velX = 0; }

      setRotX(s.rotX); setRotY(s.rotY); setZoom(s.zoom);

      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, []);

  const onPointerDown = (e) => {
    const s = state.current;
    s.dragging = true;
    s.moved = false;
    s.downX = e.clientX; s.downY = e.clientY;
    s.lastX = e.clientX; s.lastY = e.clientY; s.lastT = performance.now();
    s.velX = 0; s.velY = 0;
    autoSpin.current = false;
    setDragging(true);
  };
  const onPointerMove = (e) => {
    const s = state.current;
    if (!s.dragging) return;
    const now = performance.now();
    const dt = Math.max(0.001, (now - s.lastT) / 1000);
    const dx = e.clientX - s.lastX;
    const dy = e.clientY - s.lastY;
    if (Math.abs(e.clientX - s.downX) + Math.abs(e.clientY - s.downY) > 4) {
      if (!s.moved) e.currentTarget.setPointerCapture?.(e.pointerId);
      s.moved = true;
    }
    if (!s.moved) return;
    const sens = 0.006 / s.zoom;
    s.rotY += dx * sens;
    s.rotX += dy * sens;
    s.velY = (dx * sens) / dt;
    s.velX = (dy * sens) / dt;
    s.lastX = e.clientX; s.lastY = e.clientY; s.lastT = now;
    setRotX(s.rotX); setRotY(s.rotY);
  };
  const onPointerUp = (e) => {
    const s = state.current;
    s.dragging = false;
    const maxV = 4;
    s.velX = Math.max(-maxV, Math.min(maxV, s.velX));
    s.velY = Math.max(-maxV, Math.min(maxV, s.velY));
    setDragging(false);
    try { e.currentTarget.releasePointerCapture?.(e.pointerId); } catch {}
  };

  const onWheel = (e) => {
    e.preventDefault();
    const s = state.current;
    const factor = Math.exp(-e.deltaY * 0.0018);
    s.targetZoom = Math.max(0.6, Math.min(3.2, s.targetZoom * factor));
    autoSpin.current = false;
  };

  // attach wheel with non-passive listener
  const wrapRef = useRef(null);
  useEffect(() => {
    const el = wrapRef.current;
    if (!el) return;
    const handler = (e) => onWheel(e);
    el.addEventListener('wheel', handler, { passive: false });
    return () => el.removeEventListener('wheel', handler);
  }, []);

  // Spin-to-selected
  useEffect(() => {
    if (!selectedId) return;
    const loc = locations.find(l => l.id === selectedId);
    if (!loc) return;
    autoSpin.current = false;
    const s = state.current;
    const targetY = Math.PI / 2 - (loc.lng + 180) * Math.PI / 180;
    const targetX = loc.lat * Math.PI / 180;
    const normY = ((targetY - s.rotY) % (2 * Math.PI) + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
    const endY = s.rotY + normY;
    const startY = s.rotY, startX = s.rotX;
    const t0 = performance.now();
    const dur = 700;
    let raf;
    const animate = (now) => {
      const t = Math.min(1, (now - t0) / dur);
      const e = t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
      s.rotY = startY + (endY - startY) * e;
      s.rotX = startX + (targetX - startX) * e;
      s.velX = 0; s.velY = 0;
      if (t < 1) raf = requestAnimationFrame(animate);
    };
    raf = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(raf);
  }, [selectedId]);

  const R = size * 0.42 * zoom;
  const cx = size / 2;
  const cy = size / 2;

  const project = (lat, lng) => {
    const p0 = latLngTo3D(lat, lng);
    const p1 = rotateY(p0, rotY);
    const p2 = rotateX(p1, rotX);
    return { sx: cx + p2.x * R, sy: cy - p2.y * R, z: p2.z };
  };

  // Grid
  const gridPaths = useMemo(() => {
    const paths = [];
    for (let lng = -180; lng < 180; lng += 30) {
      const pts = [];
      for (let lat = -90; lat <= 90; lat += 6) pts.push(project(lat, lng));
      paths.push({ pts, type: 'meridian' });
    }
    for (let lat = -60; lat <= 60; lat += 30) {
      const pts = [];
      for (let lng = -180; lng <= 180; lng += 6) pts.push(project(lat, lng));
      paths.push({ pts, type: 'parallel', lat });
    }
    return paths;
  }, [rotX, rotY, zoom]);

  // Country polygons — project each ring, break into visible segments
  const countryPaths = useMemo(() => {
    if (!countryPolygons) return [];
    const out = [];
    for (const ring of countryPolygons) {
      // project all points
      const proj = ring.map(p => project(p[1], p[0])); // ring is [lng,lat]
      // build path — break on back-of-sphere
      let d = '';
      let penUp = true;
      for (let i = 0; i < proj.length; i++) {
        const p = proj[i];
        if (p.z < -0.02) { penUp = true; continue; }
        d += (penUp ? 'M' : 'L') + p.sx.toFixed(1) + ' ' + p.sy.toFixed(1) + ' ';
        penUp = false;
      }
      if (d) out.push(d);
    }
    return out;
  }, [countryPolygons, rotX, rotY, zoom]);

  const dots = useMemo(() => {
    return locations.map(loc => {
      const p = project(loc.lat, loc.lng);
      return { loc, ...p, visible: p.z > -0.05, inFilter: filterResultIds.has(loc.id) };
    }).sort((a, b) => a.z - b.z);
  }, [locations, rotX, rotY, zoom, filterResultIds]);

  const pathFromPts = (pts) => {
    let d = '', penUp = true;
    for (const p of pts) {
      if (p.z < -0.05) { penUp = true; continue; }
      d += (penUp ? 'M' : 'L') + p.sx.toFixed(1) + ' ' + p.sy.toFixed(1) + ' ';
      penUp = false;
    }
    return d;
  };

  // reference labels projected
  const refs = useMemo(() =>
    REF_LABELS.map(r => ({ ...r, ...project(r.lat, r.lng) }))
              .filter(r => r.z > 0.15),
  [rotX, rotY, zoom]);
  const oceans = useMemo(() =>
    OCEANS.map(r => ({ ...r, ...project(r.lat, r.lng) }))
          .filter(r => r.z > 0.25),
  [rotX, rotY, zoom]);

  return (
    <div className="globe-wrap"
      ref={wrapRef}
      style={{ width: size, height: size, userSelect: 'none', cursor: dragging ? 'grabbing' : 'grab', touchAction: 'none' }}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerCancel={onPointerUp}
      onMouseLeave={() => onHover(null)}>
      <svg width={size} height={size} style={{ display: 'block', overflow: 'visible' }}>
        <defs>
          <radialGradient id="globeBG" cx="40%" cy="38%">
            <stop offset="0%" stopColor="#16243a" stopOpacity="1" />
            <stop offset="55%" stopColor="#0e1a2b" stopOpacity="1" />
            <stop offset="100%" stopColor="#08101c" stopOpacity="1" />
          </radialGradient>
          <radialGradient id="globeShine" cx="35%" cy="30%">
            <stop offset="0%" stopColor="#fff" stopOpacity="0.06" />
            <stop offset="60%" stopColor="#fff" stopOpacity="0" />
          </radialGradient>
          <clipPath id="globeClip">
            <circle cx={cx} cy={cy} r={R}/>
          </clipPath>
          <filter id="dotGlow" x="-50%" y="-50%" width="200%" height="200%">
            <feGaussianBlur stdDeviation="3" result="b"/>
            <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
          </filter>
        </defs>

        <circle cx={cx} cy={cy} r={R + 12} fill="none" stroke="var(--rule)" strokeWidth="1" strokeDasharray="1 4" opacity="0.5"/>
        <circle cx={cx} cy={cy} r={R} fill="url(#globeBG)" />

        <g clipPath="url(#globeClip)">
          {/* grid */}
          {gridPaths.map((g, i) => (
            <path key={i} d={pathFromPts(g.pts)}
              fill="none"
              stroke={g.type === 'parallel' && g.lat === 0 ? 'rgba(212, 166, 108, 0.3)' : 'rgba(180, 200, 230, 0.06)'}
              strokeWidth={g.type === 'parallel' && g.lat === 0 ? 0.7 : 0.4} />
          ))}
          {/* country outlines */}
          {countryPaths.map((d, i) => (
            <path key={i} d={d} fill="rgba(90, 120, 150, 0.13)" stroke="rgba(180, 210, 235, 0.38)" strokeWidth="0.55" strokeLinejoin="round"/>
          ))}
        </g>

        <circle cx={cx} cy={cy} r={R} fill="url(#globeShine)" pointerEvents="none"/>

        {/* ocean labels */}
        {oceans.map((o, i) => (
          <text key={i} x={o.sx} y={o.sy} textAnchor="middle"
            fontFamily="'IBM Plex Mono', monospace"
            fontSize="8.5" fill="rgba(160, 190, 220, 0.45)"
            letterSpacing="3" fontStyle="italic" pointerEvents="none">
            {o.name}
          </text>
        ))}

        {/* reference region/city labels */}
        {refs.map((r, i) => (
          <g key={i} pointerEvents="none">
            {!r.major && <circle cx={r.sx} cy={r.sy} r="1.2" fill="rgba(220,220,220,0.55)"/>}
            <text x={r.sx + (r.major ? 0 : 4)} y={r.sy + (r.major ? 0 : 3)}
              textAnchor={r.major ? 'middle' : 'start'}
              fontFamily="'IBM Plex Mono', monospace"
              fontSize={r.major ? 9 : 8}
              fill={r.major ? 'rgba(220,220,230,0.55)' : 'rgba(210,220,230,0.5)'}
              letterSpacing={r.major ? 3 : 0.8}
              fontWeight={r.major ? 500 : 400}>
              {r.major ? r.name : r.name}
            </text>
          </g>
        ))}

        {/* hub dots */}
        {dots.map(({ loc, sx, sy, visible, inFilter }) => {
          if (!visible) return null;
          const isSelected = selectedId === loc.id;
          const isHovered = hoveredId === loc.id;
          const isFav = favoritedIds.has(loc.id);
          const baseR = 2 + loc.weight * 1.4;
          const r = isSelected ? baseR + 3 : isHovered ? baseR + 2 : baseR;
          const color = inFilter ? (isFav ? '#e5c07b' : (loc.userAdded ? '#7fb6d9' : '#d4a66c')) : 'rgba(220,210,195,0.35)';
          return (
            <g key={loc.id}
              onMouseEnter={() => onHover(loc.id)}
              onMouseDown={(e) => e.stopPropagation()}
              onClick={(e) => { e.stopPropagation(); onSelect(loc.id); }}
              style={{ cursor: 'pointer' }}>
              {inFilter && (
                <circle cx={sx} cy={sy} r={r + 4} fill={color} opacity="0.22" filter="url(#dotGlow)"/>
              )}
              {loc.userAdded && (
                <rect x={sx - r - 1.5} y={sy - r - 1.5} width={r*2 + 3} height={r*2 + 3}
                  fill="none" stroke="#7fb6d9" strokeWidth="0.8" strokeDasharray="2 1" opacity="0.9"/>
              )}
              <circle cx={sx} cy={sy} r={r} fill={color}
                stroke={isSelected ? '#fff' : isFav ? '#e5c07b' : 'rgba(0,0,0,0.5)'}
                strokeWidth={isSelected ? 1.5 : isFav ? 1 : 0.5}/>
              {isFav && !isSelected && (
                <circle cx={sx} cy={sy} r={r + 2.5} fill="none" stroke="#e5c07b" strokeWidth="0.6" opacity="0.6"/>
              )}
            </g>
          );
        })}

        {/* labels */}
        {dots.map(({ loc, sx, sy, visible, inFilter }) => {
          if (!visible) return null;
          const show = selectedId === loc.id || hoveredId === loc.id || (favoritedIds.has(loc.id) && inFilter);
          if (!show) return null;
          const r = 2 + loc.weight * 1.4;
          return (
            <g key={loc.id + '-lbl'} pointerEvents="none">
              <line x1={sx + r + 2} y1={sy} x2={sx + r + 16} y2={sy - 10}
                stroke="rgba(229, 192, 123, 0.7)" strokeWidth="0.8"/>
              <rect x={sx + r + 16} y={sy - 20} width={loc.name.length * 6 + 14} height="24"
                fill="rgba(10, 15, 25, 0.88)" stroke="rgba(229,192,123,0.5)" strokeWidth="0.6"/>
              <text x={sx + r + 22} y={sy - 9}
                fontFamily="'IBM Plex Mono', monospace" fontSize="10"
                fill="#f1ebe0" letterSpacing="0.5">
                {loc.name.toUpperCase()}
              </text>
              <text x={sx + r + 22} y={sy + 1}
                fontFamily="'IBM Plex Mono', monospace" fontSize="8.5"
                fill="rgba(220,210,195,0.7)" letterSpacing="0.5">
                {loc.country.toUpperCase()}
              </text>
            </g>
          );
        })}
      </svg>
    </div>
  );
}

window.Globe = Globe;
