import { useRef, useState } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { useXR } from '@react-three/xr';
import * as THREE from 'three';
import { getAllModelLocalGroups } from '@/lib/modelTransforms';
import { useModelStore } from '@/stores/useModelStore';
import { toast } from 'sonner';

/**
 * Mapeamento de Locomoção por Teletransporte Parabólico via Controle XR:
 * 
 * - Incline e segure o Thumbstick (analógico) de qualquer controle para frente.
 *   Um arco parabólico surgirá projetado a partir da mira física do controle.
 * - Ao soltar o direcional, você se teletransporta para o local.
 * - Para entrar em escala real dentro do modelo, aponte diretamente para ele. A escala 
 *   será automaticamente definida para 1:1 ao pousar.
 */
interface Props {
  /** Ref para o <group> que envolve <XROrigin/>; sua pose move o usuário. */
  rigRef: React.RefObject<THREE.Group>;
}

const DEAD = 0.7;
const RELEASE = 0.3;

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

  const fwdPushed = useRef(false);
  const raycaster = useRef(new THREE.Raycaster());
  
  const [arcLine] = useState(() => {
    const geom = new THREE.BufferGeometry();
    const mat = new THREE.LineBasicMaterial({
      color: '#06b6d4',
      transparent: true,
      opacity: 0.8,
    });
    const line = new THREE.Line(geom, mat);
    line.visible = false;
    return line;
  });

  // Refs visuais
  const reticleRef = useRef<THREE.Mesh>(null);

  // Estados temporários do teletransporte
  const lastTeleportTarget = useRef<THREE.Vector3 | null>(null);
  const lastTeleportHitModel = useRef<boolean>(false);

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

    const referenceSpace = gl.xr.getReferenceSpace();
    if (!referenceSpace) return;

    let activeSource: XRInputSource | null = null;
    let stickY = 0;

    // Procura qualquer controle com analógico inclinado para a frente
    for (const src of session.inputSources) {
      const gp = src.gamepad;
      if (gp && gp.axes.length >= 4) {
        const sy = gp.axes[3] ?? 0; // Eixo Y vertical do joystick
        if (sy < -DEAD) {
          activeSource = src;
          stickY = sy;
          break;
        }
      }
    }

    // Se encontramos um controle ativo mirando/inclinado para frente
    if (activeSource && stickY < -DEAD) {
      fwdPushed.current = true;

      const origin = new THREE.Vector3();
      const dir = new THREE.Vector3();

      // Obtém pose do controle usando o targetRaySpace (mira física do controle)
      const ctrlPose = frame.getPose(activeSource.targetRaySpace, referenceSpace);
      if (ctrlPose) {
        const m = new THREE.Matrix4().fromArray(ctrlPose.transform.matrix);
        // Aplica a matriz de mundo do rig para alinhar a origem física com a locomoção do rig
        m.premultiply(rig.matrixWorld);
        origin.setFromMatrixPosition(m);
        const q = new THREE.Quaternion().setFromRotationMatrix(m);
        dir.set(0, 0, -1).applyQuaternion(q).normalize();
      } else {
        // Fallback para direção do olhar se pose falhar
        camera.getWorldPosition(origin);
        camera.getWorldDirection(dir);
      }

      // Parâmetros da simulação do arco parabólico
      const points: THREE.Vector3[] = [];
      const currentPos = origin.clone();
      const velocity = dir.clone().multiplyScalar(7.5); // velocidade do tiro do arco
      const gravity = new THREE.Vector3(0, -9.8, 0);
      const dt = 0.025; // passo da física
      let hitPoint: THREE.Vector3 | null = null;
      let hitModel = false;
      const modelsObjects = getAllModelLocalGroups();

      points.push(currentPos.clone());

      // Executa a trajetória balística
      for (let i = 0; i < 35; i++) {
        const nextPos = currentPos.clone().addScaledVector(velocity, dt);
        velocity.addScaledVector(gravity, dt);

        const segDir = new THREE.Vector3().subVectors(nextPos, currentPos);
        const segLen = segDir.length();

        if (segLen > 0.001) {
          segDir.normalize();
          raycaster.current.set(currentPos, segDir);
          raycaster.current.far = segLen;

          // 1. Verifica colisão com modelos 3D
          const hits = raycaster.current.intersectObjects(modelsObjects, true);
          const firstHit = hits.find(
            (h) => h.object instanceof THREE.Mesh && !h.object.userData.__edgeLine
          );

          if (firstHit) {
            hitPoint = firstHit.point.clone();
            hitModel = true;
            points.push(hitPoint);
            break;
          }

          // 2. Verifica colisão com o chão/grid de calibração plano infinito em Y = gridY
          const gridY = useModelStore.getState().gridY;
          if (
            (currentPos.y >= gridY && nextPos.y <= gridY) ||
            (currentPos.y <= gridY && nextPos.y >= gridY)
          ) {
            const t = (gridY - currentPos.y) / (nextPos.y - currentPos.y);
            hitPoint = new THREE.Vector3().lerpVectors(currentPos, nextPos, t);
            points.push(hitPoint);
            break;
          }
        }

        currentPos.copy(nextPos);
        points.push(currentPos.clone());
      }

      const finalTarget = hitPoint ?? points[points.length - 1];
      lastTeleportTarget.current = finalTarget.clone();
      lastTeleportHitModel.current = hitModel;

      // Atualiza a geometria do arco
      arcLine.geometry.setFromPoints(points);
      arcLine.geometry.attributes.position.needsUpdate = true;
      arcLine.geometry.computeBoundingSphere();
      arcLine.visible = true;

      // Atualiza o retículo
      if (reticleRef.current) {
        const localTarget = finalTarget.clone();
        rig.worldToLocal(localTarget);
        reticleRef.current.position.copy(localTarget);
        reticleRef.current.visible = true;

        // Visual premium: Rosa/Fúcsia para escala real (modelo), Ciano para o grid/chão
        const mat = reticleRef.current.material as THREE.MeshBasicMaterial;
        if (hitModel) {
          mat.color.set('#d946ef');
          reticleRef.current.scale.setScalar(1.2);
        } else {
          mat.color.set('#06b6d4');
          reticleRef.current.scale.setScalar(1.0);
        }
      }
    } else {
      // Quando soltar o analógico
      if (fwdPushed.current) {
        fwdPushed.current = false;

        const targetWorld = lastTeleportTarget.current;
        const hitModel = lastTeleportHitModel.current;

        if (targetWorld) {
          const camWorld = new THREE.Vector3();
          camera.getWorldPosition(camWorld);

          const delta = new THREE.Vector3().subVectors(targetWorld, camWorld);

          if (hitModel) {
            // "Para entrar em escala real dentro do modelo, aponte para ele"
            useModelStore.getState().resetScale();
            // Ajusta o rig para que o pé fique no Y do modelo, mantendo a altura da câmera
            rig.position.y = targetWorld.y;
            delta.y = 0;
            toast.success("Entrando na peça em Escala Real (1:1)!");
          } else {
            delta.y = 0; // preserva a altura vertical do usuário
          }

          rig.position.add(delta);
        }
      }

      // Oculta feixe e retículo
      arcLine.visible = false;
      if (reticleRef.current) reticleRef.current.visible = false;
    }
  });

  return (
    <>
      {/* Arco parabólico premium */}
      <primitive object={arcLine} />

      {/* Retículo de pouso */}
      <mesh ref={reticleRef} visible={false} rotation={[-Math.PI / 2, 0, 0]}>
        <ringGeometry args={[0.07, 0.09, 32]} />
        <meshBasicMaterial color="#06b6d4" transparent opacity={0.85} side={THREE.DoubleSide} depthWrite={false} />
      </mesh>
    </>
  );
}
