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

interface XRGrabbableProps {
  /** Allow uniform scaling via two-hand grab (distance between hands) */
  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 = gripWorld^-1 * groupWorld at grab start */
  offset: THREE.Matrix4;
}

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 _tmpMat = new THREE.Matrix4();
const _tmpMat2 = new THREE.Matrix4();
const _tmpVecL = new THREE.Vector3();
const _tmpVecR = new THREE.Vector3();
const _tmpQuat = new THREE.Quaternion();

/**
 * Wraps children in a group whose pose is driven by Touch Plus grip:
 *  • One-hand grab: model becomes an extension of that hand (6DOF).
 *  • Two-hand grab: midpoint = pivot, axis between hands = rotation,
 *    distance ratio = uniform scale (if allowScale).
 *
 * On release the group keeps its last pose (we don't write to the store —
 * the manual fine-tuning sliders remain available as additional offsets).
 */
export function XRGrabbable({ allowScale = false, lockedActive = false, onGrabStart, children }: XRGrabbableProps) {
  const groupRef = useRef<THREE.Group>(null);
  const grab = useControllerGrab();

  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);
    // Abort any in-progress two-hand scaling so it doesn't snap back.
    dual.current = null;
    single.current = null;
  }, [scaleResetNonce]);

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

    // Locked: ignore all grab input — release any in-progress grabs and bail.
    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;

    // ─── Halo intensity (visual feedback for analog grip) ────────
    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;
    }

    // ─── Two-hand mode ───────────────────────────────────────────
    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) {
        // Capture
        group.updateMatrixWorld();
        dual.current = {
          startGroupWorld: group.matrixWorld.clone(),
          startMid: midNow.clone(),
          startAxis: axisNow.clone(),
          startDist: distNow,
          startScale: group.scale.x,
        };
        single.current = null; // dual takes over
        if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
        console.log('[XR][grab] ◆ TWO-HAND start');
      }

      const d = dual.current;
      // Rotation delta: from startAxis to axisNow
      const deltaQuat = new THREE.Quaternion().setFromUnitVectors(d.startAxis, axisNow);
      // Scale ratio
      const scaleRatio = allowScale
        ? THREE.MathUtils.clamp(distNow / d.startDist, 0.1, 10)
        : 1;

      // Build target world: T(midNow) * R(delta) * S(ratio) * T(-startMid) * startGroupWorld
      const m = new THREE.Matrix4();
      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);
      m.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;
    }

    // ─── One-hand mode ───────────────────────────────────────────
    const activeHand: 'left' | 'right' | null = lActive ? 'left' : rActive ? 'right' : null;
    if (activeHand) {
      const slot = activeHand === 'left' ? L : R;
      if (!single.current || single.current.hand !== activeHand) {
        group.updateMatrixWorld();
        const offset = new THREE.Matrix4()
          .copy(slot.gripWorld).invert()
          .multiply(group.matrixWorld);
        single.current = { hand: activeHand, offset };
        if (!everGrabbed.current) { everGrabbed.current = true; onGrabStart?.(); }
        console.log(`[XR][grab] ● ONE-HAND start (${activeHand})`);
      }
      _tmpMat.multiplyMatrices(slot.gripWorld, single.current.offset);
      applyWorldMatrixToLocal(group, _tmpMat);
    } else if (single.current) {
      single.current = null;
    }
  });

  return (
    <group ref={groupRef}>
      {/* Subtle halo proportional to analog grip — invisible by default */}
      <mesh ref={haloRef} visible={false}>
        <sphereGeometry args={[0.5, 16, 12]} />
        <meshBasicMaterial color="#22c55e" transparent opacity={0} depthWrite={false} />
      </mesh>
      {children}
    </group>
  );
}

/** Convert a desired world matrix to the group's local matrix (given its parent). */
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);
}
