// Three.js globe with real country outlines from Natural Earth topojson.
// Renders at 60fps, hover reveals country names, visited countries are highlighted.

const VISITED_SET = new Set([
  'United Kingdom', 'France', 'Spain', 'Germany', 'Belgium', 'Italy',
  'Switzerland', 'Austria', 'Czechia', 'Turkey', 'Egypt', 'Saudi Arabia',
  'United Arab Emirates', 'Qatar', 'India', 'Sri Lanka',
]);
const VISITED_COUNT_LABEL = 18; // including England/Wales/Scotland as separate personal list

// ---------- lat/lon → unit sphere ----------
function latLonToVec3(lat, lon, radius = 1) {
  const phi = (90 - lat) * Math.PI / 180;
  const theta = (lon + 180) * Math.PI / 180;
  const x = -radius * Math.sin(phi) * Math.cos(theta);
  const z = radius * Math.sin(phi) * Math.sin(theta);
  const y = radius * Math.cos(phi);
  return new THREE.Vector3(x, y, z);
}

// ---------- load topojson once, cache promise ----------
let worldDataPromise = null;
function loadWorldData() {
  if (worldDataPromise) return worldDataPromise;
  worldDataPromise = fetch('https://unpkg.com/world-atlas@2.0.2/countries-110m.json')
    .then(r => r.json())
    .then(topo => {
      const countries = topojson.feature(topo, topo.objects.countries);
      return countries.features;
    });
  return worldDataPromise;
}

// ---------- build a THREE.Group of country line meshes ----------
function buildCountryGroup(features, palette, setHovered) {
  const group = new THREE.Group();
  const dotGroup = new THREE.Group();
  const R = 1;

  const visitedColor = new THREE.Color(palette.pop);
  const landColor = new THREE.Color(palette.accent);

  const countryMeshes = [];

  features.forEach((feat) => {
    const name = feat.properties.name;
    const isVisited = VISITED_SET.has(name);
    const coords = feat.geometry.coordinates;
    const type = feat.geometry.type;

    const record = {
      name,
      visited: isVisited,
      lineMaterials: [],
      dotMaterials: [],
    };

    const polygons = type === 'Polygon' ? [coords] : coords;
    polygons.forEach((polygon) => {
      polygon.forEach((ring) => {
        const positions = [];
        for (let i = 0; i < ring.length - 1; i++) {
          const [lon1, lat1] = ring[i];
          const [lon2, lat2] = ring[i + 1];
          if (Math.abs(lon2 - lon1) > 180) continue;
          const steps = 3;
          for (let s = 0; s < steps; s++) {
            const t1 = s / steps, t2 = (s + 1) / steps;
            const la1 = lat1 + (lat2 - lat1) * t1;
            const lo1 = lon1 + (lon2 - lon1) * t1;
            const la2 = lat1 + (lat2 - lat1) * t2;
            const lo2 = lon1 + (lon2 - lon1) * t2;
            const p1 = latLonToVec3(la1, lo1, R * 1.002);
            const p2 = latLonToVec3(la2, lo2, R * 1.002);
            positions.push(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
          }
        }
        const geo = new THREE.BufferGeometry();
        geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
        const mat = new THREE.LineBasicMaterial({
          color: isVisited ? visitedColor : landColor,
          transparent: true,
          opacity: isVisited ? 1 : 0.45,
          linewidth: 1,
        });
        const line = new THREE.LineSegments(geo, mat);
        line.userData.country = name;
        group.add(line);
        record.lineMaterials.push(mat);
      });
    });

    if (isVisited) {
      polygons.forEach((poly) => {
        const outer = poly[0];
        const holes = poly.slice(1);
        if (outer.length < 4) return;

        let crosses = false;
        for (let i = 1; i < outer.length; i++) {
          if (Math.abs(outer[i][0] - outer[i - 1][0]) > 180) { crosses = true; break; }
        }
        if (crosses) return;

        const subdivide = (ring) => {
          const out = [];
          for (let i = 0; i < ring.length - 1; i++) {
            const [x1, y1] = ring[i];
            const [x2, y2] = ring[i + 1];
            const dx = x2 - x1, dy = y2 - y1;
            const dist = Math.sqrt(dx * dx + dy * dy);
            const steps = Math.max(1, Math.ceil(dist / 0.5));
            for (let s = 0; s < steps; s++) {
              const t = s / steps;
              out.push([x1 + dx * t, y1 + dy * t]);
            }
          }
          out.push(ring[ring.length - 1]);
          return out;
        };

        const subOuter = subdivide(outer);
        const subHoles = holes.map(subdivide);

        const flat = [];
        subOuter.forEach(([lo, la]) => flat.push(lo, la));
        const holeIndices = [];
        let offset = subOuter.length;
        subHoles.forEach((h) => {
          holeIndices.push(offset);
          h.forEach(([lo, la]) => flat.push(lo, la));
          offset += h.length;
        });

        const triIndices = earcut(flat, holeIndices.length ? holeIndices : undefined, 2);
        if (triIndices.length === 0) return;

        const positions = new Float32Array((flat.length / 2) * 3);
        for (let i = 0; i < flat.length; i += 2) {
          const lon = flat[i], lat = flat[i + 1];
          const v = latLonToVec3(lat, lon, R * 1.001);
          positions[(i / 2) * 3] = v.x;
          positions[(i / 2) * 3 + 1] = v.y;
          positions[(i / 2) * 3 + 2] = v.z;
        }

        const geo = new THREE.BufferGeometry();
        geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geo.setIndex(triIndices);

        const mat = new THREE.MeshBasicMaterial({
          color: visitedColor,
          transparent: true,
          opacity: 0.85,
          side: THREE.DoubleSide,
          depthWrite: false,
        });
        const mesh = new THREE.Mesh(geo, mat);
        mesh.userData.country = name;
        dotGroup.add(mesh);
        record.dotMaterials.push(mat);
      });
    }

    countryMeshes.push(record);
  });

  group.add(dotGroup);
  return { group, countryMeshes };
}

// ---------- Globe component ----------
function Globe({ palette, size: sizeProp }) {
  const mountRef = React.useRef(null);
  const [hovered, setHovered] = React.useState(null);
  const [loaded, setLoaded] = React.useState(false);
  const windowWidth = useWindowWidth();
  const size = sizeProp || Math.min(windowWidth - 32, 460);
  const stateRef = React.useRef({
    renderer: null, scene: null, camera: null, globe: null,
    countryMeshes: [], rotation: -Math.PI / 2, targetRotY: -Math.PI / 2, rotX: 0.4,
    autoRotate: false, dragging: false, lastX: 0, lastY: 0,
    raycaster: new THREE.Raycaster(), mouse: new THREE.Vector2(-2, -2),
    palette,
  });

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

  React.useEffect(() => {
    const mount = mountRef.current;
    if (!mount) return;

    const width = size, height = size;
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 10);
    camera.position.set(0, 0, 3);

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(width, height);
    renderer.setClearColor(0x000000, 0);
    mount.appendChild(renderer.domElement);

    const sphereGeo = new THREE.SphereGeometry(0.995, 64, 64);
    const sphereMat = new THREE.MeshBasicMaterial({
      color: new THREE.Color('#2a1f4a'),
      transparent: true, opacity: 0.18,
    });
    const sphere = new THREE.Mesh(sphereGeo, sphereMat);
    scene.add(sphere);

    const glowGeo = new THREE.SphereGeometry(1.06, 48, 48);
    const glowMat = new THREE.ShaderMaterial({
      uniforms: { uColor: { value: new THREE.Color(palette.pop) } },
      vertexShader: `
        varying vec3 vNormal;
        void main() {
          vNormal = normalize(normalMatrix * normal);
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        varying vec3 vNormal;
        uniform vec3 uColor;
        void main() {
          float intensity = pow(0.7 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);
          gl_FragColor = vec4(uColor, 1.0) * intensity * 0.9;
        }
      `,
      side: THREE.BackSide,
      blending: THREE.AdditiveBlending,
      transparent: true,
      depthWrite: false,
    });
    const glow = new THREE.Mesh(glowGeo, glowMat);
    scene.add(glow);

    const globe = new THREE.Group();
    scene.add(globe);

    stateRef.current.renderer = renderer;
    stateRef.current.scene = scene;
    stateRef.current.camera = camera;
    stateRef.current.globe = globe;

    loadWorldData().then((features) => {
      const { group, countryMeshes } = buildCountryGroup(features, stateRef.current.palette, setHovered);
      globe.add(group);
      stateRef.current.countryMeshes = countryMeshes;
      setLoaded(true);
    });

    let raf;
    const tick = () => {
      const st = stateRef.current;
      if (st.autoRotate && !st.dragging) {
        st.targetRotY += 0.0018;
      }
      st.rotation += (st.targetRotY - st.rotation) * 0.12;
      globe.rotation.y = st.rotation;
      globe.rotation.x = st.rotX;
      renderer.render(scene, camera);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(raf);
      renderer.dispose();
      if (renderer.domElement && renderer.domElement.parentNode) {
        renderer.domElement.parentNode.removeChild(renderer.domElement);
      }
      scene.traverse((obj) => {
        if (obj.geometry) obj.geometry.dispose();
        if (obj.material) {
          if (Array.isArray(obj.material)) obj.material.forEach(m => m.dispose());
          else obj.material.dispose();
        }
      });
    };
  }, []);

  React.useEffect(() => {
    const st = stateRef.current;
    if (!st.countryMeshes || st.countryMeshes.length === 0) return;
    const visitedColor = new THREE.Color(palette.pop);
    const landColor = new THREE.Color(palette.accent);
    st.countryMeshes.forEach((rec) => {
      rec.lineMaterials.forEach((m) => {
        m.color = rec.visited ? visitedColor.clone() : landColor.clone();
        m.opacity = rec.visited ? 1 : 0.45;
      });
      rec.dotMaterials.forEach((m) => { m.color = visitedColor.clone(); });
    });
  }, [palette]);

  const onPointerDown = (e) => {
    const st = stateRef.current;
    st.dragging = true;
    st.autoRotate = false;
    st.lastX = e.clientX;
    st.lastY = e.clientY;
  };
  const onPointerUp = () => { stateRef.current.dragging = false; };

  React.useEffect(() => {
    const onMove = (e) => {
      const st = stateRef.current;
      if (st.dragging) {
        const dx = e.clientX - st.lastX;
        const dy = e.clientY - st.lastY;
        st.targetRotY += dx * 0.006;
        st.rotX = Math.max(-0.8, Math.min(0.8, st.rotX + dy * 0.006));
        st.lastX = e.clientX;
        st.lastY = e.clientY;
      } else {
        const mount = mountRef.current;
        if (!mount) return;
        const rect = mount.getBoundingClientRect();
        const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
        const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
        if (x < -1 || x > 1 || y < -1 || y > 1) { setHovered(null); return; }
        st.mouse.set(x, y);
        st.raycaster.setFromCamera(st.mouse, st.camera);
        const hitSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
        const hit = new THREE.Vector3();
        const hasHit = st.raycaster.ray.intersectSphere(hitSphere, hit);
        if (!hasHit) { setHovered(null); return; }
        const inv = new THREE.Matrix4().copy(st.globe.matrixWorld).invert();
        hit.applyMatrix4(inv);
        const lat = 90 - Math.acos(hit.y / hit.length()) * 180 / Math.PI;
        let lon = Math.atan2(hit.z, -hit.x) * 180 / Math.PI - 180;
        if (lon < -180) lon += 360;
        if (lon > 180) lon -= 360;
        const name = findCountryAt(lat, lon);
        setHovered(name);
      }
    };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onPointerUp);
    return () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onPointerUp);
    };
  }, []);

  React.useEffect(() => {
    const st = stateRef.current;
    if (!st.countryMeshes.length) return;
    const visitedColor = new THREE.Color(st.palette.pop);
    const landColor = new THREE.Color(st.palette.accent);
    const highlightColor = new THREE.Color('#fff4e8');
    st.countryMeshes.forEach((rec) => {
      const isHov = rec.name === hovered;
      rec.lineMaterials.forEach((m) => {
        if (isHov) {
          m.color = highlightColor.clone(); m.opacity = 1;
        } else {
          m.color = rec.visited ? visitedColor.clone() : landColor.clone();
          m.opacity = rec.visited ? 1 : 0.45;
        }
      });
      rec.dotMaterials.forEach((m) => {
        m.color = isHov ? highlightColor.clone() : visitedColor.clone();
        m.opacity = isHov ? 1 : 0.85;
      });
    });
  }, [hovered]);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', userSelect: 'none', width: '100%' }}>
      <div style={{ position: 'relative', width: size, height: size, maxWidth: '100%' }}>
        <div ref={mountRef}
             onPointerDown={onPointerDown}
             style={{
               position: 'absolute', inset: 0,
               cursor: 'grab',
               touchAction: 'none',
             }} />
        {hovered && (
          <div style={{
            position: 'absolute', left: '50%', top: 16,
            transform: 'translateX(-50%)',
            background: '#2a1f4a', color: '#fff4e8',
            padding: '6px 14px', borderRadius: 999, fontSize: 13, fontWeight: 600,
            fontFamily: "'Manrope', sans-serif",
            border: `1px solid ${palette.accent}`,
            pointerEvents: 'none', whiteSpace: 'nowrap',
            boxShadow: '0 4px 16px rgba(0,0,0,0.4)',
          }}>{hovered}</div>
        )}
        {!loaded && (
          <div style={{
            position: 'absolute', inset: 0,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: palette.accent, fontSize: 14, opacity: 0.7,
          }}>~ loading the world ~</div>
        )}
      </div>
      <div className="hand" style={{ color: palette.accent, fontSize: 18, marginTop: 8, opacity: 0.8 }}>
        ~ drag to spin, hover over countries ~
      </div>
    </div>
  );
}

// ---------- find country at lat/lon ----------
let cachedFeatures = null;
loadWorldData().then((f) => { cachedFeatures = f; });

function pointInRing(lat, lon, ring) {
  let inside = false;
  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const [xi, yi] = ring[i], [xj, yj] = ring[j];
    const intersect = ((yi > lat) !== (yj > lat)) &&
                      (lon < (xj - xi) * (lat - yi) / (yj - yi) + xi);
    if (intersect) inside = !inside;
  }
  return inside;
}

function findCountryAt(lat, lon) {
  if (!cachedFeatures) return null;
  for (const feat of cachedFeatures) {
    const polys = feat.geometry.type === 'Polygon'
      ? [feat.geometry.coordinates]
      : feat.geometry.coordinates;
    for (const poly of polys) {
      if (pointInRing(lat, lon, poly[0])) return feat.properties.name;
    }
  }
  return null;
}

// ---------- Travels section ----------
function Travels({ palette }) {
  const isMobile = useIsMobile();
  const facts = [
    { emoji: '✈️', label: `${VISITED_COUNT_LABEL} countries explored, so far` },
    { emoji: '🌍', label: '3 continents: Europe, Asia, Africa' },
    { emoji: '🏔️', label: 'favourite scenery: Mount Jungfraujoch' },
    { emoji: '🗼', label: 'next on the list: Japan' },
  ];
  const careers = [
    {
      emoji: '🚀',
      title: 'astronaut',
      blurb: "i've always wanted to leave Earth and see it from a distance. i think that's kind of everyone's dream honestly. you can probably tell by my website that i love space.",
    },
    {
      emoji: '🦁',
      title: 'zookeeper',
      blurb: "i love animals. the idea of a job where my whole day is spent around them, learning their personalities and caring for them. also my favourite animal are otters.",
    },
  ];
  return (
    <section id="travels" style={{
      position: 'relative',
      padding: isMobile ? '80px 16px' : '120px 24px',
      maxWidth: 1200, margin: '0 auto',
    }}>
      <SectionHeader kicker="~ beyond the keyboard ~" title="fun facts" palette={palette} />
      <div style={{
        display: 'grid',
        gridTemplateColumns: isMobile ? '1fr' : 'minmax(320px, 1fr) minmax(280px, 440px)',
        gap: isMobile ? 32 : 56,
        alignItems: 'start',
      }}>
        <div style={{ justifySelf: 'center', position: isMobile ? 'static' : 'sticky', top: 80, width: '100%' }}>
          <Globe palette={palette} />
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 28 }}>
          <DoodleCard color="rgba(255, 244, 232, 0.95)" accent={palette.pop} style={{ padding: isMobile ? 18 : 32 }}>
            <div style={{
              display: 'inline-block',
              fontSize: 11, letterSpacing: '0.2em', textTransform: 'uppercase',
              color: '#fff4e8', fontWeight: 700, marginBottom: 14,
              background: '#3d2d66', padding: '5px 12px', borderRadius: 999,
            }}>~ travel ~</div>
            <p style={{
              fontSize: 17, lineHeight: 1.65, color: '#2a1f4a', margin: '0 0 18px',
              textWrap: 'pretty',
            }}>
              i love travelling and one of my biggest goals in life is to see as much of the world as i possibly can. every new place teaches me something i didn't know i needed to learn.
            </p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {facts.map((f, i) => (
                <div key={i} style={{
                  display: 'flex', alignItems: 'center', gap: 12,
                  padding: '10px 12px', background: `${palette.pop}18`,
                  borderRadius: 12, fontSize: 14.5, color: '#2a1f4a',
                }}>
                  <span style={{ fontSize: 20, lineHeight: 1 }}>{f.emoji}</span>
                  <span>{f.label}</span>
                </div>
              ))}
            </div>
          </DoodleCard>

          <DoodleCard color="rgba(255, 244, 232, 0.95)" accent="#b9a0e8" style={{ padding: isMobile ? 18 : 32 }}>
            <div style={{
              display: 'inline-block',
              fontSize: 11, letterSpacing: '0.2em', textTransform: 'uppercase',
              color: '#fff4e8', fontWeight: 700, marginBottom: 14,
              background: '#3d2d66', padding: '5px 12px', borderRadius: 999,
            }}>~ if not computer science, then... ~</div>
            <p style={{
              fontSize: 15, lineHeight: 1.65, color: '#2a1f4a', margin: '0 0 16px',
              textWrap: 'pretty', fontStyle: 'italic',
            }}>
              two alternative lives I think about:
            </p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
              {careers.map((c, i) => (
                <div key={i} style={{
                  padding: '14px 16px', background: '#ece2fa',
                  borderRadius: 14, border: '1px dashed #b9a0e8',
                }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6 }}>
                    <span style={{ fontSize: 22, lineHeight: 1 }}>{c.emoji}</span>
                    <span style={{
                      fontFamily: "'Nunito', sans-serif", fontWeight: 800,
                      fontSize: 16, color: '#2a1f4a', letterSpacing: '-0.01em',
                    }}>{c.title}</span>
                  </div>
                  <p style={{
                    margin: 0, fontSize: 14, lineHeight: 1.55, color: '#5b4a82',
                    textWrap: 'pretty',
                  }}>{c.blurb}</p>
                </div>
              ))}
            </div>
          </DoodleCard>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { Globe, Travels });
