import { useRef, useState } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import * as THREE from 'three';
import { useModelStore } from '@/stores/useModelStore';
import { resolveSnap, detectCircularEdgeAtPoint, findNearestEdgeSegment } from './SmartMeasure';

const TRIG_ON = 0.7;
const TRIG_OFF = 0.3;
const BTN_ON = 0.6;
const MAX_RAY = 10; // meters

/**
 * Right-controller trigger driven measurement for AR.
 *
 * Mapping (right hand):
 *   - Trigger  → add measurement point (with snap)
 *   - Button A (gp.buttons[4]) → undo last point/measurement
 *   - Button B (gp.buttons[5]) → clear all measurements
 * Left hand:
 *   - Trigger → toggle vertex snap ON/OFF
 *
 * Renders a thin laser from the right controller to the snap candidate
 * (green if snapped to vertex/edge, amber if surface).
 */
export function XRControllerMeasure() {
  const { scene, gl } = useThree();
  const trigState = useRef(false);
  const aState = useRef(false);
  const bState = useRef(false);
  const lTrigState = useRef(false);
  const [snapEnabled, setSnapEnabled] = useState(true);

  const raycaster = useRef(new THREE.Raycaster());
  const tmpOrigin = useRef(new THREE.Vector3());
  const tmpDir = useRef(new THREE.Vector3());
  const tmpQuat = useRef(new THREE.Quaternion());

  // Visual laser refs
  const laserRef = useRef<THREE.Line>(null);
  const tipRef = useRef<THREE.Mesh>(null);
  const laserGeom = useRef<THREE.BufferGeometry>(
    new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(), new THREE.Vector3(0, 0, -1)])
  );
  const laserMat = useRef(new THREE.LineBasicMaterial({ color: '#22c55e', transparent: true, opacity: 0.7, depthTest: false }));
  const tipColor = useRef(new THREE.Color('#22c55e'));

  // Dwell detection for hover-based smart measurement (1 s)
  const dwellPos = useRef(new THREE.Vector3(Infinity, Infinity, Infinity));
  const dwellStart = useRef(0);
  const dwellFired = useRef(false);

  // Throttling: raycast + snap analysis are heavy on dense IFC models and
  // running them every frame at 72fps can stall the Quest right after
  // toggling "Medir" from the Ferramentas tab. We cap heavy work to ~24Hz.
  // Trigger/A/B/L-trigger polling continues every frame for responsiveness.
  const heavyFrame = useRef(0);
  const cachedSnappedPoint = useRef<THREE.Vector3 | null>(null);
  const cachedSnapKind = useRef<'vertex' | 'edge' | 'surface' | 'hole'>('surface');

  useFrame((_state, _dt, frame: XRFrame | undefined) => {
    const measureMode = useModelStore.getState().measureMode;
    const selectionMode = useModelStore.getState().selectionMode;
    if (laserRef.current) laserRef.current.visible = false;
    if (tipRef.current) tipRef.current.visible = false;
    if ((!measureMode && !selectionMode) || !frame) return;
    const doHeavy = (heavyFrame.current++ % 3) === 0;

    const session = frame.session;
    const refSpace = gl.xr.getReferenceSpace();
    if (!session || !refSpace) return;

    let right: XRInputSource | null = null;
    let left: XRInputSource | null = null;
    for (const src of session.inputSources) {
      if (src.handedness === 'right') right = src;
      else if (src.handedness === 'left') left = src;
    }

    // ── Left trigger: toggle snap ─────────────────────────────────────
    if (left) {
      const v = left.gamepad?.buttons?.[0]?.value ?? (left.gamepad?.buttons?.[0]?.pressed ? 1 : 0);
      if (!lTrigState.current && v > TRIG_ON) {
        lTrigState.current = true;
        setSnapEnabled((s) => !s);
      } else if (lTrigState.current && v < TRIG_OFF) {
        lTrigState.current = false;
      }
    }

    if (!right) return;
    const gp = right.gamepad;

    // ── Compute right-controller ray ──────────────────────────────────
    const raySpace = right.targetRaySpace ?? right.gripSpace;
    if (!raySpace) return;
    const pose = frame.getPose(raySpace, refSpace);
    if (!pose) return;

    const m = new THREE.Matrix4().fromArray(pose.transform.matrix);
    tmpOrigin.current.setFromMatrixPosition(m);
    tmpQuat.current.setFromRotationMatrix(m);
    tmpDir.current.set(0, 0, -1).applyQuaternion(tmpQuat.current).normalize();

    raycaster.current.set(tmpOrigin.current, tmpDir.current);
    raycaster.current.far = MAX_RAY;

    let snappedPoint: THREE.Vector3 | null = null;
    let snapKind: 'vertex' | 'edge' | 'surface' | 'hole' = 'surface';
    let hoverDetected: { kind: 'hole' | 'edge'; value: number; position: THREE.Vector3; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;

    if (doHeavy) {
      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;
        if (o.geometry instanceof THREE.SphereGeometry) return false;
        if (o.geometry instanceof THREE.RingGeometry) return false;
        if (o.geometry instanceof THREE.PlaneGeometry) return false;
        return true;
      });

      if (hit && hit.object instanceof THREE.Mesh) {
        const size = gl.getSize(new THREE.Vector2());
        const canvasSize = { width: size.x || 1024, height: size.y || 1024 };
        const fakeCam = new THREE.PerspectiveCamera(60, 1, 0.01, 100);
        fakeCam.position.copy(tmpOrigin.current);
        fakeCam.quaternion.copy(tmpQuat.current);
        fakeCam.updateMatrixWorld(true);

        // Snap to existing registered hole centers first (within ~30px screen)
        const existing = useModelStore.getState().measurements;
        let bestHoleCenter: THREE.Vector3 | null = null;
        let bestHolePx = Infinity;
        const hitProj = hit.point.clone().project(fakeCam);
        const hx = (hitProj.x * 0.5 + 0.5) * canvasSize.width;
        const hy = (-hitProj.y * 0.5 + 0.5) * canvasSize.height;
        for (const m of existing) {
          if (m.kind !== 'hole') continue;
          const c = new THREE.Vector3(m.pointA.x, m.pointA.y, m.pointA.z);
          const p = c.clone().project(fakeCam);
          const sx = (p.x * 0.5 + 0.5) * canvasSize.width;
          const sy = (-p.y * 0.5 + 0.5) * canvasSize.height;
          const d = Math.hypot(sx - hx, sy - hy);
          if (d < 30 && d < bestHolePx) { bestHolePx = d; bestHoleCenter = c; }
        }

        if (snapEnabled && bestHoleCenter) {
          snappedPoint = bestHoleCenter;
          snapKind = 'hole';
        } else if (snapEnabled) {
          // Try detecting a circular edge (hole) at hit — radius mais generoso
          const circle = detectCircularEdgeAtPoint(hit.object, hit.point, fakeCam, canvasSize, 90);
          if (circle) {
            snappedPoint = circle.center;
            snapKind = 'hole';
            hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center };
          } else {
            // Snap mais "magnético": raios maiores de busca em pixels
            //  vértice: 32px (antes 14), aresta: 48px (antes 18)
            const snap = resolveSnap(hit.object, hit.point, fakeCam, canvasSize, 32, 48);
            snappedPoint = snap.point;
            snapKind = snap.type;
            if (snap.type === 'edge') {
              const seg = findNearestEdgeSegment(hit.object, hit.point, fakeCam, canvasSize, 48);
              if (seg) {
                const lenMM = seg.a.distanceTo(seg.b) * 1000;
                hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, endpoints: { a: seg.a, b: seg.b } };
              }
            }
          }
        } else {
          snappedPoint = hit.point.clone();
          snapKind = 'surface';
        }
      }
      // Cache for non-heavy frames so the laser + trigger still have a target.
      // Sticky: se conseguimos um snap "forte" (vertex/edge/hole), preservamos
      // ele entre frames leves para evitar cintilação.
      cachedSnappedPoint.current = snappedPoint ? snappedPoint.clone() : null;
      cachedSnapKind.current = snapKind;
    } else {
      // Light frame: reuse the last computed snap so visuals stay smooth
      snappedPoint = cachedSnappedPoint.current ? cachedSnappedPoint.current.clone() : null;
      snapKind = cachedSnapKind.current;
    }

    // ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
    const measureModeNow = useModelStore.getState().measureMode;
    const nowT = performance.now();
    if (snappedPoint && hoverDetected) {
      const dist = dwellPos.current.distanceTo(snappedPoint);
      if (dist > 0.005) {
        dwellPos.current.copy(snappedPoint);
        dwellStart.current = nowT;
        dwellFired.current = false;
      } else if (!dwellFired.current && nowT - dwellStart.current > 1000) {
        dwellFired.current = true;
        if (measureModeNow) {
          useModelStore.getState().registerHoverMeasurement({
            type: hoverDetected.kind,
            value: hoverDetected.value,
            position: [hoverDetected.position.x, hoverDetected.position.y, hoverDetected.position.z],
            endpoints: hoverDetected.endpoints && {
              a: { x: hoverDetected.endpoints.a.x, y: hoverDetected.endpoints.a.y, z: hoverDetected.endpoints.a.z },
              b: { x: hoverDetected.endpoints.b.x, y: hoverDetected.endpoints.b.y, z: hoverDetected.endpoints.b.z },
            },
          });
        }
      }
    } else {
      dwellFired.current = false;
      dwellStart.current = nowT;
    }

    // ── Update laser visual ───────────────────────────────────────────
    if (laserRef.current && tipRef.current) {
      const end = snappedPoint ?? tmpOrigin.current.clone().add(tmpDir.current.clone().multiplyScalar(MAX_RAY));
      const positions = laserGeom.current.attributes.position as THREE.BufferAttribute;
      positions.setXYZ(0, tmpOrigin.current.x, tmpOrigin.current.y, tmpOrigin.current.z);
      positions.setXYZ(1, end.x, end.y, end.z);
      positions.needsUpdate = true;
      laserGeom.current.computeBoundingSphere();

      const color = selectionMode ? '#a855f7' : (snapKind === 'hole' ? '#f59e0b' : snapKind === 'vertex' ? '#22c55e' : snapKind === 'edge' ? '#3b82f6' : '#eab308');
      tipColor.current.set(color);
      (laserRef.current.material as THREE.LineBasicMaterial).color.set(color);
      ((tipRef.current.material as THREE.MeshBasicMaterial)).color.copy(tipColor.current);

      laserRef.current.visible = true;
      if (snappedPoint) {
        tipRef.current.visible = true;
        tipRef.current.position.copy(snappedPoint);
        // Scale tip with distance from controller (~12mm at 1m)
        const dist = tmpOrigin.current.distanceTo(snappedPoint);
        const s = Math.max(0.004, dist * 0.012);
        tipRef.current.scale.setScalar(s);
      }
    }

    // ── Right trigger: add point (measure) OR toggle selection ────────
    const trigVal = gp?.buttons?.[0]?.value ?? (gp?.buttons?.[0]?.pressed ? 1 : 0);
    if (!trigState.current && trigVal > TRIG_ON) {
      trigState.current = true;
      const st = useModelStore.getState();
      if (st.selectionMode) {
        // Fresh raycast on the press itself (not every frame) for selection
        const triggerHits = raycaster.current.intersectObjects(scene.children, true);
        const triggerHit = triggerHits.find((h) => {
          const o = h.object;
          if (!(o instanceof THREE.Mesh)) return false;
          if (o.userData.__edgeLine) return false;
          if (o.geometry instanceof THREE.SphereGeometry) return false;
          if (o.geometry instanceof THREE.RingGeometry) return false;
          if (o.geometry instanceof THREE.PlaneGeometry) return false;
          return true;
        });
        if (triggerHit && triggerHit.object instanceof THREE.Mesh) {
          let cur: THREE.Object3D | null = triggerHit.object;
          let element: THREE.Object3D | null = null;
          let modelId: string | null = null;
          while (cur) {
            if (!element && cur.userData?.ifcElement) element = cur;
            if (!modelId && cur.userData?.modelId) modelId = cur.userData.modelId as string;
            cur = cur.parent;
          }
          if (!element) element = triggerHit.object;
          if (!modelId) modelId = st.activeModelId;
          if (modelId && element) {
            const id = element.userData?.ifcId ?? element.name ?? element.uuid;
            st.toggleElementSelection(`${modelId}:${id}`);
          }
        }
      } else if (snappedPoint) {
        st.addMeasurePoint({
          x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
        });
      }
    } else if (trigState.current && trigVal < TRIG_OFF) {
      trigState.current = false;
    }

    // ── Button A (undo) ──────────────────────────────────────────────
    const aBtn = gp?.buttons?.[4];
    const aVal = aBtn ? (aBtn.value || (aBtn.pressed ? 1 : 0)) : 0;
    if (!aState.current && aVal > BTN_ON) {
      aState.current = true;
      useModelStore.getState().undoLastMeasurement();
    } else if (aState.current && aVal < TRIG_OFF) {
      aState.current = false;
    }

    // ── Button B (clear) ─────────────────────────────────────────────
    const bBtn = gp?.buttons?.[5];
    const bVal = bBtn ? (bBtn.value || (bBtn.pressed ? 1 : 0)) : 0;
    if (!bState.current && bVal > BTN_ON) {
      bState.current = true;
      useModelStore.getState().clearMeasurements();
    } else if (bState.current && bVal < TRIG_OFF) {
      bState.current = false;
    }
  });

  // Construct the laser Line object once so we can attach via <primitive>
  const laserObject = useRef<THREE.Line>();
  if (!laserObject.current) {
    laserObject.current = new THREE.Line(laserGeom.current, laserMat.current);
    laserObject.current.frustumCulled = false;
    laserObject.current.renderOrder = 999;
    (laserRef as React.MutableRefObject<THREE.Line | null>).current = laserObject.current;
  }

  return (
    <>
      <primitive object={laserObject.current} />
      <mesh ref={tipRef} frustumCulled={false} renderOrder={999}>
        <sphereGeometry args={[1, 12, 12]} />
        <meshBasicMaterial color="#22c55e" depthTest={false} transparent opacity={0.9} />
      </mesh>
    </>
  );
}

