// MolecularRender — a real-time ball-and-stick molecular structure rendered with
// three.js. Procedurally generated atoms (instanced spheres) + bonds (additive
// line segments) + a faint dust field, slowly rotating in a continuous loop.
// Recolors to --site-accent. Designed to live on a full-bleed dark band — the
// performant, instant-loading way to put a "techy 3D object" on a landing page.

function MolecularCanvas({ accentKey }) {
  const mountRef = React.useRef(null);

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

    // ---- Resolve the brand accent (CSS var) into a real RGB ----------------
    const probe = document.createElement("span");
    probe.style.color = "var(--site-accent, #00FF66)";
    document.body.appendChild(probe);
    const accentCSS = getComputedStyle(probe).color || "rgb(0,255,102)";
    document.body.removeChild(probe);
    const accent = new THREE.Color(accentCSS);
    const paper = new THREE.Color("#F5F3EE");

    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    // ---- Scene / camera / renderer -----------------------------------------
    let w = mount.clientWidth || 800;
    let h = mount.clientHeight || 560;

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(48, w / h, 0.1, 100);
    camera.position.set(0, 0, 9.2);

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    renderer.setSize(w, h);
    mount.appendChild(renderer.domElement);
    renderer.domElement.style.display = "block";

    const group = new THREE.Group();
    // Offset the structure toward the right so the left-side copy stays clear.
    group.position.x = 1.7;
    scene.add(group);

    // ---- Generate atoms -----------------------------------------------------
    // A clustered molecular cloud: fibonacci-distributed directions, varied
    // radii, plus a few core atoms near the center.
    const N = 78;
    const positions = [];
    const golden = Math.PI * (3 - Math.sqrt(5));
    for (let i = 0; i < N; i++) {
      const t = i / (N - 1);
      const inclination = Math.acos(1 - 2 * t);
      const azimuth = golden * i;
      // radius shell with some randomness; a handful pulled toward the core
      let r = 1.4 + Math.pow(Math.random(), 0.6) * 2.0;
      if (i % 9 === 0) r *= 0.35;
      const x = r * Math.sin(inclination) * Math.cos(azimuth);
      const y = r * Math.sin(inclination) * Math.sin(azimuth) * 0.85;
      const z = r * Math.cos(inclination);
      positions.push(new THREE.Vector3(x, y, z));
    }

    // ---- Atoms as an InstancedMesh -----------------------------------------
    const sphereGeo = new THREE.IcosahedronGeometry(1, 2);
    const sphereMat = new THREE.MeshStandardMaterial({
      roughness: 0.32, metalness: 0.05,
      emissiveIntensity: 0.55,
    });
    const atoms = new THREE.InstancedMesh(sphereGeo, sphereMat, N);
    atoms.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    const dummy = new THREE.Object3D();
    const radii = [];
    for (let i = 0; i < N; i++) {
      const big = i % 11 === 0;
      const radius = big ? 0.30 : 0.085 + Math.random() * 0.12;
      radii.push(radius);
      dummy.position.copy(positions[i]);
      dummy.scale.setScalar(radius);
      dummy.updateMatrix();
      atoms.setMatrixAt(i, dummy.matrix);
      // ~30% are light "nodes" for two-tone contrast; rest glow accent.
      const c = Math.random() < 0.30 ? paper : accent;
      atoms.setColorAt(i, c);
    }
    // Emissive needs to match base color for the glow — approximate with accent.
    sphereMat.emissive = accent;
    group.add(atoms);

    // ---- Bonds: connect each atom to its nearest neighbors -----------------
    const segPts = [];
    const maxBond = 1.45;
    for (let i = 0; i < N; i++) {
      // find up to 3 nearest within maxBond
      const dists = [];
      for (let j = 0; j < N; j++) {
        if (i === j) continue;
        const d = positions[i].distanceTo(positions[j]);
        if (d < maxBond) dists.push([d, j]);
      }
      dists.sort((a, b) => a[0] - b[0]);
      for (let k = 0; k < Math.min(3, dists.length); k++) {
        const j = dists[k][1];
        if (j > i) { segPts.push(positions[i], positions[j]); }
      }
    }
    const bondGeo = new THREE.BufferGeometry().setFromPoints(segPts);
    const bondMat = new THREE.LineBasicMaterial({
      color: accent, transparent: true, opacity: 0.42,
      blending: THREE.AdditiveBlending, depthWrite: false,
    });
    const bonds = new THREE.LineSegments(bondGeo, bondMat);
    group.add(bonds);

    // ---- Dust field for depth ----------------------------------------------
    const dustN = 360;
    const dustPos = new Float32Array(dustN * 3);
    for (let i = 0; i < dustN; i++) {
      const r = 4 + Math.random() * 6;
      const a = Math.random() * Math.PI * 2;
      const b = Math.acos(2 * Math.random() - 1);
      dustPos[i * 3] = r * Math.sin(b) * Math.cos(a);
      dustPos[i * 3 + 1] = r * Math.sin(b) * Math.sin(a);
      dustPos[i * 3 + 2] = r * Math.cos(b);
    }
    const dustGeo = new THREE.BufferGeometry();
    dustGeo.setAttribute("position", new THREE.BufferAttribute(dustPos, 3));
    const dustMat = new THREE.PointsMaterial({
      color: accent, size: 0.035, transparent: true, opacity: 0.5,
      blending: THREE.AdditiveBlending, depthWrite: false,
    });
    const dust = new THREE.Points(dustGeo, dustMat);
    scene.add(dust);

    // ---- Lighting -----------------------------------------------------------
    scene.add(new THREE.AmbientLight(0xffffff, 0.55));
    const key = new THREE.PointLight(accent.getHex(), 2.4, 40);
    key.position.set(6, 5, 8);
    scene.add(key);
    const rim = new THREE.PointLight(0xffffff, 1.1, 40);
    rim.position.set(-7, -4, 4);
    scene.add(rim);

    // ---- Interaction: subtle pointer parallax ------------------------------
    const target = { x: 0, y: 0 };
    const onMove = (e) => {
      const r = mount.getBoundingClientRect();
      target.x = ((e.clientX - r.left) / r.width - 0.5) * 2;
      target.y = ((e.clientY - r.top) / r.height - 0.5) * 2;
    };
    mount.addEventListener("pointermove", onMove);

    // ---- Resize -------------------------------------------------------------
    const resize = () => {
      w = mount.clientWidth; h = mount.clientHeight;
      if (!w || !h) return;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    };
    const ro = new ResizeObserver(resize);
    ro.observe(mount);

    // ---- Render loop (paused when off-screen) ------------------------------
    let raf, visible = true, t0 = performance.now();
    const io = new IntersectionObserver(
      ([entry]) => { visible = entry.isIntersecting; if (visible) loop(); },
      { threshold: 0.01 }
    );
    io.observe(mount);

    let curRotX = 0, curRotY = 0;
    const loop = () => {
      if (!visible) return;
      // Paused while the page-transition overlay is covering/animating.
      if (window.__omPtAnimating) { raf = requestAnimationFrame(loop); return; }
      const now = performance.now();
      const elapsed = (now - t0) / 1000;

      if (!reduce) {
        group.rotation.y += 0.0026;
        group.rotation.x = Math.sin(elapsed * 0.18) * 0.18;
        dust.rotation.y -= 0.0008;
        // gentle emissive pulse
        sphereMat.emissiveIntensity = 0.5 + Math.sin(elapsed * 1.1) * 0.12;
        // pointer parallax
        curRotY += (target.x * 0.22 - curRotY) * 0.05;
        curRotX += (target.y * 0.16 - curRotX) * 0.05;
        group.rotation.y += curRotY * 0.0;
        camera.position.x += (target.x * 0.6 - camera.position.x) * 0.04;
        camera.position.y += (-target.y * 0.4 - camera.position.y) * 0.04;
        camera.lookAt(0, 0, 0);
      }

      renderer.render(scene, camera);
      raf = requestAnimationFrame(loop);
    };
    renderer.render(scene, camera);
    if (!reduce) loop();

    // ---- Cleanup ------------------------------------------------------------
    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      io.disconnect();
      mount.removeEventListener("pointermove", onMove);
      sphereGeo.dispose(); sphereMat.dispose();
      bondGeo.dispose(); bondMat.dispose();
      dustGeo.dispose(); dustMat.dispose();
      renderer.dispose();
      if (renderer.domElement.parentNode === mount) mount.removeChild(renderer.domElement);
    };
  }, [accentKey]);

  return <div ref={mountRef} style={{ position: "absolute", inset: 0 }} />;
}

function MolecularSection({ accentKey }) {
  return (
    <section id="studio" style={{
      position: "relative",
      background: "var(--ink)",
      color: "var(--paper)",
      overflow: "hidden",
      borderTop: "1px solid rgba(245,243,238,0.08)",
      borderBottom: "1px solid rgba(245,243,238,0.08)",
      minHeight: "min(720px, 88vh)",
    }}>
      {/* Live molecular render fills the band */}
      <MolecularCanvas accentKey={accentKey} />

      {/* Radial vignette to keep the left-side copy legible */}
      <div aria-hidden="true" style={{
        position: "absolute", inset: 0, pointerEvents: "none",
        background: "radial-gradient(120% 100% at 78% 50%, transparent 0%, transparent 38%, rgba(10,10,10,0.55) 72%, rgba(10,10,10,0.92) 100%), linear-gradient(90deg, rgba(10,10,10,0.86) 0%, rgba(10,10,10,0.45) 40%, transparent 62%)",
      }}/>

      {/* Blueprint HUD chrome */}
      <div style={{
        position: "absolute", right: 28, top: 28,
        fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em",
        color: "var(--n-400)", textTransform: "uppercase",
        display: "flex", alignItems: "center", gap: 8, pointerEvents: "none",
      }}>
        <span style={{
          width: 6, height: 6, borderRadius: "50%",
          background: "var(--site-accent, #00FF66)",
          boxShadow: "0 0 10px var(--site-accent, #00FF66)",
          animation: "blink 1.6s ease-in-out infinite",
        }}/>
        WEBGL · REALTIME
      </div>

      {/* Left-aligned copy */}
      <div className="container" style={{
        position: "relative",
        minHeight: "min(720px, 88vh)",
        display: "flex",
        alignItems: "center",
      }}>
        <Reveal className="reveal-up" style={{
          maxWidth: "44ch", display: "flex", flexDirection: "column", gap: 24,
        }}>
          <EyebrowLabel color="var(--n-400)">RESEARCH &amp; DEVELOPMENT</EyebrowLabel>
          <h2 style={{
            fontFamily: "var(--font-display)",
            fontSize: "clamp(40px, 5.2vw, 76px)",
            fontWeight: 500, lineHeight: 0.98, letterSpacing: "-0.025em",
            color: "var(--paper)", margin: 0, textWrap: "balance",
          }}>
            Systems engineered down to the
            {" "}<span style={{ color: "var(--site-accent, #00FF66)" }}>last node</span>.
          </h2>
          <p style={{
            fontSize: 18, lineHeight: 1.6, color: "var(--n-300)",
            fontFamily: "var(--font-sans)", margin: 0, maxWidth: "50ch",
          }}>
            Every tool is co-developed with practising professionals — the architects,
            lawyers, accountants and clinicians who live the workflow every day. We
            build alongside the people who feel the pain, because we're practitioners
            too. We know your industry because we've worked it.
          </p>
        </Reveal>
      </div>
    </section>
  );
}

Object.assign(window, { MolecularSection });
