// Ranger — Three.js Globe component
// Light-aesthetic globe, soft atmosphere, orbit drag, animated pins.

const { useEffect, useRef } = React;

function RangerGlobe({ size = 560, onPinClick, cameraTarget, autoRotate = true }) {
  const hostRef = useRef(null);
  const apiRef = useRef(null);

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

    const host = hostRef.current;
    const width = host.clientWidth;
    const height = host.clientHeight;

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(32, 1, 0.1, 100);
    camera.position.set(0, 0, 5.2);

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

    // Earth sphere — real NASA blue marble texture
    const R = 1.6;
    const globeGeo = new THREE.SphereGeometry(R, 96, 96);
    const texLoader = new THREE.TextureLoader();
    texLoader.setCrossOrigin('anonymous');
    const earthTexture = texLoader.load('https://unpkg.com/three-globe@2.31.0/example/img/earth-blue-marble.jpg');
    earthTexture.colorSpace = THREE.SRGBColorSpace;
    const globeMat = new THREE.MeshBasicMaterial({ map: earthTexture });
    const globe = new THREE.Mesh(globeGeo, globeMat);
    scene.add(globe);

    // Atmosphere
    const atmoGeo = new THREE.SphereGeometry(R * 1.08, 64, 64);
    const atmoMat = new THREE.ShaderMaterial({
      transparent: true,
      side: THREE.BackSide,
      blending: THREE.AdditiveBlending,
      uniforms: {},
      vertexShader: `
        varying vec3 vN;
        void main(){
          vN = normalize(normalMatrix * normal);
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        varying vec3 vN;
        void main(){
          float intensity = pow(0.78 - dot(vN, vec3(0,0,1.0)), 2.4);
          vec3 c = vec3(0.45, 0.72, 0.85) * intensity;
          gl_FragColor = vec4(c, intensity * 0.85);
        }
      `
    });
    const atmo = new THREE.Mesh(atmoGeo, atmoMat);
    scene.add(atmo);

    // Pins
    const pinsGroup = new THREE.Group();
    globe.add(pinsGroup);

    const latLngToVec3 = (lat, lng, r) => {
      const phi = (90 - lat) * Math.PI / 180;
      const theta = (lng + 180) * Math.PI / 180;
      return new THREE.Vector3(
        -r * Math.sin(phi) * Math.cos(theta),
         r * Math.cos(phi),
         r * Math.sin(phi) * Math.sin(theta)
      );
    };

    const wildlands = window.WILDLANDS;
    const pinMeshes = [];
    wildlands.forEach((w, i) => {
      const pos = latLngToVec3(w.coords.lat, w.coords.lng, R * 1.005);
      const group = new THREE.Group();
      group.position.copy(pos);
      group.lookAt(0,0,0);
      group.rotateX(Math.PI);

      // Dot — sun accent, larger (no stem)
      const SUN = new THREE.Color(0xFDC33A);
      const dotGeo = new THREE.SphereGeometry(0.05, 20, 20);
      const dotMat = new THREE.MeshBasicMaterial({ color: SUN });
      const dot = new THREE.Mesh(dotGeo, dotMat);
      dot.position.y = 0.025;
      group.add(dot);

      // Soft inner halo (constant glow around the dot)
      const haloGeo = new THREE.RingGeometry(0.06, 0.085, 40);
      const haloMat = new THREE.MeshBasicMaterial({ color: SUN, transparent: true, opacity: 0.35, side: THREE.DoubleSide });
      const halo = new THREE.Mesh(haloGeo, haloMat);
      halo.position.y = 0.025;
      halo.rotation.x = Math.PI / 2;
      group.add(halo);

      // Ring pulse — radiates outward
      const ringGeo = new THREE.RingGeometry(0.05, 0.058, 40);
      const ringMat = new THREE.MeshBasicMaterial({ color: SUN, transparent: true, opacity: 0.7, side: THREE.DoubleSide });
      const ring = new THREE.Mesh(ringGeo, ringMat);
      ring.position.y = 0.025;
      ring.rotation.x = Math.PI / 2;
      group.add(ring);

      group.userData = { wildland: w, ring, dot, halo, basePos: pos.clone(), index: i };
      pinsGroup.add(group);
      pinMeshes.push(group);
    });

    // Lights (non-shader meshes don't need them but atmo shader is self-lit)

    // Interaction
    let isDragging = false;
    let lastX = 0, lastY = 0;
    let velX = 0, velY = 0;
    let targetRotY = 0.5;
    let targetRotX = -0.2;
    let curRotY = 0.5, curRotX = -0.2;

    const onDown = (e) => {
      isDragging = true;
      const p = (e.touches && e.touches[0]) || e;
      lastX = p.clientX; lastY = p.clientY;
      host.style.cursor = 'grabbing';
    };
    const onMove = (e) => {
      if (!isDragging) return;
      const p = (e.touches && e.touches[0]) || e;
      const dx = p.clientX - lastX;
      const dy = p.clientY - lastY;
      lastX = p.clientX; lastY = p.clientY;
      targetRotY += dx * 0.005;
      targetRotX += dy * 0.005;
      targetRotX = Math.max(-1.1, Math.min(1.1, targetRotX));
      velX = dx * 0.005;
      velY = dy * 0.005;
    };
    const onUp = () => {
      isDragging = false;
      host.style.cursor = 'grab';
    };

    host.addEventListener('mousedown', onDown);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    host.addEventListener('touchstart', onDown, { passive: true });
    window.addEventListener('touchmove', onMove, { passive: true });
    window.addEventListener('touchend', onUp);
    host.style.cursor = 'grab';

    // API to fly to a wildland
    apiRef.current = {
      flyTo: (target, duration = 1800) => {
        let lat, lng;
        if (typeof target === 'string') {
          const w = wildlands.find(x => x.id === target);
          if (!w) return;
          lat = w.coords.lat; lng = w.coords.lng;
        } else if (target && typeof target === 'object') {
          lat = target.lat; lng = target.lng;
        } else { return; }
        if (lat == null || lng == null) return;
        const targetY = -(lng * Math.PI / 180) - Math.PI / 2;
        const targetX = (lat * Math.PI / 180);
        // Normalize
        const wrap = (a, base) => {
          let diff = a - base;
          while (diff >  Math.PI) diff -= 2*Math.PI;
          while (diff < -Math.PI) diff += 2*Math.PI;
          return base + diff;
        };
        targetRotY = wrap(targetY, curRotY);
        targetRotX = targetX * 0.6;
      },
      setAutoRotate: (v) => { autoFlag.value = v; }
    };

    const autoFlag = { value: autoRotate };

    // Animation loop
    let raf;
    let t0 = performance.now();
    const loop = () => {
      const now = performance.now();
      const dt = (now - t0) / 1000;
      t0 = now;

      if (!isDragging && autoFlag.value) {
        targetRotY += dt * 0.06;
      }

      // Damping
      curRotY += (targetRotY - curRotY) * 0.06;
      curRotX += (targetRotX - curRotX) * 0.06;
      globe.rotation.y = curRotY;
      globe.rotation.x = curRotX;

      // Pulse rings + breathing dots
      const pulse = (now % 2600) / 2600;
      const breath = (Math.sin(now * 0.0028) + 1) / 2; // 0..1
      pinMeshes.forEach((g, i) => {
        const local = (pulse + i * 0.12) % 1;
        // Outward radiating ring
        g.userData.ring.scale.setScalar(1 + local * 2.6);
        g.userData.ring.material.opacity = (1 - local) * 0.7;
        // Breathing dot — gentle scale + opacity oscillation
        const phase = (Math.sin(now * 0.0028 + i * 0.7) + 1) / 2;
        const s = 0.85 + phase * 0.45;
        g.userData.dot.scale.setScalar(s);
        // Halo breathes counter-phase for depth
        const haloPhase = (Math.sin(now * 0.0028 + i * 0.7 + Math.PI) + 1) / 2;
        g.userData.halo.scale.setScalar(0.9 + haloPhase * 0.5);
        g.userData.halo.material.opacity = 0.2 + haloPhase * 0.3;
      });

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

    // Resize
    const ro = new ResizeObserver(() => {
      const w = host.clientWidth;
      const h = host.clientHeight;
      renderer.setSize(w, h);
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
    });
    ro.observe(host);

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      host.removeEventListener('mousedown', onDown);
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
      host.removeEventListener('touchstart', onDown);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('touchend', onUp);
      host.removeChild(renderer.domElement);
      renderer.dispose();
      globeGeo.dispose();
      globeMat.dispose();
      atmoGeo.dispose();
      atmoMat.dispose();
    };
  }, []);

  // React to cameraTarget prop (for flythrough)
  useEffect(() => {
    if (cameraTarget && apiRef.current) {
      apiRef.current.flyTo(cameraTarget);
    }
  }, [cameraTarget]);

  useEffect(() => {
    if (apiRef.current) apiRef.current.setAutoRotate(autoRotate);
  }, [autoRotate]);

  return (
    <div
      ref={hostRef}
      className="globe-canvas"
      style={{ width: '100%', height: '100%', position: 'relative' }}
      aria-label="Interactive 3D globe of Ranger Wildlands"
      role="img"
    />
  );
}

window.RangerGlobe = RangerGlobe;
