import { useRef, useMemo, useEffect, useState } from 'react';
import { Canvas, useFrame, useThree, ThreeEvent } from '@react-three/fiber';
import * as THREE from 'three';
import {
  mainCameraRef,
  mainControlsRef,
  requestView,
  calibration,
  startCalibration,
  cancelCalibration,
  pushCubeFace,
  subscribeCalibration,
} from './three/viewCubeBus';
import { useModelStore } from '@/stores/useModelStore';
import { getModelLocalGroup } from '@/lib/modelTransforms';
import { Check, RotateCcw, Maximize2 } from 'lucide-react';
import { toast } from 'sonner';

/** Builds a square canvas texture with a label centered on it. */
function makeFaceTexture(label: string, accent: boolean): THREE.CanvasTexture {
  const size = 256;
  const c = document.createElement('canvas');
  c.width = size;
  c.height = size;
  const ctx = c.getContext('2d')!;
  const grad = ctx.createLinearGradient(0, 0, 0, size);
  grad.addColorStop(0, accent ? '#1a2742' : '#101725');
  grad.addColorStop(1, accent ? '#0c1424' : '#070b14');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, size, size);
  ctx.strokeStyle = '#00f3ff';
  ctx.lineWidth = 6;
  ctx.strokeRect(3, 3, size - 6, size - 6);
  ctx.fillStyle = '#e6faff';
  ctx.font = '700 56px JetBrains Mono, ui-monospace, monospace';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.shadowColor = '#00f3ff';
  ctx.shadowBlur = 18;
  ctx.fillText(label, size / 2, size / 2);
  const tex = new THREE.CanvasTexture(c);
  tex.anisotropy = 4;
  tex.needsUpdate = true;
  return tex;
}

const FACE_DEFS: { label: string; dir: [number, number, number] }[] = [
  { label: 'DIR',    dir: [ 1, 0, 0] },
  { label: 'ESQ',    dir: [-1, 0, 0] },
  { label: 'TOPO',   dir: [ 0, 1, 0] },
  { label: 'BASE',   dir: [ 0,-1, 0] },
  { label: 'FRENTE', dir: [ 0, 0, 1] },
  { label: 'ATRÁS',  dir: [ 0, 0,-1] },
];

function CubeMesh({ calibrating, activeModelId }: { calibrating: boolean; activeModelId: string | null }) {
  const meshRef = useRef<THREE.Mesh>(null);
  const groupRef = useRef<THREE.Group>(null);
  const { camera: localCam } = useThree();
  const [hover, setHover] = useState<number | null>(null);

  const materials = useMemo(() => {
    return FACE_DEFS.map((f, i) => {
      const tex = makeFaceTexture(f.label, false);
      const mat = new THREE.MeshBasicMaterial({ map: tex });
      mat.userData.baseTex = tex;
      mat.userData.hoverTex = makeFaceTexture(f.label, true);
      mat.userData.faceIndex = i;
      return mat;
    });
  }, []);

  useEffect(() => {
    return () => {
      materials.forEach((m) => {
        m.userData.baseTex?.dispose?.();
        m.userData.hoverTex?.dispose?.();
        m.dispose();
      });
    };
  }, [materials]);

  useEffect(() => {
    materials.forEach((m, i) => {
      m.map = i === hover ? m.userData.hoverTex : m.userData.baseTex;
      m.needsUpdate = true;
    });
  }, [hover, materials]);

  // Reusable temp objects for the per-frame model-quaternion sync.
  const modelQuat = useMemo(() => new THREE.Quaternion(), []);
  /** Returns the active model's current world quaternion (fineTuning rotation
   *  + calibration). Used to keep the cube glued to the piece's pose. */
  const getModelWorldQuat = (): THREE.Quaternion => {
    const g = getModelLocalGroup(activeModelId);
    if (g) {
      g.updateWorldMatrix(true, false);
      g.getWorldQuaternion(modelQuat);
      return modelQuat;
    }
    modelQuat.identity();
    return modelQuat;
  };

  useFrame(() => {
    const main = mainCameraRef.current;
    const controls = mainControlsRef.current;
    if (!main) return;
    const target: THREE.Vector3 = controls?.target ?? new THREE.Vector3();
    const dir = new THREE.Vector3().subVectors(main.position, target).normalize();
    localCam.position.copy(dir.multiplyScalar(3));
    localCam.up.copy(main.up);
    localCam.lookAt(0, 0, 0);
    // Sync the cube group orientation with the active model so it always
    // mirrors the piece's pose (rotation from Posicionar, calibration, etc).
    if (groupRef.current) {
      groupRef.current.quaternion.copy(getModelWorldQuat());
    }
  });

  const onClick = (e: ThreeEvent<MouseEvent>) => {
    e.stopPropagation();
    const idx = e.face?.materialIndex;
    if (idx == null) return;
    const def = FACE_DEFS[idx];
    if (!def) return;
    // Convert the local cube-face direction to world space using the model's
    // current world quaternion — this keeps cube ↔ model coherent after any
    // rotation/translation/reset of the piece.
    const dirWorld = new THREE.Vector3(...def.dir).applyQuaternion(getModelWorldQuat()).normalize();
    if (calibrating) {
      pushCubeFace(dirWorld);
    } else {
      requestView(dirWorld);
    }
  };

  // 70% of previous 1.4 → ~0.98
  const SIZE = 0.98;
  return (
    <group ref={groupRef}>
      <mesh
        ref={meshRef}
        material={materials}
        onClick={onClick}
        onPointerMove={(e) => {
          e.stopPropagation();
          const idx = e.face?.materialIndex ?? null;
          setHover(idx);
        }}
        onPointerOut={() => setHover(null)}
      >
        <boxGeometry args={[SIZE, SIZE, SIZE]} />
      </mesh>
      <AxisTripod />
    </group>
  );
}

function AxisTripod() {
  return (
    <group position={[-1.2, -1.2, 0]}>
      <arrowHelper args={[new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 0), 0.7, 0xff4444, 0.18, 0.12]} />
      <arrowHelper args={[new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 0), 0.7, 0x44ff66, 0.18, 0.12]} />
      <arrowHelper args={[new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, 0), 0.7, 0x4488ff, 0.18, 0.12]} />
    </group>
  );
}

const STEP_HINTS: Record<string, string> = {
  'await-cube-1': '1/3 — clique numa face do cubo',
  'await-model-1': '1/3 — clique na face correspondente da peça',
  'await-cube-2': '2/3 — clique noutra face do cubo',
  'await-model-2': '2/3 — clique na face correspondente da peça',
  'await-cube-3': '3/3 (verificação) — clique numa terceira face do cubo (opcional)',
  'await-model-3': '3/3 (verificação) — clique na face correspondente da peça',
  'done': 'Calibração concluída',
};

export function ViewCube() {
  const [, force] = useState(0);
  useEffect(() => subscribeCalibration(() => force(t => t + 1)), []);
  const activeModelId = useModelStore((s) => s.activeModelId);
  const models = useModelStore((s) => s.models);
  const setCalibrationStore = useModelStore((s) => s.setCalibration);
  const active = models.find(m => m.id === activeModelId);

  const isCalibrating = calibration.step !== 'idle' && calibration.step !== 'done';
  const isDone = calibration.step === 'done';
  const progress = isDone ? 1 : calibration.progress;

  const onClickCalibrar = () => {
    const store = useModelStore.getState();
    if (isCalibrating) {
      // Cancel mid-flow — restore previous camera mode if we forced ortho.
      const prev = (window as any).__tsxr_prevCamMode as 'ortho' | 'persp' | undefined;
      if (prev) { store.setCameraMode(prev); (window as any).__tsxr_prevCamMode = undefined; }
      cancelCalibration();
      toast.info('Calibração cancelada');
      return;
    }
    if (isDone) {
      const prev = (window as any).__tsxr_prevCamMode as 'ortho' | 'persp' | undefined;
      if (prev) { store.setCameraMode(prev); (window as any).__tsxr_prevCamMode = undefined; }
      cancelCalibration();
      return;
    }
    if (!active) {
      toast.error('Selecione uma peça antes de calibrar');
      return;
    }
    if (active.locked) {
      toast.error('Peça travada — destranque o cadeado para calibrar');
      return;
    }
    // Force orthographic view for accurate face picking.
    (window as any).__tsxr_prevCamMode = store.cameraMode;
    if (store.cameraMode !== 'ortho') store.setCameraMode('ortho');
    startCalibration(active.id);
    toast('Calibração em vista ortogonal — clique numa face do cubo, depois na face correspondente da peça', { duration: 4000 });
  };

  const onResetCalibration = () => {
    if (!active) return;
    setCalibrationStore(active.id, null);
    toast.success('Calibração removida');
  };

  return (
    <div
      className="absolute right-3 top-3 z-20 w-[120px] flex flex-col items-stretch gap-1.5"
      style={{ pointerEvents: 'auto' }}
    >
      <div
        className="h-[120px] w-[120px] rounded-md border border-primary/30 bg-background/40 backdrop-blur-sm shadow-lg"
        title="Cubo de vistas — clique numa face para girar"
      >
        <Canvas
          camera={{ position: [2, 2, 2], fov: 35, near: 0.1, far: 50 }}
          gl={{ antialias: true, alpha: true }}
          dpr={[1, 2]}
        >
          <ambientLight intensity={1} />
          <CubeMesh calibrating={isCalibrating} activeModelId={activeModelId} />
        </Canvas>
      </div>

      {/* Calibrar button with progress fill */}
      <button
        type="button"
        onClick={onClickCalibrar}
        className={[
          'relative h-7 w-full overflow-hidden rounded border font-mono text-[10px] uppercase tracking-widest transition-colors',
          isDone
            ? 'border-success/60 bg-success/20 text-success'
            : isCalibrating
              ? 'border-amber-400/60 bg-amber-400/10 text-amber-300'
              : 'border-primary/40 bg-background/40 text-primary hover:bg-primary/10',
        ].join(' ')}
        title={isCalibrating ? 'Cancelar calibração' : 'Calibrar orientação da peça com o cubo'}
      >
        {/* progress bar */}
        <span
          className={[
            'absolute inset-y-0 left-0 transition-all duration-200',
            isDone ? 'bg-success/40' : 'bg-primary/30',
          ].join(' ')}
          style={{ width: `${Math.round(progress * 100)}%` }}
        />
        <span className="relative z-10 flex items-center justify-center gap-1">
          {isDone ? <Check className="h-3 w-3" /> : null}
          {isDone ? 'Calibrado' : isCalibrating ? 'Cancelar' : 'Calibrar'}
        </span>
      </button>

      {/* Step hint while calibrating */}
      {(isCalibrating || isDone) && (
        <div className="rounded border border-primary/30 bg-background/80 px-2 py-1 text-center font-mono text-[9px] leading-tight text-foreground/90 backdrop-blur-sm">
          {STEP_HINTS[calibration.step] ?? ''}
          {isDone && Number.isFinite(calibration.verifyErrorDeg) && (
            <div className="mt-0.5 text-success">
              erro {calibration.verifyErrorDeg.toFixed(1)}°
            </div>
          )}
        </div>
      )}

      {/* Reset existing calibration */}
      {!isCalibrating && active?.calibrationQuat && (
        <button
          type="button"
          onClick={onResetCalibration}
          className="flex h-6 w-full items-center justify-center gap-1 rounded border border-muted-foreground/30 bg-background/40 font-mono text-[9px] uppercase tracking-widest text-muted-foreground hover:text-foreground hover:border-muted-foreground/60"
          title="Remover calibração salva"
        >
          <RotateCcw className="h-2.5 w-2.5" /> reset
        </button>
      )}

      {/* Botão de Tela Cheia */}
      <button
        type="button"
        onClick={() => {
          const container = document.getElementById("canvas-container");
          if (container) {
            container.requestFullscreen().catch((err) => {
              toast.error("Não foi possível entrar em tela cheia: " + err.message);
            });
          }
        }}
        className="flex h-7 w-full items-center justify-center gap-1 rounded border border-primary/40 bg-background/40 font-mono text-[10px] uppercase tracking-widest text-primary hover:bg-primary/10 transition-colors"
        title="Entrar em modo tela cheia"
      >
        <Maximize2 className="h-3 w-3" /> Tela Cheia
      </button>
    </div>
  );
}
