/* global React, d3, topojson */
// Rotating point-cloud globe with US outline, state lines, and respondent dots.
// Uses D3 orthographic projection rendered to a single canvas.
const { useEffect, useRef } = React;

window.GlobeHero = function GlobeHero() {
  const canvasRef = useRef(null);
  const wrapRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;

    const ctx = canvas.getContext('2d');
    let width = 0, height = 0, dpr = window.devicePixelRatio || 1;
    let world = null, us = null, points = [];
    let raf = 0, t0 = performance.now();
    let rotation = 125;
    let direction = 1;
    const ROT_SPEED = 0.42;
    const CENTER_LON = 125;
    const SWING = 8;
    const MIN_LON = CENTER_LON - SWING;
    const MAX_LON = CENTER_LON + SWING;
    const ZOOM = 2.549;            // sphere diameter as multiple of viewport-min

    // Pre-built point cloud (lon/lat) for globe surface
    const cloud = [];
    {
      const N = 4000;
      for (let i = 0; i < N; i++) {
        const y = 1 - (i / (N - 1)) * 2; // -1..1
        const r = Math.sqrt(1 - y * y);
        const theta = Math.PI * (1 + Math.sqrt(5)) * i; // golden spiral
        const x = Math.cos(theta) * r;
        const z = Math.sin(theta) * r;
        const lat = Math.asin(y) * 180 / Math.PI;
        const lon = Math.atan2(z, x) * 180 / Math.PI;
        cloud.push([lon, lat]);
      }
    }

    function resize() {
      const rect = wrap.getBoundingClientRect();
      width = rect.width;
      height = rect.height;
      canvas.width = width * dpr;
      canvas.height = height * dpr;
      canvas.style.width = width + 'px';
      canvas.style.height = height + 'px';
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
    resize();
    window.addEventListener('resize', resize);

    // D3 projection
    const projection = d3.geoOrthographic()
      .clipAngle(90)
      .precision(0.5);

    function fit() {
      const size = Math.min(width, height) * ZOOM;
      const R = size / 2;
      // Anchor sphere center so its top edge sits flush with the top of the
      // canvas (no clipping at top), and shift left so the US fills the left
      // half while the right limb arcs across the view.
      const cx = width * 0.10 - 88;
      const cy = R + 112;
      projection.scale(R).translate([cx, cy]);
      return { cx, cy, R };
    }

    const path = d3.geoPath(projection, ctx);

    function draw() {
      const { cx, cy, R } = fit();
      ctx.clearRect(0, 0, width, height);

      // soft globe disk — flat light grey, no shading
      ctx.save();
      ctx.beginPath();
      ctx.arc(cx, cy, R, 0, Math.PI * 2);
      ctx.clip();
      ctx.fillStyle = '#ececea';
      ctx.fillRect(cx - R, cy - R, R * 2, R * 2);

      // point cloud (only points on visible hemisphere)
      ctx.fillStyle = 'rgba(0,0,0,0.28)';
      for (let i = 0; i < cloud.length; i++) {
        const p = cloud[i];
        const xy = projection(p);
        if (!xy) continue;
        ctx.fillRect(xy[0], xy[1], 1.1, 1.1);
      }

      if (world) {
        // Other countries: very faint outline
        ctx.beginPath();
        path(world.countries);
        ctx.strokeStyle = 'rgba(0,0,0,0.22)';
        ctx.lineWidth = 0.5;
        ctx.stroke();
      }

      if (us) {
        // US states: subtle fill + outlines
        ctx.beginPath();
        path(us.states);
        ctx.fillStyle = 'rgba(255,255,255,0.55)';
        ctx.fill();
        ctx.strokeStyle = 'rgba(0,0,0,0.45)';
        ctx.lineWidth = 0.6;
        ctx.stroke();
      }

      // respondent dots
      const now = performance.now();
      for (let i = 0; i < points.length; i++) {
        const pt = points[i];
        // pt = [lat, lng, score, phase]
        const xy = projection([pt[1], pt[0]]);
        if (!xy) continue;
        // visibility check (orthographic clips, so xy is null when on far side)
        const flicker = 0.55 + 0.45 * Math.sin(now * 0.003 + pt[3]);
        const score = pt[2]; // 0..10
        const isPos = score >= 6;
        const isNeg = score <= 4;
        let color;
        if (isPos)      color = `rgba(87, 186, 209, ${0.5 + flicker * 0.5})`;   // #57BAD1
        else if (isNeg) color = `rgba(236, 202, 184, ${0.5 + flicker * 0.5})`;  // #ECCAB8
        else            color = `rgba(120, 120, 120, ${0.35 + flicker * 0.4})`;
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.arc(xy[0], xy[1], 2.0, 0, Math.PI * 2);
        ctx.fill();
      }

      ctx.restore();
    }

    function tick(t) {
      const dt = (t - t0) / 1000;
      t0 = t;
      rotation += direction * ROT_SPEED * dt;
      // bounce when US drifts off either edge
      if (rotation < MIN_LON) { rotation = MIN_LON; direction = 1; }
      if (rotation > MAX_LON) { rotation = MAX_LON; direction = -1; }
      projection.rotate([rotation, -2, 0]);
      draw();
      raf = requestAnimationFrame(tick);
    }

    // Load topojson + respondents in parallel
    Promise.all([
      fetch('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json').then(r => r.json()),
      fetch('https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json').then(r => r.json()),
      fetch('respondents.json').then(r => r.json()),
    ]).then(([w, u, resp]) => {
      world = { countries: topojson.feature(w, w.objects.countries) };
      us = {
        states: topojson.feature(u, u.objects.states),
        nation: topojson.feature(u, u.objects.nation),
      };
      points = (resp.points || []).map((p, i) => [p[0], p[1], p[2], Math.random() * Math.PI * 2]);
      raf = requestAnimationFrame(tick);
    }).catch(err => {
      console.error('Globe load failed', err);
    });

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', resize);
    };
  }, []);

  return (
    <div className="globe-wrap" ref={wrapRef}>
      <canvas ref={canvasRef}/>
    </div>
  );
};
