// globe.jsx — real-geography rotating globe (d3-geo orthographic + TopoJSON)
const { useRef, useEffect, useState } = React;

function globeMoney(value) {
  const amount = Number(value);
  return Number.isFinite(amount) ? amount.toFixed(2) : "0.00";
}

function hexToRgb(hex) {
  const h = hex.replace("#", "");
  const n = parseInt(h.length === 3 ? h.split("").map(c => c + c).join("") : h, 16);
  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}

// shared geo cache (loaded once, reused by every globe instance on the page)
const GEO = { ready: false, countries: null, borders: null, land: null, promise: null };
function loadGeo() {
  if (GEO.promise) return GEO.promise;
  GEO.promise = (async () => {
    // wait for libs
    let tries = 0;
    while ((!window.d3 || !window.topojson || !window.d3.geoOrthographic) && tries < 200) {
      await new Promise(r => setTimeout(r, 50)); tries++;
    }
    if (!window.d3 || !window.topojson) throw new Error("geo libs unavailable");
    const topo = await fetch("/assets/countries-110m.json").then(r => r.json());
    const countries = topojson.feature(topo, topo.objects.countries);
    const borders = topojson.mesh(topo, topo.objects.countries, (a, b) => a !== b);
    const land = topojson.merge(topo, topo.objects.countries.geometries);
    Object.assign(GEO, { ready: true, countries, borders, land: { type: "Feature", geometry: land } });
    return GEO;
  })();
  return GEO.promise;
}

// realistic color palettes (independent of accent). null/"accent" → accent-tinted default.
const GLOBE_PALETTES = {
  terra:    { oceanIn: "#2168a8", oceanOut: "#08203f", land: "#3f9e5b", coast: "rgba(190,240,205,0.45)", border: "rgba(255,255,255,0.16)", dot: "#4cc06e", grat: "rgba(255,255,255,0.05)", rim: "rgba(150,200,255,0.45)", lit: true },
  daylight: { oceanIn: "#3aa3e4", oceanOut: "#103f72", land: "#5cbb5b", coast: "rgba(235,255,238,0.5)",  border: "rgba(255,255,255,0.22)", dot: "#74d883", grat: "rgba(255,255,255,0.06)", rim: "rgba(190,225,255,0.55)", lit: true },
  relief:   { oceanIn: "#2a7e8f", oceanOut: "#0b2a31", land: "#728f3c", coast: "rgba(214,224,168,0.45)", border: "rgba(40,55,25,0.35)",   dot: "#8aa84c", grat: "rgba(255,255,255,0.05)", rim: "rgba(150,210,205,0.45)", lit: true },
  emerald:  { oceanIn: "#0f4f6e", oceanOut: "#06222f", land: "#1f9e6a", coast: "rgba(170,245,210,0.5)",  border: "rgba(220,255,240,0.18)", dot: "#2fd089", grat: "rgba(255,255,255,0.05)", rim: "rgba(120,230,200,0.45)", lit: true },
};

function Globe({ plans = [], accent = "#39b6ff", spin = 5, onSelect, dark = true, palette = null }) {
  const canvasRef = useRef(null);
  const wrapRef = useRef(null);
  const markersRef = useRef([]);
  const rotRef = useRef(0);
  const [ready, setReady] = useState(GEO.ready);

  const spinRef = useRef(spin), accentRef = useRef(accent), darkRef = useRef(dark), palRef = useRef(palette);
  useEffect(() => { spinRef.current = spin; }, [spin]);
  useEffect(() => { accentRef.current = accent; }, [accent]);
  useEffect(() => { darkRef.current = dark; }, [dark]);
  useEffect(() => { palRef.current = palette; }, [palette]);

  useEffect(() => { if (!GEO.ready) loadGeo().then(() => setReady(true)).catch(() => {}); else setReady(true); }, []);

  useEffect(() => {
    if (!ready) return;
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    let raf = null, timer = null, rafFired = false, last = performance.now();
    const proj = window.d3 ? d3.geoOrthographic().clipAngle(90).precision(0.4) : null;

    const frame = (now) => {
      const dt = Math.min(60, now - last); last = now;
      rotRef.current += (spinRef.current / 5) * 9 * (dt / 1000); // deg/sec
      const deg = rotRef.current;

      const dpr = Math.min(2, window.devicePixelRatio || 1);
      const W = wrapRef.current.clientWidth, H = wrapRef.current.clientHeight;
      if (canvas.width !== Math.round(W * dpr)) { canvas.width = W * dpr; canvas.height = H * dpr; }
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.clearRect(0, 0, W, H);
      if (!proj) { raf = requestAnimationFrame(rafLoop); return; }

      const cx = W / 2, cy = H / 2, R = Math.min(W, H) * 0.46;
      const [ar, ag, ab] = hexToRgb(accentRef.current);
      const isDark = darkRef.current;
      proj.scale(R).translate([cx, cy]).rotate([deg, -16]);
      const path = d3.geoPath(proj, ctx);
      // geographic center of the visible hemisphere (rotate is [λ, φ] → center is [-λ, -φ])
      const center = [-deg, 16];
      const HALF = Math.PI / 2;
      const onFront = (lng, lat) => d3.geoDistance([lng, lat], center) < HALF;

      // resolve palette (realistic colors) or fall back to accent-tinted defaults
      const acc = (a) => `rgba(${ar},${ag},${ab},${a})`;
      const rawPal = palRef.current;
      const P = typeof rawPal === "string" ? GLOBE_PALETTES[rawPal] : rawPal;
      const pal = P || {
        oceanIn: isDark ? acc(0.10) : acc(0.16), oceanOut: isDark ? "rgba(8,11,20,0.55)" : acc(0.05),
        land: isDark ? acc(0.22) : acc(0.30), coast: acc(isDark ? 0.7 : 0.6), border: acc(isDark ? 0.28 : 0.34),
        dot: acc(isDark ? 0.85 : 0.9), grat: acc(isDark ? 0.05 : 0.07), rim: acc(isDark ? 0.35 : 0.3), lit: false,
      };

      // ocean sphere
      ctx.beginPath(); path({ type: "Sphere" });
      const ocean = ctx.createRadialGradient(cx - R * 0.32, cy - R * 0.36, R * 0.15, cx, cy, R * 1.02);
      ocean.addColorStop(0, pal.oceanIn); ocean.addColorStop(1, pal.oceanOut);
      ctx.fillStyle = ocean; ctx.fill();

      const G = GEO;
      if (G.land) {
        ctx.beginPath(); path(G.land); ctx.fillStyle = pal.land; ctx.fill();
        if (pal.lit) {
          // day-lit sheen + limb shading, clipped to land
          ctx.save(); ctx.beginPath(); path(G.land); ctx.clip();
          const sh = ctx.createRadialGradient(cx - R * 0.4, cy - R * 0.45, R * 0.1, cx, cy, R * 1.15);
          sh.addColorStop(0, "rgba(255,255,255,0.20)"); sh.addColorStop(0.45, "rgba(255,255,255,0)");
          sh.addColorStop(1, "rgba(0,0,0,0.32)");
          ctx.fillStyle = sh; ctx.fillRect(cx - R, cy - R, R * 2, R * 2); ctx.restore();
        }
        ctx.beginPath(); path(G.land); ctx.strokeStyle = pal.coast; ctx.lineWidth = 0.8; ctx.stroke();
      }
      if (G.borders) { ctx.beginPath(); path(G.borders); ctx.strokeStyle = pal.border; ctx.lineWidth = 0.6; ctx.stroke(); }

      // sphere rim
      ctx.beginPath(); path({ type: "Sphere" });
      ctx.strokeStyle = pal.rim; ctx.lineWidth = 1; ctx.stroke();

      // ---- markers (only destinations on the visible front hemisphere) ----
      const vis = [];
      for (let i = 0; i < plans.length; i++) {
        const node = markersRef.current[i];
        const front = onFront(plans[i].lng, plans[i].lat);
        const pt = front ? proj([plans[i].lng, plans[i].lat]) : null;
        if (pt) {
          const dx = pt[0] - cx, dy = pt[1] - cy;
          vis.push({ i, pt, d: Math.sqrt(dx * dx + dy * dy) });
        } else if (node) {
          node.style.opacity = "0";
          node.style.pointerEvents = "none";
          if (node.children[1]) node.children[1].style.opacity = "0";
        }
      }
      const cardSet = new Set([...vis].sort((a, b) => a.d - b.d).slice(0, 4).map(v => v.i));
      for (const { i, pt, d } of vis) {
        const node = markersRef.current[i]; if (!node) continue;
        const pop = node.children[1];
        const edge = Math.min(1, Math.max(0, (R - d) / (R * 0.5)));
        node.style.opacity = String(0.25 + edge * 0.75);
        node.style.zIndex = String(100 + Math.round((R - d)));
        node.style.transform = `translate(-50%,-100%) translate(${pt[0]}px, ${pt[1]}px)`;
        const show = cardSet.has(i);
        node.style.pointerEvents = show ? "auto" : "none";
        if (pop) { pop.style.opacity = show ? "1" : "0"; pop.style.transform = show ? "translateY(0) scale(1)" : "translateY(6px) scale(0.92)"; }
      }
    };

    const rafLoop = (now) => { rafFired = true; frame(now); raf = requestAnimationFrame(rafLoop); };
    raf = requestAnimationFrame(rafLoop);
    timer = setInterval(() => { if (!rafFired) frame(performance.now()); rafFired = false; }, 1000 / 30);
    return () => { cancelAnimationFrame(raf); clearInterval(timer); };
  }, [plans, ready]);

  return (
    <div ref={wrapRef} className="globe-wrap">
      <div className="globe-glow"></div>
      <canvas ref={canvasRef} className="globe-canvas" />
      <div className="globe-markers">
        {plans.map((p, i) => (
          <div key={p.id} ref={el => (markersRef.current[i] = el)} className="gmarker" onClick={() => onSelect && onSelect(p)}>
            <div className="gmarker-dot"><span></span></div>
            <div className="gpop">
              <div className="gpop-flag">{p.flag}</div>
              <div className="gpop-body">
                <div className="gpop-country">{p.country}</div>
                <div className="gpop-meta">{p.data} · {p.days}d</div>
              </div>
              <div className="gpop-price">${globeMoney(p.price)}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

window.Globe = Globe;
window.GLOBE_PALETTES = GLOBE_PALETTES;
