import { useEffect, useRef, useMemo, useState, useCallback } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { Grid, Billboard, Sky, Line } from '@react-three/drei';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import * as THREE from 'three';
import { useModelStore, type SceneModel } from '@/stores/useModelStore';
import { findNearestVertex } from './SmartMeasure';
import { registerModelLocalGroup, unregisterModelLocalGroup } from '@/lib/modelTransforms';
import { parseIFCtoThree } from '@/lib/convertIFC';

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

// ─── XRModel ───────────────────────────────────────────
export function XRModel({ sceneModel }: { sceneModel: SceneModel }) {
  const [rawScene, setRawScene] = useState<THREE.Object3D | null>(null);
  const scene = useMemo(() => rawScene ? rawScene.clone(true) : null, [rawScene]);

  useEffect(() => {
    let active = true;
    const isIfc = sceneModel.fileName.toLowerCase().endsWith('.ifc');
    if (isIfc) {
      fetch(sceneModel.url)
        .then((res) => res.arrayBuffer())
        .then((buf) => parseIFCtoThree(buf))
        .then((threeScene) => {
          if (active) setRawScene(threeScene);
        })
        .catch((err) => console.error('[XRModel] IFC parsing error', err));
    } else {
      const loader = new GLTFLoader();
      loader.load(
        sceneModel.url,
        (gltf) => {
          if (active) setRawScene(gltf.scene);
        },
        undefined,
        (err) => console.error('[XRModel] GLTF loading error', err)
      );
    }
    return () => {
      active = false;
    };
  }, [sceneModel.url, sceneModel.fileName]);

  const ref = useRef<THREE.Group>(null);
  useEffect(() => {
    const g = ref.current;
    if (!g) return;
    registerModelLocalGroup(sceneModel.id, g);
    return () => unregisterModelLocalGroup(sceneModel.id, g);
  }, [sceneModel.id]);

  const fineTuning = sceneModel.fineTuning;
  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 measureMode = useModelStore((s) => s.measureMode);

  const modelInfo = useMemo(() => {
    if (!scene) return { size: new THREE.Vector3(), center: new THREE.Vector3() };
    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]);

  useEffect(() => {
    if (!scene) return;
    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) {
            const isEdgesOrMeasure = renderMode === 'edges' || measureMode;
            mat.visible = true;
            const targetOpacity = isEdgesOrMeasure ? 0.25 : opacity;
            mat.transparent = targetOpacity < 1;
            mat.opacity = targetOpacity;
            mat.wireframe = false;
            if (hasRejected) {
              mat.color.setHSL(0, 0.7, 0.5);
            } else if (allApproved) {
              mat.color.setHSL(0.38, 0.7, 0.45);
            } else {
              mat.color.set(sceneModel.color);
            }
            mat.needsUpdate = true;
          }
        });

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

  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;
  const xrScaleMode = useModelStore((st) => st.xrScaleMode);
  const isTabletop = xrScaleMode === 'tabletop';

  if (!sceneModel.visible) return null;
  if (!scene) return null;

  // Escala final: se for tabletop, escala de maquete proporcional ao tamanho do objeto
  const maxDim = Math.max(modelInfo.size.x, modelInfo.size.y, modelInfo.size.z);
  const finalScaleFactor = isTabletop
    ? (maxDim > 0 ? 0.5 / maxDim : 0.05)
    : renderFactor;

  // Posição local: no tabletop, eleva-se o modelo para que sua base coincida com Y=0 da origem do pai (topo da mesa)
  const localPos: [number, number, number] = isTabletop
    ? [
        -modelInfo.center.x + fineTuning.posX,
        (-modelInfo.center.y + (modelInfo.size.y / 2)) + fineTuning.posY,
        -modelInfo.center.z + fineTuning.posZ,
      ]
    : [
        -modelInfo.center.x + fineTuning.posX,
        -modelInfo.center.y + fineTuning.posY,
        -modelInfo.center.z + fineTuning.posZ,
      ];

  const parentPos: [number, number, number] = isTabletop ? [0, 0.85, 0] : [0, 0, 0];

  return (
    <group scale={[finalScaleFactor, finalScaleFactor, finalScaleFactor]} position={parentPos}>
      <group
        ref={ref}
        userData={{ modelId: sceneModel.id }}
        position={localPos}
        rotation={[rotXRad, rotYRad, rotZRad]}
        scale={[s, s, s]}
      >
        <primitive object={scene} />
        <XRLocalModelMeasurements modelId={sceneModel.id} />
      </group>
    </group>
  );
}

export function XRBackgroundModels() {
  const models = useModelStore((s) => s.models);
  const activeId = useModelStore((s) => s.activeModelId);
  return (
    <>
      {models.filter(m => m.id !== activeId).map((m) => (
        <XRModel key={m.id} sceneModel={m} />
      ))}
    </>
  );
}

export function XRActiveModel() {
  const models = useModelStore((s) => s.models);
  const activeId = useModelStore((s) => s.activeModelId);
  const active = models.find(m => m.id === activeId);
  if (!active) return null;
  return <XRModel sceneModel={active} />;
}

// ─── ControllerFineTuning ──────────────────────────────
export function ControllerFineTuning({ freeMove }: { freeMove: boolean }) {
  const { setFineTuning } = useModelStore();
  const session = useThree((s) => s.gl.xr.getSession());
  const isActiveLocked = useModelStore((s) => {
    const a = s.models.find(m => m.id === s.activeModelId);
    return !!a?.locked;
  });

  useFrame(() => {
    if (!session || isActiveLocked) return;
    const inputSources = session.inputSources;
    if (!inputSources) return;

    let leftAxes: number[] | null = null;
    let rightAxes: number[] | null = null;
    let leftTrig = 0;
    let rightTrig = 0;
    let gripHeld = false;
    let anyGripHeld = false;

    for (const source of inputSources) {
      const gp = source.gamepad;
      if (!gp) continue;
      const gripBtn = gp.buttons[2];
      const trigBtn = gp.buttons[1];
      const gripVal = gripBtn ? (gripBtn.value || (gripBtn.pressed ? 1 : 0)) : 0;
      if (gripBtn?.pressed) gripHeld = true;
      if (gripVal > 0.3) anyGripHeld = true;
      const trigVal = trigBtn ? (trigBtn.value || (trigBtn.pressed ? 1 : 0)) : 0;
      if (source.handedness === 'left' && gp.axes.length >= 4) {
        leftAxes = [gp.axes[2], gp.axes[3]];
        leftTrig = trigVal;
      }
      if (source.handedness === 'right' && gp.axes.length >= 4) {
        rightAxes = [gp.axes[2], gp.axes[3]];
        rightTrig = trigVal;
      }
    }

    if (anyGripHeld) return;
    if (!freeMove && !gripHeld) return;

    const baseStep = 0.001;
    const baseRot = 0.1;
    const deadzone = 0.15;
    const speedL = 0.3 + leftTrig * 2.7;
    const speedR = 0.3 + rightTrig * 2.7;
    const curve = (v: number) => Math.sign(v) * v * v;

    const modelStore = useModelStore.getState();
    const ft = { ...modelStore.fineTuning };

    if (leftAxes) {
      if (Math.abs(leftAxes[0]) > deadzone) ft.posX += curve(leftAxes[0]) * baseStep * speedL;
      if (Math.abs(leftAxes[1]) > deadzone) ft.posZ += curve(leftAxes[1]) * baseStep * speedL;
    }
    if (rightAxes) {
      if (Math.abs(rightAxes[0]) > deadzone) ft.rotY += curve(rightAxes[0]) * baseRot * speedR;
      if (Math.abs(rightAxes[1]) > deadzone) ft.posY -= curve(rightAxes[1]) * baseStep * speedR;
    }

    setFineTuning(ft);
  });

  return null;
}

function XRMeasurementText({ text, color }: { text: string; color: string }) {
  const texture = useMemo(() => {
    const canvas = document.createElement('canvas');
    canvas.width = 256;
    canvas.height = 64;
    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.fillStyle = 'rgba(11, 18, 32, 0.85)';
      ctx.beginPath();
      const x = 0, y = 0, width = 256, height = 64, radius = 12;
      ctx.moveTo(x + radius, y);
      ctx.lineTo(x + width - radius, y);
      ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
      ctx.lineTo(x + width, y + height - radius);
      ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
      ctx.lineTo(x + radius, y + height);
      ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
      ctx.lineTo(x, y + radius);
      ctx.quadraticCurveTo(x, y, x + radius, y);
      ctx.closePath();
      ctx.fill();

      ctx.strokeStyle = color;
      ctx.lineWidth = 3;
      ctx.stroke();

      ctx.fillStyle = color;
      ctx.font = 'bold 24px monospace';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(text, 128, 32);
    }
    const tex = new THREE.CanvasTexture(canvas);
    tex.colorSpace = THREE.SRGBColorSpace;
    return tex;
  }, [text, color]);

  return (
    <Billboard follow={true}>
      <mesh renderOrder={999}>
        <planeGeometry args={[0.08, 0.02]} />
        <meshBasicMaterial 
          map={texture} 
          transparent 
          depthTest={false} 
          side={THREE.DoubleSide} 
        />
      </mesh>
    </Billboard>
  );
}

export function XRSafeLine({ a, b }: { a: [number, number, number]; b: [number, number, number] }) {
  return (
    <Line points={[a, b]} color="#22c55e" lineWidth={2} />
  );
}

export function XRLocalModelMeasurements({ modelId }: { modelId: string }) {
  const measurements = useModelStore((s) => s.measurements);
  const modelMeasurements = useMemo(() => {
    return measurements.filter(m => m.modelId === modelId);
  }, [measurements, modelId]);

  return (
    <>
      {modelMeasurements.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];
        const isHole = m.kind === 'hole';
        const color = isHole ? '#f59e0b' : '#3b82f6';
        const text = m.label ?? `${m.distanceMM.toFixed(1)} mm`;

        return (
          <group key={m.id}>
            {!isHole && (
              <>
                <mesh position={a}>
                  <sphereGeometry args={[0.003, 16, 16]} />
                  <meshBasicMaterial color="#22c55e" />
                </mesh>
                <mesh position={b}>
                  <sphereGeometry args={[0.003, 16, 16]} />
                  <meshBasicMaterial color="#22c55e" />
                </mesh>
                <XRSafeLine a={a} b={b} />
              </>
            )}
            <group position={mid}>
              <XRMeasurementText text={text} color={color} />
            </group>
          </group>
        );
      })}
    </>
  );
}

export function XRMeasurementOverlay() {
  const measurements = useModelStore((s) => s.measurements);
  const measurePoints = useModelStore((s) => s.measurePoints);
  const snapPoint = useModelStore((s) => s.snapPoint);
  const measureMode = useModelStore((s) => s.measureMode);

  const globalMeasurements = useMemo(() => {
    return measurements.filter(m => !m.modelId);
  }, [measurements]);

  return (
    <>
      {measureMode && snapPoint && (
        <mesh position={[snapPoint.x, snapPoint.y, snapPoint.z]} rotation={[-Math.PI / 2, 0, 0]}>
          <ringGeometry args={[0.003, 0.005, 24]} />
          <meshBasicMaterial color="#eab308" side={THREE.DoubleSide} />
        </mesh>
      )}
      {measurePoints.length === 1 && (
        <mesh position={[measurePoints[0].x, measurePoints[0].y, measurePoints[0].z]}>
          <sphereGeometry args={[0.003, 16, 16]} />
          <meshBasicMaterial color="#e8a838" />
        </mesh>
      )}
      {globalMeasurements.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];
        const isHole = m.kind === 'hole';
        const color = isHole ? '#f59e0b' : '#3b82f6';
        const text = m.label ?? `${m.distanceMM.toFixed(1)} mm`;

        return (
          <group key={m.id}>
            {!isHole && (
              <>
                <mesh position={a}>
                  <sphereGeometry args={[0.003, 16, 16]} />
                  <meshBasicMaterial color="#22c55e" />
                </mesh>
                <mesh position={b}>
                  <sphereGeometry args={[0.003, 16, 16]} />
                  <meshBasicMaterial color="#22c55e" />
                </mesh>
                <XRSafeLine a={a} b={b} />
              </>
            )}
            <group position={mid}>
              <XRMeasurementText text={text} color={color} />
            </group>
          </group>
        );
      })}
    </>
  );
}

export function XRSnapHandler() {
  const { camera, scene, gl } = useThree();
  const measureMode = useModelStore((s) => s.measureMode);
  const setSnapPoint = useModelStore((s) => s.setSnapPoint);
  const addMeasurePoint = useModelStore((s) => s.addMeasurePoint);
  const snapPoint = useModelStore((s) => s.snapPoint);
  const raycaster = useMemo(() => new THREE.Raycaster(), []);
  const mouse = useMemo(() => new THREE.Vector2(), []);

  useEffect(() => {
    if (!measureMode) 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;
    };
    gl.domElement.addEventListener('pointermove', onMove);
    return () => gl.domElement.removeEventListener('pointermove', onMove);
  }, [gl, mouse, measureMode]);

  useFrame(() => {
    if (gl.xr.isPresenting) { setSnapPoint(null); return; }
    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 || 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);
      const hitModelId = findModelId(hit.object);
      setSnapPoint(snap ? { x: snap.x, y: snap.y, z: snap.z, modelId: hitModelId } : null);
    } else {
      setSnapPoint(null);
    }
  });

  useEffect(() => {
    if (!measureMode) return;
    if (gl.xr.isPresenting) return;
    const onClick = (e: MouseEvent) => {
      if (snapPoint) {
        addMeasurePoint({ x: snapPoint.x, y: snapPoint.y, z: snapPoint.z, modelId: snapPoint.modelId });
        return;
      }
      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 => i.object instanceof THREE.Mesh && !i.object.userData.__edgeLine);
      if (hit) {
        const hitModelId = findModelId(hit.object);
        addMeasurePoint({ x: hit.point.x, y: hit.point.y, z: hit.point.z, modelId: hitModelId });
      }
    };
    gl.domElement.addEventListener('click', onClick);
    return () => gl.domElement.removeEventListener('click', onClick);
  }, [measureMode, snapPoint, gl, camera, scene, raycaster, mouse, addMeasurePoint]);

  useEffect(() => {
    gl.domElement.style.cursor = measureMode ? 'crosshair' : 'grab';
    return () => { gl.domElement.style.cursor = 'grab'; };
  }, [measureMode, gl]);

  return null;
}

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

export function XRGridAutoFollower() {
  const models = useModelStore((s) => s.models);
  const { scene } = useThree();
  useEffect(() => {
    if (!useModelStore.getState().gridAutoFollow) return;
    const id = setTimeout(() => {
      if (!useModelStore.getState().gridAutoFollow) return;
      const box = new THREE.Box3();
      let has = false;
      scene.traverse((obj) => {
        if (obj instanceof THREE.Mesh && obj.geometry) {
          if (obj.geometry instanceof THREE.SphereGeometry) return;
          if (obj.geometry instanceof THREE.RingGeometry) return;
          if (obj.userData.__edgeLine) return;
          obj.updateWorldMatrix(true, false);
          const b = new THREE.Box3().setFromObject(obj);
          if (isFinite(b.min.y)) {
            if (!has) { box.copy(b); has = true; }
            else box.union(b);
          }
        }
      });
      if (has) useModelStore.setState({ gridY: box.min.y - 0.005 });
    }, 150);
    return () => clearTimeout(id);
  }, [models, scene]);
  return null;
}

export function XREnvironmentSelector() {
  const xrEnvironment = useModelStore((s) => s.xrEnvironment);

  if (xrEnvironment === 'passthrough') return null;

  return (
    <group>
      {/* Luzes adicionais para ambientes imersivos virtuais */}
      <ambientLight intensity={0.5} />
      <directionalLight position={[5, 10, 5]} intensity={1.0} castShadow />

      {/* Cenário Escritório */}
      {xrEnvironment === 'office' && (
        <group>
          {/* Carpete do escritório */}
          <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
            <planeGeometry args={[50, 50]} />
            <meshStandardMaterial color="#263238" roughness={0.95} />
          </mesh>

          {/* Tampo da Mesa */}
          <mesh position={[0, 0.825, 0]} receiveShadow castShadow>
            <cylinderGeometry args={[0.7, 0.7, 0.05, 32]} />
            <meshStandardMaterial color="#3e2723" roughness={0.6} metalness={0.1} />
          </mesh>

          {/* Perna da Mesa */}
          <mesh position={[0, 0.4, 0]} castShadow>
            <cylinderGeometry args={[0.08, 0.08, 0.8, 16]} />
            <meshStandardMaterial color="#333333" roughness={0.4} metalness={0.8} />
          </mesh>

          {/* Base da perna */}
          <mesh position={[0, 0.01, 0]} castShadow>
            <cylinderGeometry args={[0.3, 0.3, 0.02, 24]} />
            <meshStandardMaterial color="#222222" roughness={0.3} metalness={0.8} />
          </mesh>
        </group>
      )}

      {/* Cenário Campo Aberto */}
      {xrEnvironment === 'field' && (
        <group>
          <Sky distance={450000} sunPosition={[10, 5, 10]} inclination={0} azimuth={0.25} />
          {/* Chão de grama */}
          <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
            <planeGeometry args={[1000, 1000]} />
            <meshStandardMaterial color="#1b5e20" roughness={0.9} />
          </mesh>
        </group>
      )}
    </group>
  );
}
