// ============ INTERACTIVE GLOBE (v2 — frame on the Americas) ============
// d3-geo orthographic on canvas. Two categories only (Base / Network).
// Behaviour:
//   - On enter: locked, centered on Buenos Aires (zoomed in).
//   - On scroll-through-section: zoom-out + rotate toward an "Americas overview".
//   - At rest in overview: gentle ±15° longitude sway (bounded), never showing
//     the empty hemisphere.
//   - Drag rotates, but clamped to keep America always in view; ease-back on release.
//   - Reduced motion: static Americas overview, no sway, no scroll-zoom.

const BA = [-58.3816, -34.6037];

// 6 points, 2 categories. "base" = BA. Everything else "network".
const GLOBE_POINTS = [
  { id: 'ba',  coord: BA,                   type: 'base'    },
  { id: 'sp',  coord: [-46.6333, -23.5505], type: 'network' },
  { id: 'cba', coord: [-64.1888, -31.4201], type: 'network' },
  { id: 'er',  coord: [-60.5238, -31.7333], type: 'network' },
  { id: 'tdl', coord: [-59.1333, -37.3217], type: 'network' },
  { id: 'acp', coord: [-99.8237,  16.8531], type: 'network' },
];

const TYPE_COLOR = {
  base:    '#38bdf8',
  network: '#8b5cf6'
};

// Target views (d3 rotation = [-lon, -lat]).
// Entry: centered on Buenos Aires.
// Overview: centered roughly on Central America so all 6 points are visible.
const VIEW_ENTRY    = { rotate: [-BA[0], -BA[1]], scale: 1.95 };
const VIEW_OVERVIEW = { rotate: [80, 8],          scale: 1.00 };
// In rotation terms: lon_center ≈ -80 (Caribbean), lat_center ≈ -8.
// (d3 rotate is [-lon, -lat] so we store [80, 8].)

function Globe() {
  const { t } = window.useLang();
  const ref = window.useReveal();
  const reduce = window.useReducedMotion();

  const wrapRef = React.useRef(null);
  const canvasRef = React.useRef(null);
  const stateRef = React.useRef(null);
  const [land, setLand] = React.useState(null);
  const [hover, setHover] = React.useState(null);
  const [progress, setProgress] = React.useState(0);

  // ---- Load land geometry ----
  React.useEffect(() => {
    let cancelled = false;
    const tryFetch = (url) => fetch(url).then(r => r.ok ? r.json() : Promise.reject());
    tryFetch('https://cdn.jsdelivr.net/npm/world-atlas@2/land-110m.json')
      .catch(() => tryFetch('https://unpkg.com/world-atlas@2/land-110m.json'))
      .then(topo => {
        if (cancelled) return;
        const feature = window.topojson.feature(topo, topo.objects.land);
        setLand(feature);
      })
      .catch(() => { if (!cancelled) setLand({}); });
    return () => { cancelled = true; };
  }, []);

  // ---- Scroll progress (0..1 across the section) ----
  React.useEffect(() => {
    const el = wrapRef.current;
    if (!el) return;
    const onScroll = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || 800;
      // 0 when top of element hits center, 1 when bottom is at center.
      const p = (vh / 2 - r.top) / r.height;
      setProgress(Math.max(0, Math.min(1, p)));
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll, { passive: true });
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
    };
  }, []);

  // ---- Mount canvas + draw loop ----
  React.useEffect(() => {
    if (!land) return;
    const canvas = canvasRef.current;
    if (!canvas || !window.d3) return;
    const d3 = window.d3;
    let raf;
    const isMobile = window.matchMedia('(pointer:coarse)').matches;

    const state = {
      // user-introduced offset from the auto-target (clamped). Resets toward 0.
      userOffset: [0, 0],
      // velocity for ease-back
      userVel: [0, 0],
      dragging: false,
      lastPointer: null,
      sway: 0,
      arcT: 0,
      mouse: null,
      hoverId: null,
      progress: 0,
    };
    stateRef.current = state;

    // Allowed user drift (degrees) — keeps Americas in frame
    const MAX_DLON = 35;
    const MAX_DLAT = 25;

    const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));

    const onDown = (e) => {
      state.dragging = true;
      state.lastPointer = { x: e.clientX, y: e.clientY };
      canvas.classList.add('grabbing');
    };
    const onUp = () => {
      state.dragging = false;
      state.lastPointer = null;
      canvas.classList.remove('grabbing');
    };
    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      state.mouse = { x: e.clientX - r.left, y: e.clientY - r.top };
      if (state.dragging && state.lastPointer) {
        const dx = e.clientX - state.lastPointer.x;
        const dy = e.clientY - state.lastPointer.y;
        const sens = 0.35;
        state.userOffset[0] = clamp(state.userOffset[0] + dx * sens, -MAX_DLON, MAX_DLON);
        state.userOffset[1] = clamp(state.userOffset[1] - dy * sens, -MAX_DLAT, MAX_DLAT);
        state.lastPointer = { x: e.clientX, y: e.clientY };
      }
    };
    const onLeave = () => { state.mouse = null; state.dragging = false; canvas.classList.remove('grabbing'); };
    const onTouchStart = (e) => { if (e.touches[0]) onDown(e.touches[0]); };
    const onTouchMove  = (e) => { if (e.touches[0]) { onMove(e.touches[0]); e.preventDefault(); } };
    const onTouchEnd   = () => onUp();

    canvas.addEventListener('mousedown', onDown);
    window.addEventListener('mouseup', onUp);
    window.addEventListener('mousemove', onMove);
    canvas.addEventListener('mouseleave', onLeave);
    canvas.addEventListener('touchstart', onTouchStart, { passive: true });
    canvas.addEventListener('touchmove', onTouchMove, { passive: false });
    canvas.addEventListener('touchend', onTouchEnd);

    let lastT = performance.now();

    function draw(t) {
      const dt = Math.min(0.05, (t - lastT) / 1000); lastT = t;
      const w = canvas.clientWidth;
      const h = canvas.clientHeight;
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      if (canvas.width !== w * dpr) canvas.width = w * dpr;
      if (canvas.height !== h * dpr) canvas.height = h * dpr;
      const ctx = canvas.getContext('2d');
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.clearRect(0, 0, w, h);

      // ease-back to 0 when not dragging
      if (!state.dragging) {
        state.userOffset[0] *= Math.pow(0.001, dt); // exp decay
        state.userOffset[1] *= Math.pow(0.001, dt);
        if (Math.abs(state.userOffset[0]) < 0.01) state.userOffset[0] = 0;
        if (Math.abs(state.userOffset[1]) < 0.01) state.userOffset[1] = 0;
      }

      // gentle bounded sway (only after overview is reached)
      if (!reduce && !state.dragging) {
        state.sway += dt * 0.20; // rad/s
      }
      if (!reduce) state.arcT = (state.arcT + dt * 0.55) % 1;

      // p = scroll progress through section, eased
      // mobile: skip the entry-zoom; start in overview
      const p = isMobile || reduce ? 1 : easeInOut(state.progress);

      // Interpolate scale & target rotation between entry and overview
      const baseScale = Math.min(w, h) * 0.42;
      const scaleK = lerp(VIEW_ENTRY.scale, VIEW_OVERVIEW.scale, p);
      const scale = baseScale * scaleK;

      const targetLon = lerp(VIEW_ENTRY.rotate[0], VIEW_OVERVIEW.rotate[0], p);
      const targetLat = lerp(VIEW_ENTRY.rotate[1], VIEW_OVERVIEW.rotate[1], p);

      // sway only kicks in once we're at overview
      const swayLon = (!reduce ? Math.sin(state.sway) * 14 * p : 0);

      const rotation = [
        targetLon + swayLon + state.userOffset[0],
        targetLat + state.userOffset[1],
        0
      ];

      const projection = d3.geoOrthographic()
        .scale(scale)
        .translate([w/2, h/2])
        .rotate(rotation)
        .clipAngle(90);

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

      // ocean sphere
      const sphereGrad = ctx.createRadialGradient(w/2 - scale*0.3, h/2 - scale*0.3, scale*0.1, w/2, h/2, scale);
      sphereGrad.addColorStop(0, 'rgba(20, 28, 50, 1)');
      sphereGrad.addColorStop(0.7, 'rgba(10, 14, 28, 1)');
      sphereGrad.addColorStop(1, 'rgba(6, 8, 16, 1)');
      ctx.beginPath();
      path({ type: 'Sphere' });
      ctx.fillStyle = sphereGrad;
      ctx.fill();

      // outer rings
      ctx.beginPath();
      ctx.arc(w/2, h/2, scale, 0, Math.PI * 2);
      ctx.strokeStyle = 'rgba(56,189,248,0.35)';
      ctx.lineWidth = 1.2;
      ctx.stroke();
      ctx.beginPath();
      ctx.arc(w/2, h/2, scale + 8, 0, Math.PI * 2);
      ctx.strokeStyle = 'rgba(56,189,248,0.12)';
      ctx.lineWidth = 3;
      ctx.stroke();

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

      // land
      if (land && land.type) {
        ctx.beginPath();
        path(land);
        ctx.fillStyle = 'rgba(139, 92, 246, 0.16)';
        ctx.fill();
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.18)';
        ctx.lineWidth = 0.55;
        ctx.stroke();
      }

      // arcs from BA
      const center = projection.invert([w/2, h/2]) || BA;
      const visible = (coord) => d3.geoDistance(center, coord) < Math.PI / 2 - 0.02;

      GLOBE_POINTS.forEach(p => {
        if (p.id === 'ba') return;
        const interp = d3.geoInterpolate(BA, p.coord);
        const samples = 64;
        const pts = [];
        for (let i = 0; i <= samples; i++) {
          const c = interp(i / samples);
          pts.push({ xy: projection(c), vis: visible(c) });
        }
        ctx.beginPath();
        let drawing = false;
        for (const pp of pts) {
          if (!pp.vis) { drawing = false; continue; }
          if (!drawing) { ctx.moveTo(pp.xy[0], pp.xy[1]); drawing = true; }
          else ctx.lineTo(pp.xy[0], pp.xy[1]);
        }
        const gradStart = projection(BA), gradEnd = projection(p.coord);
        let arcGrad;
        if (gradStart && gradEnd) {
          arcGrad = ctx.createLinearGradient(gradStart[0], gradStart[1], gradEnd[0], gradEnd[1]);
          arcGrad.addColorStop(0, 'rgba(56,189,248,0.85)');
          arcGrad.addColorStop(1, 'rgba(139,92,246,0.85)');
        }
        ctx.strokeStyle = arcGrad || 'rgba(56,189,248,0.6)';
        ctx.lineWidth = 1.2;
        ctx.shadowColor = 'rgba(56,189,248,0.6)'; ctx.shadowBlur = 6;
        ctx.stroke();
        ctx.shadowBlur = 0;

        // traveling dash head
        if (!reduce) {
          const head = Math.floor(state.arcT * samples);
          const tailLen = 6;
          ctx.beginPath();
          let started = false;
          for (let i = Math.max(0, head - tailLen); i <= head; i++) {
            const pi = pts[i]; if (!pi || !pi.vis) { started = false; continue; }
            if (!started) { ctx.moveTo(pi.xy[0], pi.xy[1]); started = true; }
            else ctx.lineTo(pi.xy[0], pi.xy[1]);
          }
          ctx.strokeStyle = 'rgba(255,255,255,0.95)';
          ctx.lineWidth = 2;
          ctx.shadowColor = '#ffffff'; ctx.shadowBlur = 10;
          ctx.stroke();
          ctx.shadowBlur = 0;
        }
      });

      // markers
      let hoverFound = null;
      GLOBE_POINTS.forEach(p => {
        const xy = projection(p.coord);
        if (!xy || !visible(p.coord)) return;
        const color = TYPE_COLOR[p.type];
        const isBase = p.type === 'base';
        const baseR = isBase ? 6 : 4.5;

        if (isBase && !reduce) {
          const pulse = (Math.sin(t / 320) + 1) / 2;
          ctx.beginPath();
          ctx.arc(xy[0], xy[1], baseR + 8 + pulse * 6, 0, Math.PI * 2);
          ctx.fillStyle = `rgba(56,189,248,${0.18 - pulse * 0.08})`;
          ctx.fill();
        }

        ctx.beginPath();
        ctx.arc(xy[0], xy[1], baseR + 5, 0, Math.PI * 2);
        ctx.fillStyle = color + '33';
        ctx.fill();
        ctx.beginPath();
        ctx.arc(xy[0], xy[1], baseR, 0, Math.PI * 2);
        ctx.fillStyle = color;
        ctx.shadowColor = color; ctx.shadowBlur = 14;
        ctx.fill();
        ctx.shadowBlur = 0;
        ctx.lineWidth = 1.5;
        ctx.strokeStyle = 'rgba(255,255,255,0.7)';
        ctx.stroke();

        if (state.mouse) {
          const dx = state.mouse.x - xy[0], dy = state.mouse.y - xy[1];
          if (Math.hypot(dx, dy) < baseR + 12) hoverFound = { id: p.id, x: xy[0], y: xy[1] };
        }
      });

      if (hoverFound) {
        if (!state.hoverId || state.hoverId !== hoverFound.id) {
          state.hoverId = hoverFound.id;
          setHover(hoverFound);
        } else {
          setHover((h) => (h && h.x === hoverFound.x && h.y === hoverFound.y) ? h : hoverFound);
        }
      } else if (state.hoverId) {
        state.hoverId = null;
        setHover(null);
      }

      raf = requestAnimationFrame(draw);
    }

    raf = requestAnimationFrame(draw);
    return () => {
      cancelAnimationFrame(raf);
      canvas.removeEventListener('mousedown', onDown);
      window.removeEventListener('mouseup', onUp);
      window.removeEventListener('mousemove', onMove);
      canvas.removeEventListener('mouseleave', onLeave);
      canvas.removeEventListener('touchstart', onTouchStart);
      canvas.removeEventListener('touchmove', onTouchMove);
      canvas.removeEventListener('touchend', onTouchEnd);
    };
  }, [land, reduce]);

  React.useEffect(() => {
    if (stateRef.current) stateRef.current.progress = progress;
  }, [progress]);

  const tipData = hover ? { ...t.map.points[hover.id], type: GLOBE_POINTS.find(p => p.id === hover.id)?.type } : null;

  return (
    <section id="map" ref={ref}>
      <div className="container">
        <div className="fade-up">
          <span className="kicker">{t.map.kicker}</span>
          <h2 className="section-title">{t.map.h2}</h2>
        </div>
        <div className="globe-wrap fade-up d1" ref={wrapRef} style={{marginTop: 40}}>
          <canvas ref={canvasRef} className="globe-canvas" aria-label="Globo interactivo"></canvas>

          <div className="globe-legend">
            <div className="row"><span className="swatch base" />{t.map.legend.base}</div>
            <div className="row"><span className="swatch network" />{t.map.legend.network}</div>
          </div>

          <div className="globe-hint">{t.map.hint}</div>

          {hover && tipData && (
            <div className="globe-tip" style={{ left: hover.x, top: hover.y }}>
              <div className="tip-label">
                <span className="swatch" style={{ background: TYPE_COLOR[tipData.type], boxShadow: `0 0 8px ${TYPE_COLOR[tipData.type]}` }} />
                {tipData.label}
              </div>
              <div className="tip-desc">{tipData.tip}</div>
              <div className="tip-arrow" />
            </div>
          )}
        </div>
      </div>
    </section>
  );
}

function lerp(a, b, t) { return a + (b - a) * t; }
function easeInOut(t) { return t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t + 2, 2)/2; }

window.Globe = Globe;
