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:
 *   • Joystick para frente (axes[1] < -0.7) e soltar (volta a 0)
 *       → teletransporta para o ponto apontado pelo controle direito
 *         (raycast contra `scene.children`). Sem alvo → passo de 0.5 m
 *         na direção do controle.
 *   • Joystick para trás  (axes[1] > 0.7) → passo de 0.3 m para trás
 *         (em relação à orientação do XROrigin / yaw da câmera).
 *   • Joystick lateral   (|axes[0]| > 0.7) → snap-rotate ±30° em torno
 *         do eixo Y do XROrigin.
 *
 * Lê apenas o thumbstick (axes[2]/axes[3] em xr-standard) — não toca em
 * grip nem trigger.
 */
interface Props {
  /** Ref para o <group> que envolve <XROrigin/>; sua posição/rotação move o usuário. */
  rigRef: RefObject<THREE.Group>;
}

const DEAD = 0.7;
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);

  // Edge states per direction (need to release to fire again)
  const fwdArmed = useRef(false);
  const fwdLatched = useRef(false); // armed once axis exceeded threshold; fires on release
  const backLatched = useRef(false);
  const rotLatched = useRef<0 | -1 | 1>(0);

  const raycaster = useRef(new THREE.Raycaster());

  useFrame((_state, _dt, frame: XRFrame | undefined) => {
    const rig = rigRef.current;
    if (!rig || !session || !frame) return;

    const refSpace = (camera as unknown as { matrixWorldInverse: THREE.Matrix4 }) && (frame as XRFrame).session
      ? (frame as XRFrame).session
      : null;
    if (!refSpace) return;

    // Find thumbstick + right controller pose
    let stickX = 0;
    let stickY = 0;
    let rightSource: XRInputSource | null = null;
    for (const src of session.inputSources) {
      const gp = src.gamepad;
      if (!gp) continue;
      // xr-standard: axes[2]=thumbstick X, axes[3]=thumbstick Y
      if (gp.axes.length >= 4) {
        // Prefer left for locomotion; if no left stick, use right
        if (src.handedness === 'left') {
          stickX = gp.axes[2] ?? 0;
          stickY = gp.axes[3] ?? 0;
        } else if (src.handedness === 'right' && stickX === 0 && stickY === 0) {
          stickX = gp.axes[2] ?? 0;
          stickY = gp.axes[3] ?? 0;
        }
      }
      if (src.handedness === 'right') rightSource = src;
    }

    // ── 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) < 0.3) {
      rotLatched.current = 0;
    }

    // ── Back step ──────────────────────────────────────────────────
    if (stickY > DEAD) {
      if (!backLatched.current) {
        backLatched.current = true;
        // Move oposto à direção em que o usuário olha (xz)
        const headDir = new THREE.Vector3();
        camera.getWorldDirection(headDir);
        headDir.y = 0; headDir.normalize();
        rig.position.addScaledVector(headDir, -STEP_BACK);
      }
    } else if (stickY < 0.3) {
      backLatched.current = false;
    }

    // ── Forward → teleport (arm on push, fire on release) ─────────
    if (stickY < -DEAD) {
      fwdArmed.current = true;
      fwdLatched.current = true;
    } else if (fwdLatched.current && stickY > -0.2) {
      fwdLatched.current = false;
      // Fire teleport
      const refSpaceObj = (frame.session as XRSession).requestReferenceSpace; // not used; we use camera world
      void refSpaceObj;
      let targetWorld: THREE.Vector3 | null = null;
      if (rightSource && rightSource.targetRaySpace) {
        const pose = frame.getPose(rightSource.targetRaySpace, (camera as unknown as { parent: THREE.Object3D }).parent
          ? (frame as XRFrame).session as unknown as XRReferenceSpace
          : null as unknown as XRReferenceSpace);
        // Fallback path: build ray from controller via WebXR reference
        void pose;
      }
      // Simpler & robust: get controller pose from its object via three's renderer
      const gl = (rigRef.current as unknown as { __gl?: unknown }).__gl as unknown;
      void gl;

      // We can't get the controller's three.js Object3D here without the XR
      // input wrapper — use the camera as a fallback ray origin: shoot from
      // camera forward until it hits something.
      if (!targetWorld) {
        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;
        });
        if (hit) {
          targetWorld = hit.point.clone();
        } else {
          // No hit: step forward STEP_FWD_NO_HIT m on the floor plane
          targetWorld = origin.clone().add(dir.multiplyScalar(STEP_FWD_NO_HIT));
        }
      }
      if (targetWorld) {
        // Move the rig so the camera ends up at targetWorld (keep camera Y).
        const camWorld = new THREE.Vector3();
        camera.getWorldPosition(camWorld);
        const delta = new THREE.Vector3().subVectors(targetWorld, camWorld);
        delta.y = 0; // keep user height
        rig.position.add(delta);
      }
      fwdArmed.current = false;
    }
  });

  return null;
}
