// ============================================================
// HERO MOUNT — particle animation + live control panel
// ============================================================
import React, { useRef, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import * as THREE from 'three';

const PARTICLE_COUNT = 15000;

const SHAPES = {
  torusKnot: 'Torus Knot',
  sphere:    'Sphere',
  galaxy:    'Galaxy',
  helix:     'Helix',
  ribbon:    'Möbius',
  cube:      'Cube',
};

const MOTIONS = {
  weave:   { label: 'Weave',   rotSpeed: 0.05, swirl: 0.0, pulse: 0.0 },
  cyclone: { label: 'Cyclone', rotSpeed: 0.20, swirl: 0.08, pulse: 0.0 },
  breathe: { label: 'Breathe', rotSpeed: 0.03, swirl: 0.0, pulse: 0.6 },
  drift:   { label: 'Drift',   rotSpeed: 0.01, swirl: 0.0, pulse: 0.1 },
  chaos:   { label: 'Chaos',   rotSpeed: 0.10, swirl: 0.8, pulse: 0.4 },
};

const PALETTES = {
  mint:       { name: 'Mint',     hues: [0.42, 0.45, 0.48], sat: 0.65 },
  aurora:     { name: 'Aurora',   hues: [0.45, 0.55, 0.75], sat: 0.85 },
  ember:      { name: 'Ember',    hues: [0.02, 0.08, 0.13], sat: 0.95 },
  oceanic:    { name: 'Oceanic',  hues: [0.50, 0.58, 0.65], sat: 0.80 },
  monochrome: { name: 'Mono',     hues: [0.0,  0.0,  0.0],  sat: 0.0  },
  prism:      { name: 'Prism',    hues: [0.0,  0.33, 0.66], sat: 0.90 },
  rose:       { name: 'Rose',     hues: [0.95, 0.05, 0.10], sat: 0.70 },
};

// ---- shape generators ----
function generateShape(type, count) {
  const positions = new Float32Array(count * 3);

  if (type === 'torusKnot') {
    const geo = new THREE.TorusKnotGeometry(1.5, 0.5, 200, 32);
    const verts = geo.attributes.position.count;
    for (let i = 0; i < count; i++) {
      const v = i % verts;
      positions[i*3]   = geo.attributes.position.getX(v);
      positions[i*3+1] = geo.attributes.position.getY(v);
      positions[i*3+2] = geo.attributes.position.getZ(v);
    }
    geo.dispose();
  }
  else if (type === 'sphere') {
    for (let i = 0; i < count; i++) {
      const phi = Math.acos(1 - 2 * (i + 0.5) / count);
      const theta = Math.PI * (1 + Math.sqrt(5)) * i;
      const r = 2.0 + (Math.random() - 0.5) * 0.15;
      positions[i*3]   = r * Math.sin(phi) * Math.cos(theta);
      positions[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
      positions[i*3+2] = r * Math.cos(phi);
    }
  }
  else if (type === 'galaxy') {
    const arms = 4;
    for (let i = 0; i < count; i++) {
      const t = i / count;
      const radius = Math.pow(t, 0.6) * 3.0;
      const arm = (i % arms) / arms;
      const angle = arm * Math.PI * 2 + radius * 1.8;
      const spread = (1 - t) * 0.3 + 0.05;
      positions[i*3]   = Math.cos(angle) * radius + (Math.random() - 0.5) * spread;
      positions[i*3+1] = (Math.random() - 0.5) * spread * 0.5;
      positions[i*3+2] = Math.sin(angle) * radius + (Math.random() - 0.5) * spread;
    }
  }
  else if (type === 'helix') {
    const turns = 6;
    for (let i = 0; i < count; i++) {
      const t = (i / count) * Math.PI * 2 * turns;
      const y = (i / count - 0.5) * 5;
      const strand = i % 2 === 0 ? 1 : -1;
      positions[i*3]   = Math.cos(t) * strand + (Math.random() - 0.5) * 0.05;
      positions[i*3+1] = y;
      positions[i*3+2] = Math.sin(t) * strand + (Math.random() - 0.5) * 0.05;
    }
  }
  else if (type === 'ribbon') {
    for (let i = 0; i < count; i++) {
      const u = (i / count) * Math.PI * 2;
      const v = (Math.random() - 0.5) * 1.0;
      const r = 1.8;
      positions[i*3]   = (r + v * Math.cos(u/2)) * Math.cos(u);
      positions[i*3+1] = (r + v * Math.cos(u/2)) * Math.sin(u);
      positions[i*3+2] = v * Math.sin(u/2);
    }
  }
  else if (type === 'cube') {
    const side = 2.2;
    const edges = [
      [[-1,-1,-1],[ 1,-1,-1]],[[ 1,-1,-1],[ 1, 1,-1]],[[ 1, 1,-1],[-1, 1,-1]],[[-1, 1,-1],[-1,-1,-1]],
      [[-1,-1, 1],[ 1,-1, 1]],[[ 1,-1, 1],[ 1, 1, 1]],[[ 1, 1, 1],[-1, 1, 1]],[[-1, 1, 1],[-1,-1, 1]],
      [[-1,-1,-1],[-1,-1, 1]],[[ 1,-1,-1],[ 1,-1, 1]],[[ 1, 1,-1],[ 1, 1, 1]],[[-1, 1,-1],[-1, 1, 1]],
    ].map(e => e.map(p => p.map(c => c * side / 2)));
    for (let i = 0; i < count; i++) {
      const e = edges[Math.floor(Math.random() * 12)];
      const t = Math.random();
      positions[i*3]   = e[0][0] + (e[1][0]-e[0][0])*t + (Math.random()-0.5)*0.04;
      positions[i*3+1] = e[0][1] + (e[1][1]-e[0][1])*t + (Math.random()-0.5)*0.04;
      positions[i*3+2] = e[0][2] + (e[1][2]-e[0][2])*t + (Math.random()-0.5)*0.04;
    }
  }
  return positions;
}

// ============================================================
// CANVAS
// ============================================================
function WovenCanvas({ shape, palette, motion, particleSize, mouseForce }) {
  const mountRef = useRef(null);
  const stateRef = useRef({});
  const motionRef = useRef(motion);
  const sizeRef = useRef(particleSize);
  const forceRef = useRef(mouseForce);
  useEffect(() => { motionRef.current = motion; }, [motion]);
  useEffect(() => { sizeRef.current = particleSize; }, [particleSize]);
  useEffect(() => { forceRef.current = mouseForce; }, [mouseForce]);

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

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(90, mount.clientWidth / mount.clientHeight, 0.1, 1000);
    camera.position.z = 7.5;

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(mount.clientWidth, mount.clientHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    mount.appendChild(renderer.domElement);

    const positions         = new Float32Array(PARTICLE_COUNT * 3);
    const originalPositions = new Float32Array(PARTICLE_COUNT * 3);
    const velocities        = new Float32Array(PARTICLE_COUNT * 3);
    const colors            = new Float32Array(PARTICLE_COUNT * 3);

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.setAttribute('color',    new THREE.BufferAttribute(colors, 3));

    const material = new THREE.PointsMaterial({
      size: sizeRef.current,
      vertexColors: true,
      transparent: true,
      opacity: 0.85,
      depthWrite: false,
    });

    const points = new THREE.Points(geometry, material);
    points.scale.setScalar(2.4);
    // On desktop the sphere sits to the right of the hero copy (offset +4 in
    // world space). On mobile the visual stacks below the copy, so center it.
    const updateSpherePlacement = () => {
      points.position.x = window.innerWidth <= 720 ? 0 : 4.0;
    };
    updateSpherePlacement();
    scene.add(points);

    const mouse = new THREE.Vector2(0, 0);
    const clock = new THREE.Clock();

    const onMouseMove = (e) => {
      const rect = mount.getBoundingClientRect();
      mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
    };
    window.addEventListener('mousemove', onMouseMove);

    const onResize = () => {
      if (!mount.clientWidth || !mount.clientHeight) return;
      camera.aspect = mount.clientWidth / mount.clientHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(mount.clientWidth, mount.clientHeight);
      updateSpherePlacement();
    };
    window.addEventListener('resize', onResize);

    stateRef.current = {
      scene, camera, renderer, points, geometry, material,
      positions, originalPositions, velocities, colors, mount,
    };

    const tmpPos = new THREE.Vector3();
    const tmpOrig = new THREE.Vector3();
    const tmpDir = new THREE.Vector3();
    const mouseWorld = new THREE.Vector3();

    let frameId;
    const animate = () => {
      frameId = requestAnimationFrame(animate);
      const t = clock.getElapsedTime();
      const m = MOTIONS[motionRef.current];
      const pulse = 1 + Math.sin(t * 1.5) * m.pulse * 0.15;
      mouseWorld.set(mouse.x * 3, mouse.y * 3, 0);

      const force = forceRef.current;

      for (let i = 0; i < PARTICLE_COUNT; i++) {
        const ix = i*3, iy = i*3+1, iz = i*3+2;
        tmpPos.set(positions[ix], positions[iy], positions[iz]);
        tmpOrig.set(originalPositions[ix] * pulse, originalPositions[iy] * pulse, originalPositions[iz] * pulse);

        const dist = tmpPos.distanceTo(mouseWorld);
        if (dist < 1.5) {
          const f = (1.5 - dist) * 0.012 * force;
          tmpDir.subVectors(tmpPos, mouseWorld).normalize().multiplyScalar(f);
          velocities[ix] += tmpDir.x;
          velocities[iy] += tmpDir.y;
          velocities[iz] += tmpDir.z;
        }

        if (m.swirl > 0) {
          const sw = m.swirl * 0.003;
          velocities[ix] += Math.sin(t + originalPositions[iy] * 2) * sw;
          velocities[iy] += Math.cos(t + originalPositions[iz] * 2) * sw;
          velocities[iz] += Math.sin(t + originalPositions[ix] * 2) * sw;
        }

        velocities[ix] += (tmpOrig.x - tmpPos.x) * 0.0015;
        velocities[iy] += (tmpOrig.y - tmpPos.y) * 0.0015;
        velocities[iz] += (tmpOrig.z - tmpPos.z) * 0.0015;

        velocities[ix] *= 0.94;
        velocities[iy] *= 0.94;
        velocities[iz] *= 0.94;

        positions[ix] += velocities[ix];
        positions[iy] += velocities[iy];
        positions[iz] += velocities[iz];
      }

      geometry.attributes.position.needsUpdate = true;
      points.rotation.y = t * m.rotSpeed;
      points.rotation.x = Math.sin(t * m.rotSpeed * 0.5) * 0.2;

      renderer.render(scene, camera);
    };
    animate();

    return () => {
      cancelAnimationFrame(frameId);
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('resize', onResize);
      geometry.dispose();
      material.dispose();
      renderer.dispose();
      if (mount.contains(renderer.domElement)) mount.removeChild(renderer.domElement);
    };
  }, []);

  // ---- shape changes: regenerate target, spring force pulls particles in ----
  useEffect(() => {
    const s = stateRef.current;
    if (!s.originalPositions) return;
    const target = generateShape(shape, PARTICLE_COUNT);
    for (let i = 0; i < target.length; i++) s.originalPositions[i] = target[i];
  }, [shape]);

  // ---- palette changes: rewrite vertex colors ----
  useEffect(() => {
    const s = stateRef.current;
    if (!s.colors) return;
    const p = PALETTES[palette];
    const c = new THREE.Color();
    for (let i = 0; i < PARTICLE_COUNT; i++) {
      const hue = p.hues[i % p.hues.length] + (Math.random() - 0.5) * 0.04;
      const lightness = p.sat === 0 ? 0.3 + Math.random() * 0.4 : 0.25 + Math.random() * 0.25;
      c.setHSL((hue + 1) % 1, p.sat, lightness);
      s.colors[i*3]   = c.r;
      s.colors[i*3+1] = c.g;
      s.colors[i*3+2] = c.b;
    }
    s.geometry.attributes.color.needsUpdate = true;
  }, [palette]);

  // ---- particle size changes: update material ----
  useEffect(() => {
    const s = stateRef.current;
    if (s.material) s.material.size = particleSize;
  }, [particleSize]);

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

// ============================================================
// ROOT — final tuned values
// ============================================================
const container = document.getElementById('hero-canvas');
if (container) {
  createRoot(container).render(
    <WovenCanvas
      shape="sphere"
      motion="cyclone"
      palette="prism"
      particleSize={0.019}
      mouseForce={0.3}
    />
  );
} else {
  console.warn('[hero-mount] #hero-canvas not found');
}
