import { useRef, useState, useEffect } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import * as THREE from 'three';
import { useModelStore } from '@/stores/useModelStore';
import { sendRemoteLog } from '@/lib/remoteLogger';
import { 
  resolveSnap, 
  detectCircularEdgeAtPoint, 
  findNearestEdgeSegment,
  resolveSnap3D,
  detectCircularEdgeAtPoint3D,
  findNearestEdgeSegment3D
} from './SmartMeasure';
import { toast } from 'sonner';

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

function isPickableModelMesh(o: THREE.Object3D): boolean {
  let cur: THREE.Object3D | null = o;
  while (cur) {
    if (cur.userData?.modelId) return true;
    cur = cur.parent;
  }
  return false;
}

function getModelIdFromObject(o: THREE.Object3D): string | undefined {
  let cur: THREE.Object3D | null = o;
  while (cur) {
    if (cur.userData?.modelId) return cur.userData.modelId as string;
    cur = cur.parent;
  }
  return undefined;
}

/**
 * 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'));
  const lockedSnap = useRef<{ point: THREE.Vector3; kind: 'vertex' | 'edge' | 'hole'; modelId?: string; lastSeen: number } | null>(null);

  // 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 hitTestSourceRef = useRef<XRHitTestSource | null>(null);
  const hitTestRequestedRef = useRef<boolean>(false);

  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) {
      if (hitTestRequestedRef.current) {
        hitTestSourceRef.current = null;
        hitTestRequestedRef.current = false;
      }
      return;
    }

    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);
    const xrRig = useModelStore.getState().xrRig;
    if (xrRig) {
      m.premultiply(xrRig.matrixWorld);
    }
    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; modelId?: string; endpoints?: { a: THREE.Vector3; b: THREE.Vector3 } } | null = null;
    let hitModelId: string | undefined;
    const nowT = performance.now();

    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 (!isPickableModelMesh(o)) 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) {
      hitModelId = getModelIdFromObject(hit.object);
      const dist = tmpOrigin.current.distanceTo(hit.point);
      const vertexThreshold = Math.max(0.08, dist * 0.12); 
      const edgeThreshold = Math.max(0.10, dist * 0.14);

      // Snap to existing registered hole centers first
      const existing = useModelStore.getState().measurements;
      let bestHoleCenter: THREE.Vector3 | null = null;
      let bestHoleDist = Infinity;
      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 d = hit.point.distanceTo(c);
        if (d < 0.05 && d < bestHoleDist) {
          bestHoleDist = d;
          bestHoleCenter = c;
        }
      }

      if (snapEnabled && bestHoleCenter) {
        snappedPoint = bestHoleCenter;
        snapKind = 'hole';
      } else if (snapEnabled) {
        const circle = detectCircularEdgeAtPoint3D(hit.object, hit.point, Math.max(0.08, dist * 0.12));
        if (circle) {
          snappedPoint = circle.center;
          snapKind = 'hole';
          hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center, modelId: hitModelId };
        } else {
          const snap = resolveSnap3D(hit.object, hit.point, vertexThreshold, edgeThreshold);
          snappedPoint = snap.point;
          snapKind = snap.type;
          if (snap.type === 'edge') {
            const seg = findNearestEdgeSegment3D(hit.object, hit.point, edgeThreshold);
            if (seg) {
              const lenMM = seg.a.distanceTo(seg.b) * 1000;
              hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, modelId: hitModelId, endpoints: { a: seg.a, b: seg.b } };
            }
          }
        }
      } else {
        snappedPoint = hit.point.clone();
        snapKind = 'surface';
      }
    }

    const strongSnapKind = snapKind === 'vertex' || snapKind === 'edge' || snapKind === 'hole' ? snapKind : null;
    if (snappedPoint && strongSnapKind) {
      const locked = lockedSnap.current;
      const sameTarget = locked && locked.kind === strongSnapKind && locked.modelId === hitModelId;
      if (sameTarget && locked.point.distanceTo(snappedPoint) < (snapKind === 'edge' ? 0.08 : 0.12)) {
        if (snapKind === 'edge') locked.point.lerp(snappedPoint, 0.25);
        snappedPoint = locked.point.clone();
        locked.lastSeen = nowT;
      } else {
        lockedSnap.current = { point: snappedPoint.clone(), kind: strongSnapKind, modelId: hitModelId, lastSeen: nowT };
      }
    } else if (snappedPoint && lockedSnap.current && lockedSnap.current.point.distanceTo(snappedPoint) < 0.12 && nowT - lockedSnap.current.lastSeen < 600) {
      snapKind = lockedSnap.current.kind;
      snappedPoint = lockedSnap.current.point.clone();
      lockedSnap.current.lastSeen = nowT;
    } else if (!snappedPoint || (lockedSnap.current && nowT - lockedSnap.current.lastSeen > 600)) {
      lockedSnap.current = null;
    }

    // ── Dwell detection (1 s) to auto-register hovered hole/edge ─────
    const measureModeNow = useModelStore.getState().measureMode;
    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 },
            },
            modelId: hoverDetected.modelId,
          });
        }
      }
    } 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;
      
      const tipPos = snappedPoint;
      if (tipPos) {
        tipRef.current.visible = true;
        tipRef.current.position.copy(tipPos);
        const dist = tmpOrigin.current.distanceTo(tipPos);
        const strongSnap = snapKind === 'vertex' || snapKind === 'edge' || snapKind === 'hole';
        const factor = strongSnap ? 0.030 : 0.012;
        const s = Math.max(strongSnap ? 0.010 : 0.004, dist * factor);
        tipRef.current.scale.setScalar(s);
      } else {
        tipRef.current.visible = false;
      }
    }

    // ── 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 (!isPickableModelMesh(o)) 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) {
        sendRemoteLog('info', 'Trigger físico pressionado no AR com snapPoint ativo', snappedPoint);
        st.addMeasurePoint({
          x: snappedPoint.x, y: snappedPoint.y, z: snappedPoint.z,
          modelId: hitModelId,
        });
      }
    } else if (trigState.current && trigVal < TRIG_OFF) {
      trigState.current = false;
    }

    // A and B physical buttons are now handled globally in XRHudInWorld.tsx for the Summon Menu.
  });

  // 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>
    </>
  );
}

