// hero3d.jsx — "The Eye of the Model": 4D hypercube projection + iris streamers
// + central singularity. A symbol for godmodel — all-seeing intelligence.
(function () {
  const PALETTES = {
    iridescent: { a: 0xc4b5fd, b: 0xa8e6ff, c: 0xfcd5b5, bg: 0x07070a },
    obsidian:   { a: 0xeeeae0, b: 0xb8b3a8, c: 0xffffff, bg: 0x050507 },
    plasma:     { a: 0xff4d8f, b: 0x8a2be2, c: 0x00e0ff, bg: 0x06030d },
    aurora:     { a: 0x6ee7b7, b: 0xa8e6ff, c: 0xc4b5fd, bg: 0x040b0a },
    solar:      { a: 0xfcd5b5, b: 0xff8a3d, c: 0xffe27a, bg: 0x0a0703 },
  };

  function createScene(canvas) {
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(34, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.set(0, 0, 11);

    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor(0x000000, 0);
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 0.8;

    function makeEnvTexture(palette) {
      const w = 1024, h = 512;
      const cnv = document.createElement('canvas');
      cnv.width = w; cnv.height = h;
      const ctx = cnv.getContext('2d');
      const bg = '#' + palette.bg.toString(16).padStart(6, '0');
      const a = '#' + palette.a.toString(16).padStart(6, '0');
      const b = '#' + palette.b.toString(16).padStart(6, '0');
      const c = '#' + palette.c.toString(16).padStart(6, '0');
      const grad = ctx.createLinearGradient(0, 0, 0, h);
      grad.addColorStop(0, bg);
      grad.addColorStop(0.45, a);
      grad.addColorStop(0.55, b);
      grad.addColorStop(0.85, c);
      grad.addColorStop(1, bg);
      ctx.fillStyle = grad; ctx.fillRect(0, 0, w, h);
      [[w*0.3, h*0.4, 200, 'rgba(255,255,255,0.7)'],
       [w*0.7, h*0.55, 260, a]].forEach(([x,y,r,col])=>{
        const g2 = ctx.createRadialGradient(x,y,0,x,y,r);
        g2.addColorStop(0,col); g2.addColorStop(1,'rgba(0,0,0,0)');
        ctx.fillStyle=g2; ctx.fillRect(0,0,w,h);
       });
      const tex = new THREE.CanvasTexture(cnv);
      tex.mapping = THREE.EquirectangularReflectionMapping;
      tex.colorSpace = THREE.SRGBColorSpace;
      return tex;
    }

    let envTex = makeEnvTexture(PALETTES.iridescent);
    scene.environment = envTex;

    scene.add(new THREE.AmbientLight(0xffffff, 0.3));
    const key = new THREE.PointLight(0xc4b5fd, 30, 40); key.position.set(5,5,6); scene.add(key);
    const fill = new THREE.PointLight(0xa8e6ff, 20, 40); fill.position.set(-5,-3,4); scene.add(fill);

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

    // ============================================================
    // LAYER 1: 4D Hypercube (tesseract) — projected to 3D every frame
    // ============================================================
    // 16 vertices of a 4D unit cube, edges connect when they differ in one coordinate.
    const verts4D = [];
    for (let i = 0; i < 16; i++) {
      verts4D.push([
        (i & 1) ? 1 : -1,
        (i & 2) ? 1 : -1,
        (i & 4) ? 1 : -1,
        (i & 8) ? 1 : -1,
      ]);
    }
    const edges4D = [];
    for (let i = 0; i < 16; i++) {
      for (let j = i+1; j < 16; j++) {
        let diff = 0;
        for (let k = 0; k < 4; k++) if (verts4D[i][k] !== verts4D[j][k]) diff++;
        if (diff === 1) edges4D.push([i, j]);
      }
    }

    const tesseractGeo = new THREE.BufferGeometry();
    const tesseractPos = new Float32Array(edges4D.length * 2 * 3);
    tesseractGeo.setAttribute('position', new THREE.BufferAttribute(tesseractPos, 3));
    const tesseractMat = new THREE.LineBasicMaterial({
      color: 0xc4b5fd,
      transparent: true,
      opacity: 0.55,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    });
    const tesseractLines = new THREE.LineSegments(tesseractGeo, tesseractMat);
    group.add(tesseractLines);

    // Glowing vertex points
    const vGeo = new THREE.BufferGeometry();
    const vPos = new Float32Array(16 * 3);
    vGeo.setAttribute('position', new THREE.BufferAttribute(vPos, 3));
    const vMat = new THREE.PointsMaterial({
      color: 0xffffff,
      size: 0.08,
      transparent: true,
      opacity: 0.9,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      sizeAttenuation: true,
    });
    const tesseractPoints = new THREE.Points(vGeo, vMat);
    group.add(tesseractPoints);

    function project4D(t) {
      // Rotation in XW + YW planes (the "true" 4D rotation)
      const a = t * 0.4;
      const b = t * 0.3;
      const cosA = Math.cos(a), sinA = Math.sin(a);
      const cosB = Math.cos(b), sinB = Math.sin(b);
      const TR = 1.4; // size

      const proj = verts4D.map(([x,y,z,w]) => {
        // XW rotation
        let nx = x * cosA - w * sinA;
        let nw = x * sinA + w * cosA;
        // YW rotation
        let ny = y * cosB - nw * sinB;
        nw = y * sinB + nw * cosB;
        // Perspective project from 4D to 3D (w as depth)
        const dist = 3;
        const factor = 1 / (dist - nw);
        return [nx * factor * TR, ny * factor * TR, z * factor * TR];
      });

      // Fill edge buffer
      edges4D.forEach(([i, j], idx) => {
        const p1 = proj[i], p2 = proj[j];
        tesseractPos[idx*6+0] = p1[0]; tesseractPos[idx*6+1] = p1[1]; tesseractPos[idx*6+2] = p1[2];
        tesseractPos[idx*6+3] = p2[0]; tesseractPos[idx*6+4] = p2[1]; tesseractPos[idx*6+5] = p2[2];
      });
      tesseractGeo.attributes.position.needsUpdate = true;

      // Fill point buffer
      proj.forEach((p, i) => {
        vPos[i*3] = p[0]; vPos[i*3+1] = p[1]; vPos[i*3+2] = p[2];
      });
      vGeo.attributes.position.needsUpdate = true;
    }

    // ============================================================
    // LAYER 2: The Eye — iris streamers (radial line strands)
    // ============================================================
    const irisGroup = new THREE.Group();
    group.add(irisGroup);

    const STRANDS = 64;
    const irisMat = new THREE.LineBasicMaterial({
      color: 0xa8e6ff,
      transparent: true,
      opacity: 0.4,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    });
    const strands = [];
    for (let i = 0; i < STRANDS; i++) {
      const ang = (i / STRANDS) * Math.PI * 2;
      const inner = 1.7 + Math.random() * 0.2;
      const outer = 2.5 + Math.random() * 0.6;
      const wob = Math.random() * 0.15;
      const points = [];
      const segs = 24;
      for (let s = 0; s <= segs; s++) {
        const t = s / segs;
        const r = inner + (outer - inner) * t;
        const a = ang + Math.sin(t * Math.PI * 2 + i) * wob;
        points.push(new THREE.Vector3(Math.cos(a) * r, Math.sin(a) * r, 0));
      }
      const g = new THREE.BufferGeometry().setFromPoints(points);
      const line = new THREE.Line(g, irisMat.clone());
      irisGroup.add(line);
      strands.push({ line, baseAng: ang, inner, outer, wob, points });
    }

    // ============================================================
    // LAYER 3: The Pupil — black sphere with thin glowing rim
    // ============================================================
    const pupilGeo = new THREE.SphereGeometry(0.6, 64, 64);
    const pupilMat = new THREE.MeshBasicMaterial({ color: 0x000000 });
    const pupil = new THREE.Mesh(pupilGeo, pupilMat);
    group.add(pupil);

    // Rim ring
    const rimGeo = new THREE.RingGeometry(0.6, 0.62, 96);
    const rimMat = new THREE.MeshBasicMaterial({
      color: 0xffffff, transparent: true, opacity: 0.95, side: THREE.DoubleSide,
    });
    const rim = new THREE.Mesh(rimGeo, rimMat);
    group.add(rim);

    // Outer accent ring (subtle)
    const outerRingGeo = new THREE.TorusGeometry(2.7, 0.004, 8, 200);
    const outerRingMat = new THREE.MeshBasicMaterial({
      color: 0xc4b5fd, transparent: true, opacity: 0.3,
    });
    const outerRing = new THREE.Mesh(outerRingGeo, outerRingMat);
    outerRing.rotation.x = Math.PI / 2;
    group.add(outerRing);

    // ============================================================
    // LAYER 4: Singularity light streams pulled toward pupil
    // ============================================================
    const STREAM_COUNT = 600;
    const sGeo = new THREE.BufferGeometry();
    const sPos = new Float32Array(STREAM_COUNT * 3);
    const sSeed = new Float32Array(STREAM_COUNT);
    for (let i = 0; i < STREAM_COUNT; i++) {
      const r = 0.6 + Math.random() * 3.5;
      const ang = Math.random() * Math.PI * 2;
      sPos[i*3]   = Math.cos(ang) * r;
      sPos[i*3+1] = Math.sin(ang) * r;
      sPos[i*3+2] = (Math.random() - 0.5) * 0.4;
      sSeed[i] = Math.random();
    }
    sGeo.setAttribute('position', new THREE.BufferAttribute(sPos, 3));
    sGeo.setAttribute('aSeed', new THREE.BufferAttribute(sSeed, 1));

    const sMat = new THREE.ShaderMaterial({
      uniforms: {
        uTime: { value: 0 },
        uColorA: { value: new THREE.Color(0xc4b5fd) },
        uColorB: { value: new THREE.Color(0xa8e6ff) },
      },
      vertexShader: `
        attribute float aSeed;
        uniform float uTime;
        varying float vSeed;
        varying float vR;
        void main() {
          vSeed = aSeed;
          vec3 p = position;
          // pull toward center (singularity), then respawn at outer when crosses pupil
          float r0 = length(p.xy);
          float speed = 0.35 + aSeed * 0.4;
          float phase = mod(uTime * speed + aSeed * 6.28, 6.28);
          // travel inward in 0..2 timeline, holding outer when phase > some threshold
          float life = mod(phase, 6.28) / 6.28;
          float r = mix(r0, 0.55, smoothstep(0.0, 1.0, life));
          float ang = atan(p.y, p.x) + life * 1.8 * (aSeed - 0.5);
          vec3 np = vec3(cos(ang) * r, sin(ang) * r, p.z * (1.0 - life));
          vR = r;
          vec4 mv = modelViewMatrix * vec4(np, 1.0);
          gl_Position = projectionMatrix * mv;
          gl_PointSize = (1.5 + aSeed * 2.0) * (300.0 / -mv.z) * (1.0 - life * 0.6);
        }
      `,
      fragmentShader: `
        uniform vec3 uColorA;
        uniform vec3 uColorB;
        varying float vSeed;
        varying float vR;
        void main() {
          vec2 c = gl_PointCoord - 0.5;
          float d = length(c);
          if (d > 0.5) discard;
          float alpha = smoothstep(0.5, 0.0, d) * 0.7;
          vec3 col = mix(uColorA, uColorB, vSeed);
          // brighten near pupil edge
          float glow = smoothstep(1.2, 0.6, vR);
          col += glow * 0.5;
          gl_FragColor = vec4(col, alpha);
        }
      `,
      transparent: true,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
    });
    const streams = new THREE.Points(sGeo, sMat);
    group.add(streams);

    // ============================================================
    // LAYER 5: Distant star field
    // ============================================================
    const starGeo = new THREE.BufferGeometry();
    const STAR_COUNT = 400;
    const starPos = new Float32Array(STAR_COUNT * 3);
    for (let i = 0; i < STAR_COUNT; i++) {
      const r = 6 + Math.random() * 6;
      const theta = Math.random() * Math.PI * 2;
      const phi = Math.acos(2 * Math.random() - 1);
      starPos[i*3]   = r * Math.sin(phi) * Math.cos(theta);
      starPos[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
      starPos[i*3+2] = r * Math.cos(phi);
    }
    starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
    const starMat = new THREE.PointsMaterial({
      color: 0xffffff, size: 0.02, transparent: true, opacity: 0.45,
      blending: THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true,
    });
    const stars = new THREE.Points(starGeo, starMat);
    scene.add(stars);

    function applyPalette(palette) {
      key.color.setHex(palette.a);
      fill.color.setHex(palette.b);
      tesseractMat.color.setHex(palette.a);
      irisGroup.children.forEach((line, i) => {
        const c = i % 2 === 0 ? palette.a : palette.b;
        line.material.color.setHex(c);
      });
      outerRingMat.color.setHex(palette.a);
      sMat.uniforms.uColorA.value.setHex(palette.a);
      sMat.uniforms.uColorB.value.setHex(palette.b);
      if (envTex) envTex.dispose();
      envTex = makeEnvTexture(palette);
      scene.environment = envTex;
    }

    let state = { paletteName: 'iridescent', autoRotate: true };
    applyPalette(PALETTES[state.paletteName]);

    // Interaction
    const target = { rx: 0, ry: 0, scroll: 0 };
    const current = { rx: 0, ry: 0, scroll: 0 };
    let mouseX = 0, mouseY = 0;
    let dragging = false;
    let dragStart = { x: 0, y: 0 };
    let dragRot = { x: 0, y: 0 };

    window.addEventListener('mousemove', (e) => {
      if (!dragging) {
        mouseX = (e.clientX / window.innerWidth - 0.5);
        mouseY = (e.clientY / window.innerHeight - 0.5);
      }
    });
    canvas.addEventListener('pointerdown', (e) => {
      dragging = true; canvas.classList.add('dragging');
      canvas.setPointerCapture(e.pointerId);
      dragStart.x = e.clientX; dragStart.y = e.clientY;
      dragRot.x = target.rx; dragRot.y = target.ry;
    });
    canvas.addEventListener('pointermove', (e) => {
      if (!dragging) return;
      target.ry = dragRot.y + (e.clientX - dragStart.x) / 100;
      target.rx = dragRot.x + (e.clientY - dragStart.y) / 100;
    });
    const endDrag = (e) => {
      if (!dragging) return;
      dragging = false; canvas.classList.remove('dragging');
      try { canvas.releasePointerCapture(e.pointerId); } catch (err) {}
    };
    canvas.addEventListener('pointerup', endDrag);
    canvas.addEventListener('pointercancel', endDrag);

    window.addEventListener('scroll', () => { target.scroll = window.scrollY; }, { passive: true });
    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });

    let t = 0;
    let rafId = null;
    function loop() {
      t += 0.005;
      current.rx += (target.rx - current.rx) * 0.05;
      current.ry += (target.ry - current.ry) * 0.05;
      current.scroll += (target.scroll - current.scroll) * 0.08;

      sMat.uniforms.uTime.value = t;

      // Update tesseract
      project4D(t);

      const auto = state.autoRotate ? t * 0.18 : 0;
      group.rotation.x = current.rx + mouseY * 0.25 + Math.sin(t * 0.5) * 0.02;
      group.rotation.y = current.ry + auto + mouseX * 0.25;

      // Iris breathing
      strands.forEach((s, i) => {
        const breath = 1 + Math.sin(t * 0.8 + i * 0.1) * 0.04;
        s.line.scale.setScalar(breath);
        s.line.material.opacity = 0.25 + Math.sin(t * 1.5 + i * 0.2) * 0.2;
      });
      irisGroup.rotation.z = t * 0.05;

      // Pupil pulse
      const pulse = 1 + Math.sin(t * 1.5) * 0.04;
      pupil.scale.setScalar(pulse);
      rim.scale.setScalar(pulse);
      rimMat.opacity = 0.85 + Math.sin(t * 2) * 0.1;

      // Outer ring slow spin
      outerRing.rotation.z = t * 0.08;
      outerRingMat.opacity = 0.2 + Math.sin(t * 0.6) * 0.1;

      // Star slow drift
      stars.rotation.y = t * 0.02;

      const scrollNorm = Math.min(current.scroll / window.innerHeight, 1.5);
      const scale = 0.85 - scrollNorm * 0.15;
      group.scale.setScalar(scale);
      group.position.y = 0.5 - current.scroll * 0.001 + Math.sin(t * 0.6) * 0.03;

      camera.position.x = mouseX * 0.4;
      camera.position.y = -mouseY * 0.25;
      camera.lookAt(0, group.position.y, 0);

      renderer.render(scene, camera);
      rafId = requestAnimationFrame(loop);
    }
    loop();

    return {
      setVariant() {},
      setPalette(p) {
        if (!PALETTES[p]) return;
        state.paletteName = p;
        applyPalette(PALETTES[p]);
      },
      setAutoRotate(v) { state.autoRotate = !!v; },
      destroy() { if (rafId) cancelAnimationFrame(rafId); renderer.dispose(); },
      PALETTES,
    };
  }

  window.HeroScene = { create: createScene, PALETTES };
})();
