/* ============================================================
   CountryGlobe — a zoomed orthographic globe that highlights one
   country. Loads d3-geo + topojson + world-atlas from CDN on
   demand, draws to canvas, drags to spin, gently idles otherwise.
   Falls back to a simple marked card if the libraries can't load.
   ============================================================ */

(function () {
  const ATLAS_URL = "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json";
  const LIBS = [
    "https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js",
    "https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js",
  ];

  function loadScript(src) {
    return new Promise((resolve, reject) => {
      if ([...document.scripts].some((s) => s.src === src)) return resolve();
      const el = document.createElement("script");
      el.src = src;
      el.crossOrigin = "anonymous";
      el.onload = () => resolve();
      el.onerror = () => reject(new Error("load failed: " + src));
      document.head.appendChild(el);
    });
  }

  let atlasPromise = null;
  function loadGlobeData() {
    if (atlasPromise) return atlasPromise;
    atlasPromise = (async () => {
      await loadScript(LIBS[0]);
      await loadScript(LIBS[1]);
      const res = await fetch(ATLAS_URL);
      if (!res.ok) throw new Error("atlas fetch failed");
      const topo = await res.json();
      const feats = window.topojson.feature(topo, topo.objects.countries).features;
      return feats;
    })();
    return atlasPromise;
  }

  function CountryGlobe({ country, zoom: zoomProp }) {
    const wrapRef = React.useRef(null);
    const canvasRef = React.useRef(null);
    const rafRef = React.useRef(null);
    const [status, setStatus] = React.useState("loading"); // loading | ready | error

    React.useEffect(() => {
      let cancelled = false;
      const wrap = wrapRef.current;
      const canvas = canvasRef.current;
      if (!wrap || !canvas) return;
      const ctx = canvas.getContext("2d");

      const hue = country.hue ?? 200;
      const target = [-country.coord[0], -country.coord[1]];
      const zoom = zoomProp ?? country.zoom ?? 2.6;

      // national-colour palette (falls back to hue-derived oklch)
      const C = country.colors || {};
      const colA = C.sphere1 || `oklch(0.30 0.05 ${hue})`;
      const colB = C.sphere2 || `oklch(0.17 0.04 ${hue})`;
      const colHi = C.hi || `oklch(0.72 0.17 ${hue})`;
      const colEdge = C.edge || `oklch(0.88 0.12 ${hue})`;
      const colMarker = C.marker || "#ffffff";
      const zoomFrom = 0.62; // start small \u2014 zooms in to settle

      // animation state
      const startRot = [target[0] - 42, target[1] - 8, 0];
      let rot = startRot.slice();
      let intro = 0;            // 0..1 intro progress
      let idle = 0;             // idle drift phase
      let dragging = false;
      let last = null;
      let vx = 0;               // drag inertia
      let W = 0, H = 0, R = 0, cx = 0, cy = 0;
      let projection, path, sphere, graticule, land, hi, prefersReduced = false;

      try {
        prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      } catch (e) {}

      const resize = () => {
        const dpr = Math.min(window.devicePixelRatio || 1, 2);
        const r = wrap.getBoundingClientRect();
        W = Math.max(1, Math.floor(r.width));
        H = Math.max(1, Math.floor(r.height));
        canvas.width = W * dpr;
        canvas.height = H * dpr;
        canvas.style.width = W + "px";
        canvas.style.height = H + "px";
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        cx = W / 2;
        cy = H / 2;
        if (projection) projection.translate([cx, cy]);
      };

      const draw = () => {
        if (cancelled) return;
       try {
        // ease intro
        if (intro < 1 && !prefersReduced) {
          intro = Math.min(1, intro + 0.011);
        } else { intro = 1; }
        const e = 1 - Math.pow(1 - intro, 3);

        // animated zoom: full globe → country
        const zoomNow = zoomFrom + (zoom - zoomFrom) * e;
        R = (Math.min(W, H) / 2) * zoomNow;
        projection.scale(R).translate([cx, cy]);

        if (!dragging) {
          // settle toward target + gentle idle drift
          idle += 0.0016;
          const driftX = prefersReduced ? 0 : Math.sin(idle) * 7;
          const driftY = prefersReduced ? 0 : Math.cos(idle * 0.8) * 2.5;
          const tx = target[0] + driftX;
          const ty = target[1] + driftY;
          rot[0] = startRot[0] + (tx - startRot[0]) * e + (intro >= 1 ? 0 : 0);
          rot[1] = startRot[1] + (ty - startRot[1]) * e;
          if (intro >= 1) {
            rot[0] += vx;
            vx *= 0.92;
            // re-center slowly after inertia
            rot[0] = tx + (rot[0] - tx) * 0.985;
          }
        }
        projection.rotate([rot[0], rot[1], 0]);

        ctx.clearRect(0, 0, W, H);

        // ocean sphere
        ctx.beginPath();
        path(sphere);
        const oc = ctx.createRadialGradient(cx - R * 0.3, cy - R * 0.3, R * 0.2, cx, cy, R);
        oc.addColorStop(0, colA);
        oc.addColorStop(1, colB);
        ctx.fillStyle = oc;
        ctx.fill();

        // graticule
        ctx.beginPath();
        path(graticule);
        ctx.strokeStyle = "rgba(255,255,255,0.06)";
        ctx.lineWidth = 0.6;
        ctx.stroke();

        // land (all countries, muted)
        ctx.beginPath();
        for (const f of land) { if (f !== hi) path(f); }
        ctx.fillStyle = "rgba(255,255,255,0.13)";
        ctx.fill();
        ctx.strokeStyle = "rgba(255,255,255,0.10)";
        ctx.lineWidth = 0.5;
        ctx.stroke();

        // highlight country
        if (hi) {
          ctx.save();
          ctx.beginPath();
          path(hi);
          ctx.globalAlpha = 0.95;
          ctx.fillStyle = colHi;
          ctx.fill();
          ctx.globalAlpha = 1;
          ctx.strokeStyle = colEdge;
          ctx.lineWidth = 1.2;
          ctx.stroke();
          ctx.restore();
          // soft glow
          ctx.save();
          ctx.beginPath();
          path(hi);
          ctx.shadowColor = colHi;
          ctx.shadowBlur = 28;
          ctx.globalAlpha = 0.6;
          ctx.fillStyle = colHi;
          ctx.fill();
          ctx.restore();
        }

        // capital marker (only if on the front hemisphere)
        const c = projection.rotate([rot[0], rot[1], 0]);
        const pt = projection(country.coord);
        // visibility test via geoDistance against rotation center
        const center = [-rot[0], -rot[1]];
        const dist = window.d3.geoDistance(country.coord, center);
        if (pt && dist < Math.PI / 2) {
          const pulse = prefersReduced ? 0.6 : 0.5 + 0.5 * Math.abs(Math.sin(idle * 3));
          ctx.save();
          ctx.globalAlpha = 0.25 + pulse * 0.2;
          ctx.beginPath();
          ctx.arc(pt[0], pt[1], 4 + pulse * 3, 0, Math.PI * 2);
          ctx.fillStyle = colMarker;
          ctx.fill();
          ctx.restore();
          ctx.beginPath();
          ctx.arc(pt[0], pt[1], 3.2, 0, Math.PI * 2);
          ctx.fillStyle = colMarker;
          ctx.fill();
        }

        // rim light
        ctx.save();
        ctx.globalAlpha = 0.25;
        ctx.beginPath();
        ctx.arc(cx, cy, R, 0, Math.PI * 2);
        ctx.strokeStyle = colEdge;
        ctx.lineWidth = 1;
        ctx.stroke();
        ctx.restore();

        } catch (err) { /* skip a bad frame */ }
        rafRef.current = requestAnimationFrame(draw);
      };

      // pointer drag
      const onDown = (e) => {
        dragging = true;
        last = e.clientX;
        canvas.setPointerCapture?.(e.pointerId);
        canvas.style.cursor = "grabbing";
      };
      const onMove = (e) => {
        if (!dragging || last == null) return;
        const dx = e.clientX - last;
        last = e.clientX;
        rot[0] += dx * 0.4;
        vx = dx * 0.05;
      };
      const onUp = (e) => {
        dragging = false;
        last = null;
        try { canvas.releasePointerCapture?.(e.pointerId); } catch (err) {}
        canvas.style.cursor = "grab";
      };

      loadGlobeData()
        .then((feats) => {
          if (cancelled) return;
          land = feats;
          hi = feats.find((f) => String(f.id) === String(country.id)) || null;
          projection = window.d3.geoOrthographic().clipAngle(90).precision(0.4);
          path = window.d3.geoPath(projection, ctx);
          sphere = { type: "Sphere" };
          graticule = window.d3.geoGraticule10();
          setStatus("ready");
          resize();
          canvas.style.cursor = "grab";
          canvas.style.touchAction = "pan-y";
          canvas.addEventListener("pointerdown", onDown);
          canvas.addEventListener("pointermove", onMove);
          canvas.addEventListener("pointerup", onUp);
          canvas.addEventListener("pointercancel", onUp);
          window.addEventListener("resize", resize);
          // Paint at least one full frame synchronously so the globe is never
          // blank — even when rAF is throttled (hidden tab) or motion is reduced.
          if (document.hidden || prefersReduced) intro = 1;
          draw();
        })
        .catch(() => { if (!cancelled) setStatus("error"); });

      return () => {
        cancelled = true;
        cancelAnimationFrame(rafRef.current);
        window.removeEventListener("resize", resize);
        canvas.removeEventListener("pointerdown", onDown);
        canvas.removeEventListener("pointermove", onMove);
        canvas.removeEventListener("pointerup", onUp);
        canvas.removeEventListener("pointercancel", onUp);
      };
    }, [country]);

    return (
      <div className="cglobe" ref={wrapRef} style={{ "--c-hue": country.hue }}>
        <canvas ref={canvasRef} className="cglobe__canvas" aria-hidden="true"></canvas>
        {status !== "ready" && (
          <div className={`cglobe__fallback ${status === "error" ? "is-error" : ""}`}>
            <div className="cglobe__iso">{country.iso2}</div>
            <div className="cglobe__fallback-name">{country.name}</div>
            {status === "loading" && <div className="cglobe__spinner" aria-hidden="true"></div>}
          </div>
        )}
        <div className="cglobe__caption" aria-hidden="true">
          <span className="cglobe__dot"></span>
          {country.office.city} · {country.office.code}
        </div>
      </div>
    );
  }

  window.CountryGlobe = CountryGlobe;
})();
