import { useRef, RefObject } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { useXR } from '@react-three/xr';
import * as THREE from 'three';

/**
 * Quest 3 thumbstick locomotion (rig-relative).
 *
 * Mapeamento (thumbstick = axes[2]/axes[3] em xr-standard):
 *   • Para frente (axes[1] < -0.7), libera (axis volta a ~0) → teletransporta
 *     para o ponto onde o usuário está olhando (raycast da câmera). Se não
 *     houver geometria atingida, dá um passo de 0.5 m na direção do olhar.
 *   • Para trás  (axes[1] > 0.7) → passo de 0.3 m oposto ao olhar.
 *   • Lateral   (|axes[0]| > 0.7) → snap-rotate ±30° no eixo Y do rig.
 *
 * NÃO lê grip ou trigger — esses são responsabilidade exclusiva de
 * XRGrabbable e XRControllerMeasure, respectivamente.
 */
interface Props {
  /** Ref para o <group> que envolve <XROrigin/>; sua pose move o usuário. */
  rigRef: RefObject<THREE.Group>;
}

const DEAD = 0.7;
const RELEASE = 0.3;
const SNAP_DEG = 30;
const STEP_BACK = 0.3;
const STEP_FWD_NO_HIT = 0.5;

export function ControllerLocomotion({ rigRef }: Props) {
  const { scene, camera } = useThree();
  const session = useXR((s) => s.session);

  const fwdPushed = useRef(false);
  const backLatched = useRef(false);
  const rotLatched = useRef<0 | -1 | 1>(0);
  const raycaster = useRef(new THREE.Raycaster());

  useFrame(() => {
    const rig = rigRef.current;
    if (!rig || !session) return;

    let stickX = 0;
    let stickY = 0;
    for (const src of session.inputSources) {
      const gp = src.gamepad;
      if (!gp || gp.axes.length < 4) continue;
      // Prefer left stick for locomotion
      if (src.handedness === 'left') {
        stickX = gp.axes[2] ?? 0;
        stickY = gp.axes[3] ?? 0;
        break;
      }
      if (src.handedness === 'right' && stickX === 0 && stickY === 0) {
        stickX = gp.axes[2] ?? 0;
        stickY = gp.axes[3] ?? 0;
      }
    }

    // ── Snap-rotate (lateral) ──────────────────────────────────────
    if (Math.abs(stickX) > DEAD) {
      const dir: -1 | 1 = stickX > 0 ? 1 : -1;
      if (rotLatched.current !== dir) {
        rotLatched.current = dir;
        rig.rotation.y -= (dir * SNAP_DEG * Math.PI) / 180;
      }
    } else if (Math.abs(stickX) < RELEASE) {
      rotLatched.current = 0;
    }

    // ── Back step ──────────────────────────────────────────────────
    if (stickY > DEAD) {
      if (!backLatched.current) {
        backLatched.current = true;
        const headDir = new THREE.Vector3();
        camera.getWorldDirection(headDir);
        headDir.y = 0;
        if (headDir.lengthSq() > 1e-6) {
          headDir.normalize();
          rig.position.addScaledVector(headDir, -STEP_BACK);
        }
      }
    } else if (stickY < RELEASE) {
      backLatched.current = false;
    }

    // ── Forward → teleport on release ─────────────────────────────
    if (stickY < -DEAD) {
      fwdPushed.current = true;
    } else if (fwdPushed.current && stickY > -RELEASE) {
      fwdPushed.current = false;
      // Cast from camera forward to find target on real/virtual geometry
      const origin = new THREE.Vector3();
      camera.getWorldPosition(origin);
      const dir = new THREE.Vector3();
      camera.getWorldDirection(dir);
      raycaster.current.set(origin, dir);
      raycaster.current.far = 30;
      const hits = raycaster.current.intersectObjects(scene.children, true);
      const hit = hits.find((h) => {
        const o = h.object;
        if (!(o instanceof THREE.Mesh)) return false;
        if (o.userData.__edgeLine) return false;
        return true;
      });
      const target = hit ? hit.point.clone() : origin.clone().add(dir.multiplyScalar(STEP_FWD_NO_HIT));
      const camWorld = new THREE.Vector3();
      camera.getWorldPosition(camWorld);
      const delta = new THREE.Vector3().subVectors(target, camWorld);
      delta.y = 0; // preserva altura do usuário
      rig.position.add(delta);
    }
  });

  return null;
}
