/* global React */
const { useState, useEffect, useMemo, useRef } = React;

// ============================================================
// LIVE GLOBE — rotating sphere with live visitor pings
// ============================================================

const CITIES = [
  { name: "Tokyo",     lat: 35.6,  lon: 139.7, geo: { country: "JP", region: "Tokyo",  city: "Shibuya",  asn: "AS2516",  isp: "KDDI" },             hw: "tier · high · 8c · 12 GB",   net: "4g · 28.4 Mbps · rtt 41ms",  ai: "—", intent: 0.61 },
  { name: "São Paulo", lat: -23.5, lon: -46.6, geo: { country: "BR", region: "SP",     city: "Vila Mariana", asn: "AS28573", isp: "Claro NXT" },     hw: "tier · low · 4c · 3 GB",     net: "3g · 1.4 Mbps · rtt 320ms · saveData", ai: "—", intent: 0.18 },
  { name: "London",    lat: 51.5,  lon: -0.13, geo: { country: "GB", region: "ENG",    city: "Camden",    asn: "AS2856",  isp: "BT Openreach" },     hw: "tier · high · 10c · 16 GB",  net: "4g · 42.0 Mbps · rtt 22ms",  ai: "chat.openai.com", intent: 0.84 },
  { name: "Nairobi",   lat: -1.3,  lon: 36.8,  geo: { country: "KE", region: "Nairobi", city: "Westlands", asn: "AS33771", isp: "Safaricom" },        hw: "tier · mid · 6c · 6 GB",     net: "3g · 4.2 Mbps · rtt 180ms",  ai: "perplexity.ai", intent: 0.42 },
  { name: "Sydney",    lat: -33.9, lon: 151.2, geo: { country: "AU", region: "NSW",    city: "Surry Hills", asn: "AS1221", isp: "Telstra" },          hw: "tier · high · 8c · 16 GB",   net: "fiber · 220 Mbps · rtt 8ms", ai: "—", intent: 0.71 },
  { name: "München",   lat: 48.1,  lon: 11.6,  geo: { country: "DE", region: "BY",     city: "Schwabing", asn: "AS3320",  isp: "Deutsche Telekom" }, hw: "tier · high · 12c · 32 GB",  net: "fiber · 480 Mbps · rtt 6ms", ai: "—", intent: 0.92 },
  { name: "Mumbai",    lat: 19.1,  lon: 72.9,  geo: { country: "IN", region: "MH",     city: "Andheri",   asn: "AS55836", isp: "Jio" },              hw: "tier · mid · 6c · 4 GB",     net: "4g · 18.2 Mbps · rtt 64ms",  ai: "gemini.google.com", intent: 0.55 },
  { name: "Mexico City", lat: 19.4, lon: -99.1, geo: { country: "MX", region: "CDMX",  city: "Roma Norte", asn: "AS8151", isp: "Telmex" },           hw: "tier · mid · 4c · 8 GB",     net: "4g · 12.0 Mbps · rtt 88ms",  ai: "—", intent: 0.34 },
  { name: "Singapore", lat: 1.35,  lon: 103.8, geo: { country: "SG", region: "Central", city: "Tanjong Pagar", asn: "AS3758", isp: "Singtel" },     hw: "tier · high · 8c · 16 GB",   net: "fiber · 580 Mbps · rtt 4ms", ai: "claude.ai", intent: 0.88 },
  { name: "Toronto",   lat: 43.7,  lon: -79.4, geo: { country: "CA", region: "ON",     city: "Liberty Vlg", asn: "AS812",  isp: "Rogers" },          hw: "tier · high · 8c · 16 GB",   net: "fiber · 320 Mbps · rtt 12ms", ai: "—", intent: 0.66 },
  { name: "Lagos",     lat: 6.5,   lon: 3.4,   geo: { country: "NG", region: "Lagos",  city: "Ikeja",     asn: "AS37076", isp: "MTN" },              hw: "tier · low · 4c · 3 GB",     net: "3g · 2.1 Mbps · rtt 240ms · saveData", ai: "—", intent: 0.21 },
  { name: "Stockholm", lat: 59.3,  lon: 18.1,  geo: { country: "SE", region: "Stockholm", city: "Södermalm", asn: "AS3301", isp: "Telia" },         hw: "tier · high · 12c · 32 GB",  net: "fiber · 950 Mbps · rtt 3ms", ai: "—", intent: 0.79 },
  { name: "Seoul",     lat: 37.6,  lon: 127.0, geo: { country: "KR", region: "Seoul",  city: "Gangnam",   asn: "AS4766",  isp: "KT Corp" },          hw: "tier · high · 8c · 12 GB",   net: "fiber · 720 Mbps · rtt 5ms", ai: "—", intent: 0.74 },
  { name: "Buenos Aires", lat: -34.6, lon: -58.4, geo: { country: "AR", region: "BA", city: "Palermo",  asn: "AS22927", isp: "Telefónica" },         hw: "tier · mid · 6c · 8 GB",     net: "4g · 14.0 Mbps · rtt 110ms", ai: "—", intent: 0.39 },
];

const GLOBE_R = 150;
const GLOBE_C = 200;
const TILT = (23.5 * Math.PI) / 180;

function project(lat, lon, rotation) {
  const φ = (lat * Math.PI) / 180;
  const λ = ((lon - rotation) * Math.PI) / 180;
  const x = Math.cos(φ) * Math.sin(λ);
  const yRaw = -Math.sin(φ);
  const zRaw = Math.cos(φ) * Math.cos(λ);
  const y = yRaw * Math.cos(TILT) - zRaw * Math.sin(TILT);
  const z = yRaw * Math.sin(TILT) + zRaw * Math.cos(TILT);
  return {
    x: GLOBE_C + GLOBE_R * x,
    y: GLOBE_C + GLOBE_R * y,
    z: z,
  };
}

// seeded PRNG for stable star positions
function mulberry32(seed) {
  return function () {
    seed = (seed + 0x6D2B79F5) | 0;
    let t = seed;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}



function LiveGlobe() {
  const globeRef = useRef(null);
  const worldObj = useRef(null);
  const [active, setActive] = useState(2);
  const activeRef = useRef(active);
  activeRef.current = active;

  useEffect(() => {
    const id = setInterval(
      () => setActive((i) => (i + 1) % CITIES.length),
      3400
    );
    return () => clearInterval(id);
  }, []);

  useEffect(() => {
    const el = globeRef.current;
    if (!el || !window.Globe) return;
    const sizeOf = () => el.clientWidth || (el.parentElement && el.parentElement.clientWidth) || 480;

    const arcs = [[11,5],[2,7],[8,12],[1,13],[5,8],[9,2]].map(([a,b]) => ({
      startLat: CITIES[a].lat, startLng: CITIES[a].lon,
      endLat: CITIES[b].lat,   endLng: CITIES[b].lon,
    }));

    const world = window.Globe()(el)
      .width(sizeOf()).height(sizeOf())
      .backgroundColor('rgba(0,0,0,0)')
      .globeImageUrl('https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg')
      .bumpImageUrl('https://unpkg.com/three-globe/example/img/earth-topology.png')
      .showAtmosphere(true)
      .atmosphereColor('#8fc0ff')
      .atmosphereAltitude(0.2)
      .pointsData(CITIES.map((c) => ({ lat: c.lat, lng: c.lon, intent: c.intent })))
      .pointColor(() => '#e8804f')
      .pointAltitude((d) => 0.015 + d.intent * 0.07)
      .pointRadius(0.32)
      .arcsData(arcs)
      .arcColor(() => ['rgba(196,98,61,0.04)', 'rgba(232,128,79,0.95)'])
      .arcStroke(0.45)
      .arcAltitudeAutoScale(0.45)
      .arcDashLength(0.5)
      .arcDashGap(0.35)
      .arcDashInitialGap(() => Math.random())
      .arcDashAnimateTime(2600);

    worldObj.current = world;

    const controls = world.controls();
    controls.autoRotate = true;
    controls.autoRotateSpeed = 0.55;
    controls.enableZoom = false;
    controls.enablePan = false;
    world.pointOfView({ lat: 18, lng: 6, altitude: 2.3 }, 0);

    const onResize = () => { const sz = sizeOf(); world.width(sz); world.height(sz); };
    window.addEventListener('resize', onResize);
    // Re-measure once layout has settled (mobile column width, fonts, etc.)
    requestAnimationFrame(() => onResize());
    setTimeout(onResize, 350);

    return () => {
      window.removeEventListener('resize', onResize);
      worldObj.current = null;
      el.innerHTML = '';
    };
  }, []);

  // Pulsing terracotta ring on the active session city
  useEffect(() => {
    const world = worldObj.current;
    if (!world) return;
    const c = CITIES[active];
    world
      .ringsData([{ lat: c.lat, lng: c.lon }])
      .ringColor(() => (t) => 'rgba(232,128,79,' + (1 - t).toFixed(3) + ')')
      .ringMaxRadius(5.5)
      .ringPropagationSpeed(3)
      .ringRepeatPeriod(850);
  }, [active]);

  const activeCity = CITIES[active];

  return (
    <section className="section globe-section">
      <div className="wrap">
        <div className="globe-head">
          <div className="eyebrow" style={{color: "rgba(239,233,216,0.55)"}}>VITALITY · REAL-VISITOR OBSERVATION</div>
          <h2 className="h-1" style={{color: "var(--paper)", marginTop: 16, maxWidth: "22ch"}}>
            We watch your site from <span className="serif-italic accent-text">where it's actually loading</span> — not from a synthetic monitor in Frankfurt.
          </h2>
          <p className="lede" style={{color: "rgba(239,233,216,0.72)", marginTop: 18, maxWidth: "60ch"}}>
            Every light is a real session captured live. Cloudflare geo. Hardware tier. Network quality. Save-Data flag. AI engine referral. All before the first byte renders.
          </p>
        </div>

        <div className="globe-stage">
          <div className="globe-wrap">
            <div ref={globeRef} className="globe-canvas" aria-hidden="true"></div>
            <div className="globe-meta">
              <span className="dot"></span>
              <span>LIVE · 14 cities · 274 signals each</span>
            </div>
            <div className="globe-meta-right">
              <span>0 synthetic pings · 0 third-party trackers</span>
            </div>
          </div>

          <div className="globe-panel">
            <div className="globe-panel-head">
              <span className="dot live-dot"></span>
              <span className="panel-title">SESSION · CAPTURED</span>
              <span className="panel-id">sess_{(0xa1b2 + active * 17).toString(16)}</span>
            </div>

            <div className="globe-panel-body">
              <div key={active} className="city-name">
                {activeCity.geo.city}, {activeCity.geo.country}
                <span className="city-region">{activeCity.geo.region}</span>
              </div>

              <div className="capture-row">
                <span className="cr-k">geo.asn</span>
                <span className="cr-v">{activeCity.geo.asn}</span>
              </div>
              <div className="capture-row">
                <span className="cr-k">geo.isp</span>
                <span className="cr-v">{activeCity.geo.isp}</span>
              </div>
              <div className="capture-row">
                <span className="cr-k">hw</span>
                <span className="cr-v">{activeCity.hw}</span>
              </div>
              <div className="capture-row">
                <span className="cr-k">net</span>
                <span className="cr-v">{activeCity.net}</span>
              </div>
              <div className="capture-row">
                <span className="cr-k">ai.referrer</span>
                <span className="cr-v" style={{color: activeCity.ai === "—" ? "rgba(239,233,216,0.5)" : "var(--accent)"}}>
                  {activeCity.ai}
                </span>
              </div>
              <div className="capture-row">
                <span className="cr-k">intent</span>
                <span className="cr-v">
                  <span className="intent-bar">
                    <span className="intent-fill" style={{width: `${Math.round(activeCity.intent * 100)}%`}}></span>
                  </span>
                  <span className="intent-num">{activeCity.intent.toFixed(2)}</span>
                </span>
              </div>

              <div className="capture-footer">
                <span>+ 268 more captured · open in /events</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

// ============================================================
// SIGNAL ORBIT — concentric rotating rings of signal categories
// ============================================================

const ORBIT_RINGS = [
  { name: "Cloudflare geo",         ps: 10, ga: 3, dur: 60, hint: "country · region · city · postal · lat/lon · ISP · ASN · continent · EU flag · colo" },
  { name: "Hardware tier",          ps: 8,  ga: 0, dur: 72, hint: "cpu.cores · device.memory · gpu signals · ua.model · class · battery · pixel ratio · dpr" },
  { name: "Network quality",        ps: 7,  ga: 0, dur: 54, hint: "effectiveType · rtt · downlink · saveData · type · uplink · roaming" },
  { name: "Click IDs",              ps: 10, ga: 2, dur: 90, hint: "gclid · fbclid · msclkid · ttclid · li_fat_id · twclid · dclid · igshid · wbraid · gbraid" },
  { name: "AI engine referrals",    ps: 6,  ga: 0, dur: 48, hint: "chatgpt · perplexity · claude · gemini · prompt fingerprint · synthetic vs human" },
  { name: "Behavioral biometrics",  ps: 12, ga: 0, dur: 64, hint: "Fitts residual · tremor 4–8 Hz · scroll velocity sig · mouse curvature · hover dwell" },
  { name: "Web Vitals × cohort",    ps: 24, ga: 5, dur: 84, hint: "LCP · INP · CLS · FCP · TTFB · all at P75 by hardware × network × country (5 × 4 × 3 buckets)" },
  { name: "Paragraph engagement",   ps: 18, ga: 0, dur: 56, hint: "per-<p> dwell · scanpath · read complete · AI quote inclusion · headline pull" },
  { name: "Bot signal classes",     ps: 32, ga: 1, dur: 110, hint: "30+ orthogonal classes · Bayesian joint distribution · physiology · timing · environment" },
  { name: "Identity / cross-site",  ps: 14, ga: 0, dur: 76, hint: "HMAC keyed · lineage · session merge · device link · committee assembly" },
  { name: "Form & input telemetry", ps: 16, ga: 1, dur: 68, hint: "field abandonment · kbd detect · paste origin · autofill · validation cascade · keypress entropy" },
  { name: "Session replay metadata",ps: 22, ga: 0, dur: 96, hint: "dom snapshot diff · scroll · click · pointer · dom mutation · selective frame · privacy mask" },
  { name: "Conversion & funnel",    ps: 28, ga: 2, dur: 88, hint: "intent score (live) · exit pattern · attribution Markov · cohort lift · revenue weighting" },
  { name: "Cross-site / referrer",  ps: 12, ga: 0, dur: 100, hint: "dark social class · in-app browser · arrival pattern · viral coefficient · share fingerprint" },
];

const ORBIT_TOTAL_PS = ORBIT_RINGS.reduce((s, r) => s + r.ps, 0); // 219
const ORBIT_TOTAL_GA = ORBIT_RINGS.reduce((s, r) => s + r.ga, 0); // 14
const DERIVED_PS = 274 - ORBIT_TOTAL_PS; // 55

function OrbitRing({ ring, idx, total, active, r }) {
  const count = Math.min(ring.ps, 18);
  const dots = [];
  for (let i = 0; i < count; i++) {
    const ang = (i / count) * Math.PI * 2;
    dots.push({ x: Math.cos(ang) * r, y: Math.sin(ang) * r });
  }
  const gaCount = Math.min(ring.ga, 5);

  return (
    <g
      className="orbit-ring"
      style={{
        animation: `orbitSpin ${ring.dur}s linear infinite ${idx % 2 ? "reverse" : ""}`,
        opacity: active === idx ? 1 : 0.55,
        transition: "opacity 0.4s ease",
      }}
    >
      {/* ring path */}
      <circle
        cx="0" cy="0" r={r}
        fill="none"
        stroke={active === idx ? "var(--accent)" : "rgba(25,23,15,0.18)"}
        strokeWidth={active === idx ? 1.2 : 0.7}
        strokeDasharray={active === idx ? "none" : "2 5"}
      />
      {/* dots */}
      {dots.map((d, i) => (
        <circle
          key={i}
          cx={d.x} cy={d.y}
          r={active === idx ? 2.4 : 1.4}
          fill={i < gaCount ? "rgba(25,23,15,0.5)" : "var(--accent)"}
          opacity={active === idx ? 1 : 0.75}
        />
      ))}
    </g>
  );
}

function SignalOrbit() {
  const [active, setActive] = useState(8); // Bot signal classes — most striking
  const [paused, setPaused] = useState(false);
  const cycleRef = useRef(null);

  // auto-cycle
  useEffect(() => {
    if (paused) return;
    cycleRef.current = setInterval(() => {
      setActive((i) => (i + 1) % ORBIT_RINGS.length);
    }, 2400);
    return () => clearInterval(cycleRef.current);
  }, [paused]);

  const R_MIN = 50;
  const R_MAX = 250;
  const step = (R_MAX - R_MIN) / (ORBIT_RINGS.length - 1);

  const cur = ORBIT_RINGS[active];

  return (
    <section className="section orbit-section">
      <div className="wrap">
        <div className="orbit-head">
          <div className="eyebrow">THE COSMIC SCALE OF 274</div>
          <h2 className="h-1" style={{marginTop: 16, maxWidth: "22ch"}}>
            GA4 sees the <span className="serif-italic accent-text">white core</span>. We see <span className="serif-italic accent-text">everything past it.</span>
          </h2>
          <p className="lede" style={{marginTop: 18, maxWidth: "62ch"}}>
            Each ring is a class of signal. The size of the ring is its index, not its weight — every signal matters. Dark dots are what GA4 captures inside that class. The terracotta dots are what only PageSight captures.
          </p>
        </div>

        <div className="orbit-stage">
          <div
            className="orbit-canvas"
            onMouseEnter={() => setPaused(true)}
            onMouseLeave={() => setPaused(false)}
          >
            <svg viewBox="-300 -300 600 600" className="orbit-svg" aria-hidden="true">
              {/* GA4 core */}
              <g>
                <circle cx="0" cy="0" r={R_MIN - 16} fill="rgba(255,255,255,0.95)" stroke="rgba(25,23,15,0.4)" strokeWidth="1" />
                <text x="0" y="-4" textAnchor="middle" fontFamily="var(--mono)" fontSize="9" fill="rgba(25,23,15,0.7)" letterSpacing="0.06em">GA4</text>
                <text x="0" y="8" textAnchor="middle" fontFamily="var(--sans)" fontSize="18" fontWeight="500" fill="var(--ink)" letterSpacing="-0.02em">14</text>
              </g>

              {/* rings */}
              {ORBIT_RINGS.map((ring, i) => (
                <OrbitRing
                  key={ring.name}
                  ring={ring}
                  idx={i}
                  total={ORBIT_RINGS.length}
                  active={active}
                  r={R_MIN + i * step}
                />
              ))}

              {/* outer halo label */}
              <circle cx="0" cy="0" r={R_MAX + 16} fill="none" stroke="rgba(25,23,15,0.08)" strokeWidth="0.5" strokeDasharray="2 6" />
            </svg>

            <div className="orbit-center-label">
              <span className="mono">PAGESIGHT · 274</span>
            </div>
          </div>

          <div className="orbit-side">
            <div className="eyebrow">RING · {String(active + 1).padStart(2, "0")} / {String(ORBIT_RINGS.length).padStart(2, "0")}</div>
            <h3 className="orbit-active-name">{cur.name}</h3>
            <p className="orbit-active-hint">{cur.hint}</p>

            <div className="orbit-counts">
              <div className="orbit-count-card us">
                <div className="oc-eyebrow">PAGESIGHT</div>
                <div className="oc-num">{cur.ps}</div>
                <div className="oc-bar">
                  <span style={{width: `${(cur.ps / 32) * 100}%`}}></span>
                </div>
              </div>
              <div className="orbit-count-card them">
                <div className="oc-eyebrow">GA4</div>
                <div className="oc-num">{cur.ga}</div>
                <div className="oc-bar">
                  <span style={{width: `${(cur.ga / 32) * 100}%`, background: "rgba(25,23,15,0.5)"}}></span>
                </div>
              </div>
            </div>

            <div className="orbit-categories">
              {ORBIT_RINGS.map((r, i) => (
                <button
                  key={r.name}
                  className={`orbit-cat ${i === active ? "on" : ""}`}
                  onClick={() => { setActive(i); setPaused(true); }}
                >
                  <span className="oc-dot"></span>
                  <span className="oc-name">{r.name}</span>
                  <span className="oc-tally">{r.ps}<span className="oc-tally-sep"> / </span><span className="oc-tally-them">{r.ga}</span></span>
                </button>
              ))}
            </div>

            <div className="orbit-totals">
              <div>
                <div className="ot-label">CORE 14 CLASSES · TOTAL</div>
                <div className="ot-num">
                  <span style={{color: "var(--accent)"}}>{ORBIT_TOTAL_PS}</span>
                  <span className="vs">vs</span>
                  <span style={{color: "var(--ink-mute)"}}>{ORBIT_TOTAL_GA}</span>
                </div>
              </div>
              <div>
                <div className="ot-label">+ DERIVED · CROSS-PRODUCT</div>
                <div className="ot-num">
                  <span style={{color: "var(--accent)"}}>{DERIVED_PS}</span>
                  <span className="vs">vs</span>
                  <span style={{color: "var(--ink-mute)"}}>0</span>
                </div>
              </div>
              <div className="ot-grand">
                <div className="ot-label">GRAND TOTAL · PER VISITOR</div>
                <div className="ot-num grand">
                  <span className="serif-italic" style={{color: "var(--accent)"}}>274</span>
                  <span className="vs">vs</span>
                  <span style={{color: "var(--ink-mute)"}}>14</span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { LiveGlobe, SignalOrbit });
