import { Suspense, useRef, useMemo, useEffect, useCallback } from 'react';
import { Canvas, useThree, useFrame } from '@react-three/fiber';
import { OrbitControls, useGLTF, Grid, Html, Line } from '@react-three/drei';
import { Loader2 } from 'lucide-react';
import * as THREE from 'three';
import { useModelStore, type SceneModel } from '@/stores/useModelStore';
import { findNearestVertex, detectHoleAtFace, findNearestEdgeSegment } from './SmartMeasure';

interface ModelViewerProps {
  url?: string; // legacy, ignored — uses store.models
}

function LoadingFallback() {
  return (
    <Html center>
      <div className="flex items-center gap-2 rounded-lg bg-card px-4 py-2 text-foreground shadow-lg">
        <Loader2 className="h-4 w-4 animate-spin text-primary" />
        <span className="font-mono text-sm">Carregando modelo…</span>
      </div>
    </Html>
  );
}

function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive: boolean }) {
  const { scene: rawScene } = useGLTF(sceneModel.url);
  // Clone scene per instance so multiple GLBModels with same url don't share materials/transforms
  const scene = useMemo(() => rawScene.clone(true), [rawScene]);
  const ref = useRef<THREE.Group>(null);
  const opacity = useModelStore((s) => s.opacity);
  const renderMode = useModelStore((s) => s.renderMode);
  const wireframeColor = useModelStore((s) => s.wireframeColor);
  const wireframeThickness = useModelStore((s) => s.wireframeThickness);
  const edgeThresholdAngle = useModelStore((s) => s.edgeThresholdAngle);
  const checklist = useModelStore((s) => s.checklist);
  const setActive = useModelStore((s) => s.setActiveModel);
  const fineTuning = sceneModel.fineTuning;

  const modelInfo = useMemo(() => {
    const box = new THREE.Box3().setFromObject(scene);
    const size = new THREE.Vector3();
    const center = new THREE.Vector3();
    box.getSize(size);
    box.getCenter(center);
    return { size, center };
  }, [scene]);

  const originalColors = useRef<Map<THREE.Material, THREE.Color>>(new Map());

  useEffect(() => {
    const hasRejected = checklist.some(i => i.status === 'rejected');
    const allApproved = checklist.every(i => i.status === 'approved');

    scene.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        if (Array.isArray(child.material)) {
          child.material = child.material.map(m => m.clone());
        } else {
          child.material = child.material.clone();
        }

        const toRemove: THREE.Object3D[] = [];
        child.children.forEach(c => {
          if (c.userData.__edgeLine) toRemove.push(c);
        });
        toRemove.forEach(c => {
          if (c instanceof THREE.LineSegments) {
            c.geometry.dispose();
            (c.material as THREE.Material).dispose();
          }
          child.remove(c);
        });

        const materials = Array.isArray(child.material) ? child.material : [child.material];
        materials.forEach((mat: THREE.Material) => {
          if (mat instanceof THREE.MeshStandardMaterial) {
            if (!originalColors.current.has(mat)) {
              originalColors.current.set(mat, mat.color.clone());
            }

            if (renderMode === 'edges') {
              mat.visible = false;
            } else {
              mat.visible = true;
              mat.transparent = opacity < 1;
              mat.opacity = opacity;
              mat.wireframe = renderMode === 'wireframe';
              if (renderMode === 'wireframe') {
                mat.wireframeLinewidth = wireframeThickness;
                mat.color.set(wireframeColor);
              } else if (hasRejected) {
                mat.color.setHSL(0, 0.7, 0.5);
              } else if (allApproved) {
                mat.color.setHSL(0.38, 0.7, 0.45);
              } else {
                // Tint with the per-model color (subtle)
                mat.color.set(sceneModel.color);
              }
            }
            mat.needsUpdate = true;
          }
        });

        if (renderMode === 'edges' && child.geometry) {
          const edgesGeo = new THREE.EdgesGeometry(child.geometry, edgeThresholdAngle);
          const lineMat = new THREE.LineBasicMaterial({
            color: wireframeColor,
            linewidth: wireframeThickness,
          });
          const lineSegments = new THREE.LineSegments(edgesGeo, lineMat);
          lineSegments.userData.__edgeLine = true;
          child.add(lineSegments);
        }
      }
    });
  }, [scene, opacity, renderMode, checklist, wireframeColor, wireframeThickness, edgeThresholdAngle, sceneModel.color]);

  const rotXRad = (fineTuning.rotX * Math.PI) / 180;
  const rotYRad = (fineTuning.rotY * Math.PI) / 180;
  const rotZRad = (fineTuning.rotZ * Math.PI) / 180;
  const s = fineTuning.scale ?? 1;
  const scaleRatio = useModelStore((st) => st.scaleRatio);
  const renderFactor = scaleRatio?.factor ?? 1;

  if (!sceneModel.visible) return null;

  return (
    <group
      scale={[renderFactor, renderFactor, renderFactor]}
      onClick={(e) => { e.stopPropagation(); setActive(sceneModel.id); }}
    >
      <group
        ref={ref}
        position={[
          -modelInfo.center.x + fineTuning.posX,
          -modelInfo.center.y + fineTuning.posY,
          -modelInfo.center.z + fineTuning.posZ,
        ]}
        rotation={[rotXRad, rotYRad, rotZRad]}
        scale={[s, s, s]}
      >
        <primitive object={scene} />
        {isActive && (
          <mesh>
            <boxGeometry args={[modelInfo.size.x * 1.05, modelInfo.size.y * 1.05, modelInfo.size.z * 1.05]} />
            <meshBasicMaterial color={sceneModel.color} wireframe transparent opacity={0.25} />
          </mesh>
        )}
      </group>
    </group>
  );
}

function SceneModels() {
  const models = useModelStore((s) => s.models);
  const activeId = useModelStore((s) => s.activeModelId);
  return (
    <>
      {models.map((m) => (
        <GLBModel key={m.id} sceneModel={m} isActive={m.id === activeId} />
      ))}
    </>
  );
}

/** Sphere marker at a 3D point */
function PointMarker({ position, color = '#e8a838' }: { position: [number, number, number]; color?: string }) {
  return (
    <mesh position={position}>
      <sphereGeometry args={[0.003, 16, 16]} />
      <meshBasicMaterial color={color} />
    </mesh>
  );
}

/** Snap ring indicator */
function SnapRing({ position }: { position: [number, number, number] }) {
  return (
    <mesh position={position} rotation={[-Math.PI / 2, 0, 0]}>
      <ringGeometry args={[0.003, 0.005, 24]} />
      <meshBasicMaterial color="#eab308" side={THREE.DoubleSide} />
    </mesh>
  );
}

/** Renders all measurements, snap point, and hover info */
function MeasurementOverlay() {
  const measurements = useModelStore((s) => s.measurements);
  const measurePoints = useModelStore((s) => s.measurePoints);
  const snapPoint = useModelStore((s) => s.snapPoint);
  const hoverInfo = useModelStore((s) => s.hoverInfo);
  const measureMode = useModelStore((s) => s.measureMode);

  return (
    <>
      {/* Snap indicator */}
      {measureMode && snapPoint && (
        <SnapRing position={[snapPoint.x, snapPoint.y, snapPoint.z]} />
      )}

      {/* Hover auto-detect tooltip */}
      {hoverInfo && (
        <Html position={hoverInfo.position} center distanceFactor={1} style={{ pointerEvents: 'none' }}>
          <div className="rounded bg-card/95 border border-primary/50 px-2 py-0.5 shadow-lg backdrop-blur-sm">
            <span className="font-mono text-[11px] font-bold text-primary whitespace-nowrap">
              {hoverInfo.type === 'hole' ? '⌀ ' : ''}
              {hoverInfo.value.toFixed(1)} mm
            </span>
          </div>
        </Html>
      )}

      {/* Pending first point */}
      {measurePoints.length === 1 && (
        <PointMarker position={[measurePoints[0].x, measurePoints[0].y, measurePoints[0].z]} color="#e8a838" />
      )}

      {/* Completed measurements */}
      {measurements.map((m) => {
        const a: [number, number, number] = [m.pointA.x, m.pointA.y, m.pointA.z];
        const b: [number, number, number] = [m.pointB.x, m.pointB.y, m.pointB.z];
        const mid: [number, number, number] = [
          (a[0] + b[0]) / 2,
          (a[1] + b[1]) / 2,
          (a[2] + b[2]) / 2,
        ];

        return (
          <group key={m.id}>
            <PointMarker position={a} color="#22c55e" />
            <PointMarker position={b} color="#22c55e" />
            <Line
              points={[a, b]}
              color="#22c55e"
              lineWidth={2}
            />
            <Html position={mid} center distanceFactor={1} style={{ pointerEvents: 'none' }}>
              <div className="rounded bg-card/95 border border-success/50 px-2 py-0.5 shadow-lg backdrop-blur-sm">
                <span className="font-mono text-[11px] font-bold text-success whitespace-nowrap">
                  {m.distanceMM.toFixed(1)} mm
                </span>
              </div>
            </Html>
          </group>
        );
      })}
    </>
  );
}

/** Smart vertex snap on pointer move */
function SmartSnapHandler() {
  const { camera, scene, gl } = useThree();
  const measureMode = useModelStore((s) => s.measureMode);
  const setSnapPoint = useModelStore((s) => s.setSnapPoint);
  const raycaster = useMemo(() => new THREE.Raycaster(), []);
  const mouse = useMemo(() => new THREE.Vector2(), []);
  const mouseRef = useRef({ x: 0, y: 0 });

  useEffect(() => {
    const onMove = (e: MouseEvent) => {
      const rect = gl.domElement.getBoundingClientRect();
      mouseRef.current.x = e.clientX - rect.left;
      mouseRef.current.y = e.clientY - rect.top;
      mouse.x = (mouseRef.current.x / rect.width) * 2 - 1;
      mouse.y = -(mouseRef.current.y / rect.height) * 2 + 1;
    };
    gl.domElement.addEventListener('pointermove', onMove);
    return () => gl.domElement.removeEventListener('pointermove', onMove);
  }, [gl, mouse]);

  useFrame(() => {
    if (!measureMode) {
      setSnapPoint(null);
      return;
    }

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children, true);
    const hit = intersects.find(i => {
      const obj = i.object;
      if (obj instanceof THREE.GridHelper) return false;
      if (obj instanceof THREE.Mesh && obj.geometry instanceof THREE.SphereGeometry) return false;
      if (obj instanceof THREE.Mesh && obj.geometry instanceof THREE.RingGeometry) return false;
      if (obj.userData.__edgeLine) return false;
      return obj instanceof THREE.Mesh;
    });

    if (hit && hit.object instanceof THREE.Mesh) {
      const canvas = gl.domElement;
      const snap = findNearestVertex(
        hit.object, hit.point, camera,
        { width: canvas.clientWidth, height: canvas.clientHeight },
        10
      );
      if (snap) {
        setSnapPoint({ x: snap.x, y: snap.y, z: snap.z });
      } else {
        setSnapPoint(null);
      }
    } else {
      setSnapPoint(null);
    }
  });

  return null;
}

/** Hover detector for auto-detect hole diameter and edge length */
function HoverDetector() {
  const { camera, scene, gl } = useThree();
  const measureMode = useModelStore((s) => s.measureMode);
  const setHoverInfo = useModelStore((s) => s.setHoverInfo);
  const raycaster = useMemo(() => new THREE.Raycaster(), []);
  const mouse = useMemo(() => new THREE.Vector2(), []);
  const hoverTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const lastHitKey = useRef('');

  useEffect(() => {
    if (measureMode) {
      setHoverInfo(null);
      if (hoverTimer.current) clearTimeout(hoverTimer.current);
      return;
    }

    const onMove = (e: MouseEvent) => {
      const rect = gl.domElement.getBoundingClientRect();
      mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;

      raycaster.setFromCamera(mouse, camera);
      const intersects = raycaster.intersectObjects(scene.children, true);
      const hit = intersects.find(i => {
        const obj = i.object;
        if (obj instanceof THREE.GridHelper) return false;
        if (obj instanceof THREE.Mesh && obj.geometry instanceof THREE.SphereGeometry) return false;
        if (obj instanceof THREE.Mesh && obj.geometry instanceof THREE.RingGeometry) return false;
        if (obj.userData.__edgeLine) return false;
        return obj instanceof THREE.Mesh;
      });

      if (!hit || !(hit.object instanceof THREE.Mesh)) {
        lastHitKey.current = '';
        setHoverInfo(null);
        if (hoverTimer.current) clearTimeout(hoverTimer.current);
        return;
      }

      // Stability check – same approximate position for debounce
      const key = `${hit.point.x.toFixed(3)},${hit.point.y.toFixed(3)},${hit.point.z.toFixed(3)}`;
      if (key === lastHitKey.current) return; // timer already running
      lastHitKey.current = key;
      setHoverInfo(null);

      if (hoverTimer.current) clearTimeout(hoverTimer.current);
      hoverTimer.current = setTimeout(() => {
        if (!(hit.object instanceof THREE.Mesh)) return;
        const canvas = gl.domElement;
        const canvasSize = { width: canvas.clientWidth, height: canvas.clientHeight };

        // Try hole detection first
        if (hit.faceIndex !== undefined) {
          const hole = detectHoleAtFace(hit.object, hit.faceIndex);
          if (hole) {
            setHoverInfo({
              type: 'hole',
              position: [hole.center.x, hole.center.y, hole.center.z],
              value: hole.diameterMM,
            });
            return;
          }
        }

        // Try edge detection
        const edge = findNearestEdgeSegment(hit.object, hit.point, camera, canvasSize, 15);
        if (edge) {
          setHoverInfo({
            type: 'edge',
            position: [edge.midpoint.x, edge.midpoint.y, edge.midpoint.z],
            value: edge.lengthMM,
          });
          return;
        }

        setHoverInfo(null);
      }, 1000);
    };

    const onLeave = () => {
      lastHitKey.current = '';
      setHoverInfo(null);
      if (hoverTimer.current) clearTimeout(hoverTimer.current);
    };

    gl.domElement.addEventListener('pointermove', onMove);
    gl.domElement.addEventListener('pointerleave', onLeave);
    return () => {
      gl.domElement.removeEventListener('pointermove', onMove);
      gl.domElement.removeEventListener('pointerleave', onLeave);
      if (hoverTimer.current) clearTimeout(hoverTimer.current);
    };
  }, [measureMode, camera, scene, gl, raycaster, mouse, setHoverInfo]);

  return null;
}

/** Raycasting click handler for measurement mode – uses snap point when available */
function MeasureClickHandler() {
  const { camera, scene, gl } = useThree();
  const measureMode = useModelStore((s) => s.measureMode);
  const addMeasurePoint = useModelStore((s) => s.addMeasurePoint);
  const snapPoint = useModelStore((s) => s.snapPoint);
  const raycaster = useMemo(() => new THREE.Raycaster(), []);
  const mouse = useMemo(() => new THREE.Vector2(), []);

  const handleClick = useCallback((event: MouseEvent) => {
    if (!measureMode) return;

    // Use snap point if available
    if (snapPoint) {
      addMeasurePoint({ x: snapPoint.x, y: snapPoint.y, z: snapPoint.z });
      return;
    }

    const rect = gl.domElement.getBoundingClientRect();
    mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects(scene.children, true);

    const hit = intersects.find(i => {
      const obj = i.object;
      if (obj instanceof THREE.GridHelper) return false;
      if (obj instanceof THREE.Mesh) {
        if (obj.geometry instanceof THREE.SphereGeometry) return false;
        if (obj.geometry instanceof THREE.RingGeometry) return false;
      }
      return true;
    });

    if (hit) {
      addMeasurePoint({ x: hit.point.x, y: hit.point.y, z: hit.point.z });
    }
  }, [measureMode, addMeasurePoint, snapPoint, camera, scene, gl, raycaster, mouse]);

  useEffect(() => {
    const canvas = gl.domElement;
    canvas.addEventListener('click', handleClick);
    return () => canvas.removeEventListener('click', handleClick);
  }, [gl, handleClick]);

  // Change cursor when in measure mode
  useEffect(() => {
    gl.domElement.style.cursor = measureMode ? 'crosshair' : 'grab';
    return () => { gl.domElement.style.cursor = 'grab'; };
  }, [measureMode, gl]);

  return null;
}

function GridLayer() {
  const showGrid = useModelStore((s) => s.showGrid);
  if (!showGrid) return null;
  return (
    <Grid
      position={[0, -0.09, 0]}
      infiniteGrid
      cellSize={0.01}
      sectionSize={0.1}
      cellThickness={0.5}
      sectionThickness={1}
      cellColor="#334155"
      sectionColor="#475569"
      fadeDistance={5}
      fadeStrength={1}
    />
  );
}

export function ModelViewerCanvas({ url }: ModelViewerProps) {
  return (
    <Canvas
      camera={{ position: [2, 2, 2], fov: 50, near: 0.001, far: 1000 }}
      gl={{ antialias: true, alpha: true, powerPreference: 'high-performance', preserveDrawingBuffer: true }}
      frameloop="demand"
      className="!bg-background"
      onCreated={({ gl, invalidate }) => {
        gl.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
        const loop = () => { invalidate(); requestAnimationFrame(loop); };
        loop();
      }}
    >
      <ambientLight intensity={0.6} />
      <directionalLight position={[5, 10, 5]} intensity={1} castShadow />
      <directionalLight position={[-5, 5, -5]} intensity={0.3} />
      
      <Suspense fallback={<LoadingFallback />}>
        <SceneModels />
      </Suspense>

      <MeasurementOverlay />
      <MeasureClickHandler />
      <SmartSnapHandler />
      <HoverDetector />

      <GridLayer />

      <OrbitControls
        makeDefault
        enableDamping
        dampingFactor={0.1}
        minDistance={0.05}
        maxDistance={50}
      />
    </Canvas>
  );
}
