import { useRef, ReactNode, useEffect } from 'react';
import { useFrame } from '@react-three/fiber';
import { useXR } from '@react-three/xr';
import * as THREE from 'three';
import { useControllerGrab, ControllerGrabSnapshot } from '@/hooks/useControllerGrab';
import { useModelStore } from '@/stores/useModelStore';
import { activeModelGroupRef } from './xrCalibrationBus';

interface XRGrabbableProps {
  /**
   * Quando true (padrão), o grip duplo aplica ZOOM uniforme além da rotação.
   * Quando false, o grip duplo só ORBITA — a escala da peça é preservada.
   */
  allowScale?: boolean;
  /** When true, the active model is locked — grab is ignored entirely */
  lockedActive?: boolean;
  /** Called the first time the user grabs (e.g. to exit placement mode) */
  onGrabStart?: () => void;
  children: ReactNode;
}

interface SingleGrabRecord {
  hand: 'left' | 'right';
  /** Offset (world) = groupWorldPos - controllerWorldPos at grab start. */
  offsetWorld: THREE.Vector3;
  /** Altura Y de mundo da peça no momento em que o grab iniciou */
  startWorldY: number;
}

interface DualGrabRecord {
  /** group^world at grab start of second hand */
  startGroupWorld: THREE.Matrix4;
  /** midpoint between hands at start (world) */
  startMid: THREE.Vector3;
  /** vector from left to right at start (world, normalized) */
  startAxis: THREE.Vector3;
  /** distance between hands at start */
  startDist: number;
  /** scale at start */
  startScale: number;
}

const _tmpMat2 = new THREE.Matrix4();
const _tmpVecL = new THREE.Vector3();
const _tmpVecR = new THREE.Vector3();
const _tmpQuat = new THREE.Quaternion();

/**
 * Mapeamento Geral do Quest 3 para Manipulação do Modelo em AR:
 *
 *  • Panorâmica (Pan) -> Segurar o botão Grip (lateral) em qualquer um dos controles
 *    e mover a mão na direção que deseja arrastar (movimentação livre em 3D).
 *  • Giro (Rotação) e Zoom (Escala) -> Apontar ambos os controles para a bounding box do modelo,
 *    segurar ambos os botões de Grip (L+R) e mover as mãos em direções opostas (giro) ou 
 *    afastar/aproximar as mãos (zoom).
 */
export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabStart, children }: XRGrabbableProps) {
  const groupRef = useRef<THREE.Group>(null);
  const grab = useControllerGrab();
  const session = useXR((s) => s.session);

  const single = useRef<SingleGrabRecord | null>(null);
  const dual = useRef<DualGrabRecord | null>(null);
  const haloRef = useRef<THREE.Mesh>(null);
  const everGrabbed = useRef(false);

  // Reset scale to 1 (position/rotation kept) whenever resetScale() is called.
  const scaleResetNonce = useModelStore((s) => s.scaleResetNonce);
  useEffect(() => {
    const g = groupRef.current;
    if (!g) return;
    g.scale.set(1, 1, 1);
    dual.current = null;
    single.current = null;
  }, [scaleResetNonce]);

  useEffect(() => {
    const currentGroup = groupRef.current;
    activeModelGroupRef.current = currentGroup;
    return () => {
      if (activeModelGroupRef.current === currentGroup) {
        activeModelGroupRef.current = null;
      }
    };
  });

  const syncGrabToStore = () => {
    const group = groupRef.current;
    if (!group) return;

    const state = useModelStore.getState();
    const activeModelId = state.activeModelId;
    if (!activeModelId) return;

    const activeModel = state.models.find(m => m.id === activeModelId);
    if (!activeModel) return;

    const ft = { ...activeModel.fineTuning };
    const renderFactor = state.scaleRatio?.factor ?? 1;

    // Model's matrix relative to XRGrabbable
    const posModel = new THREE.Vector3(ft.posX * renderFactor, ft.posY * renderFactor, ft.posZ * renderFactor);
    const rotModel = new THREE.Euler(
      (ft.rotX * Math.PI) / 180,
      (ft.rotY * Math.PI) / 180,
      (ft.rotZ * Math.PI) / 180,
      'YXZ'
    );
    const quatModel = new THREE.Quaternion().setFromEuler(rotModel);
    const scaleModel = new THREE.Vector3(ft.scale * renderFactor, ft.scale * renderFactor, ft.scale * renderFactor);

    const mModel = new THREE.Matrix4().compose(posModel, quatModel, scaleModel);

    // Grab matrix of XRGrabbable group
    const mGrab = new THREE.Matrix4().compose(group.position, group.quaternion, group.scale);

    // Combined matrix
    const mCombined = new THREE.Matrix4().multiplyMatrices(mGrab, mModel);

    const finalPos = new THREE.Vector3();
    const finalQuat = new THREE.Quaternion();
    const finalScale = new THREE.Vector3();
    mCombined.decompose(finalPos, finalQuat, finalScale);

    // Reset grabbable group to identity so the transform is held entirely by fineTuning
    group.position.set(0, 0, 0);
    group.quaternion.identity();
    group.scale.set(1, 1, 1);

    // Save to store
    const euler = new THREE.Euler().setFromQuaternion(finalQuat, 'YXZ');
    state.setFineTuning({
      posX: finalPos.x / renderFactor,
      posY: finalPos.y / renderFactor,
      posZ: finalPos.z / renderFactor,
      rotX: (euler.x * 180) / Math.PI,
      rotY: (euler.y * 180) / Math.PI,
      rotZ: (euler.z * 180) / Math.PI,
      scale: finalScale.x / renderFactor,
    });
  };

  useFrame((_state, dt) => {
    const group = groupRef.current;
    if (!group) return;
    const snap = grab.current;
    const L = snap.left;
    const R = snap.right;

    if (lockedActive) {
      if (single.current) single.current = null;
      if (dual.current) dual.current = null;
      if (haloRef.current) haloRef.current.visible = false;
      return;
    }

    const lActive = L.isGrabbing && L.hasPose;
    const rActive = R.isGrabbing && R.hasPose;

    // Visual feedback para a pressão analógica do grip (halo)
    if (haloRef.current) {
      const maxGrip = Math.max(L.gripValue, R.gripValue);
      const mat = haloRef.current.material as THREE.MeshBasicMaterial;
      mat.opacity = maxGrip * 0.25;
      haloRef.current.visible = maxGrip > 0.05;
    }

    // ─── Modo de Duas Mãos: Giro (Órbita) + Zoom ────────────────────
    if (lActive && rActive) {
      _tmpVecL.setFromMatrixPosition(L.gripWorld);
      _tmpVecR.setFromMatrixPosition(R.gripWorld);
      const midNow = new THREE.Vector3().addVectors(_tmpVecL, _tmpVecR).multiplyScalar(0.5);
      const axisNow = new THREE.Vector3().subVectors(_tmpVecR, _tmpVecL);
      const distNow = axisNow.length();
      if (distNow < 1e-4) return;
      axisNow.divideScalar(distNow);

      if (!dual.current) {
        group.updateMatrixWorld();

        // Se o usuário já estava segurando a peça com uma das mãos, transita para o modo dual imediatamente.
        // Caso contrário (peça solta), exige que ambos os controles apontem para a bounding box da peça.
        const wasSingleGrabbing = !!single.current;

        const box = new THREE.Box3().setFromObject(group);
        const checkPointerCollision = (slot: typeof L) => {
          const origin = new THREE.Vector3().setFromMatrixPosition(slot.gripWorld);
          const q = new THREE.Quaternion().setFromRotationMatrix(slot.gripWorld);
          const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(q).normalize();
          const ray = new THREE.Ray(origin, dir);
          return ray.intersectsBox(box);
        };

        if (wasSingleGrabbing || (checkPointerCollision(L) && checkPointerCollision(R))) {
          dual.current = {
            startGroupWorld: group.matrixWorld.clone(),
            startMid: midNow.clone(),
            startAxis: axisNow.clone(),
            startDist: distNow,
            startScale: group.scale.x,
          };
          single.current = null;
          if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
          console.log('[XR][grab] ◆ TWO-HAND start (orbit+zoom) - ' + (wasSingleGrabbing ? 'from active single grab' : 'bounding box hit'));
        } else {
          // Se não apontavam para o modelo, ignora e permite o pan de uma mão (da primeira mão a engajar)
          return;
        }
      }

      if (dual.current) {
        const d = dual.current;
        const deltaQuat = new THREE.Quaternion().setFromUnitVectors(d.startAxis, axisNow);
        
        // ZOOM: afasta as mãos para zoom in, aproxima para zoom out
        const scaleRatio = allowScale
          ? THREE.MathUtils.clamp(distNow / d.startDist, 0.1, 10)
          : 1;

        const tToOrigin = new THREE.Matrix4().makeTranslation(-d.startMid.x, -d.startMid.y, -d.startMid.z);
        const tBack = new THREE.Matrix4().makeTranslation(midNow.x, midNow.y, midNow.z);
        const rot = new THREE.Matrix4().makeRotationFromQuaternion(deltaQuat);
        const scl = new THREE.Matrix4().makeScale(scaleRatio, scaleRatio, scaleRatio);
        const m = new THREE.Matrix4().identity()
          .multiply(tBack)
          .multiply(rot)
          .multiply(scl)
          .multiply(tToOrigin)
          .multiply(d.startGroupWorld);

        applyWorldMatrixToLocal(group, m);
        return;
      }
    } else if (dual.current) {
      console.log('[XR][grab] ◆ TWO-HAND end');
      dual.current = null;
      // Se a outra mão ainda estiver segurando, NÃO sincronizamos/resetamos o group local ainda.
      // A transição de duas mãos para uma mão herdará o groupWorldPos correto de forma contínua.
      const activeHand = lActive ? 'left' : rActive ? 'right' : null;
      if (!activeHand) {
        syncGrabToStore();
      }
    }

    // ─── Modo de Uma Mão: Panorâmica Livre (Arrastar em 3D) ────────
    const activeHand: 'left' | 'right' | null = lActive ? 'left' : rActive ? 'right' : null;
    if (activeHand) {
      const slot = activeHand === 'left' ? L : R;
      const ctrlPos = new THREE.Vector3().setFromMatrixPosition(slot.gripWorld);

      if (!single.current || single.current.hand !== activeHand) {
        group.updateMatrixWorld();
        const groupWorldPos = new THREE.Vector3().setFromMatrixPosition(group.matrixWorld);
        const offsetWorld = new THREE.Vector3().subVectors(groupWorldPos, ctrlPos);
        
        single.current = { hand: activeHand, offsetWorld, startWorldY: groupWorldPos.y };
        if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
        console.log(`[XR][grab] ● ONE-HAND start (${activeHand}) — free 3D pan`);
      }

      // Função particular improvisada: rotacionar com o analógico horizontal durante o pan
      const inputSource = session?.inputSources ? Array.from(session.inputSources).find(s => s.handedness === activeHand) : null;
      const gp = inputSource?.gamepad;
      if (gp && gp.axes.length >= 4) {
        const stickX = gp.axes[2]; // thumbstick horizontal
        const deadzone = 0.15;
        if (Math.abs(stickX) > deadzone) {
          const rotationSpeed = 1.5;
          const angle = Math.sign(stickX) * (Math.abs(stickX) - deadzone) * rotationSpeed * dt;
          const q = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
          group.quaternion.multiplyQuaternions(q, group.quaternion);
          
          // Gira o offset em torno da mão
          single.current.offsetWorld.applyQuaternion(q);
        }
      }

      // Calcula a nova posição no espaço de mundo (movimentação 3D livre)
      const targetWorldPos = new THREE.Vector3().addVectors(ctrlPos, single.current.offsetWorld);

      // Converte para coordenadas locais do pai
      if (group.parent) {
        group.parent.updateMatrixWorld();
        const invParent = new THREE.Matrix4().copy(group.parent.matrixWorld).invert();
        targetWorldPos.applyMatrix4(invParent);
      }
      group.position.copy(targetWorldPos);
    } else if (single.current) {
      console.log('[XR][grab] ● ONE-HAND end');
      single.current = null;
      syncGrabToStore();
    }
  });

  return (
    <group ref={groupRef}>
      <mesh ref={haloRef} visible={false}>
        <sphereGeometry args={[0.5, 16, 12]} />
        <meshBasicMaterial color="#22c55e" transparent opacity={0} depthWrite={false} />
      </mesh>
      {children}
    </group>
  );
}

function applyWorldMatrixToLocal(group: THREE.Group, worldMatrix: THREE.Matrix4) {
  if (group.parent) {
    group.parent.updateMatrixWorld();
    _tmpMat2.copy(group.parent.matrixWorld).invert().multiply(worldMatrix);
  } else {
    _tmpMat2.copy(worldMatrix);
  }
  _tmpMat2.decompose(group.position, _tmpQuat, group.scale);
  group.quaternion.copy(_tmpQuat);
}
