import {
  Suspense,
  useRef,
  useCallback,
  useState,
  useEffect,
  Component,
  type ErrorInfo,
  type ReactNode,
} from 'react'
import { Canvas, useThree, useFrame } from '@react-three/fiber'
import { OrbitControls, useGLTF, Environment } from '@react-three/drei'
import { toast } from 'sonner'
import { X, Camera, Loader2, AlertTriangle } from 'lucide-react'
import api from '../../api/client'

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface ThreeDViewerProps {
  cadFileId: string
  onClose: () => void
}

// ---------------------------------------------------------------------------
// Inner model loader – separated so Suspense can catch it
// ---------------------------------------------------------------------------

function GltfModel({ url }: { url: string }) {
  const { scene } = useGLTF(url)
  return <primitive object={scene} />
}

// ---------------------------------------------------------------------------
// Screenshot helper – lives inside Canvas so it can access gl / useThree
// ---------------------------------------------------------------------------

interface ScreenshotCaptureProps {
  enabled: boolean
  cadFileId: string
  onDone: () => void
}

function ScreenshotCapture({ enabled, cadFileId, onDone }: ScreenshotCaptureProps) {
  const { gl } = useThree()
  const didCapture = useRef(false)

  useFrame(() => {
    if (!enabled || didCapture.current) return
    didCapture.current = true

    // Grab the canvas as a data-URL after the current frame has been rendered
    const dataUrl = gl.domElement.toDataURL('image/png')

    // Convert data-URL → Blob without a network fetch:
    // data:[<mediatype>][;base64],<data>
    const [header, base64Data] = dataUrl.split(',')
    const mimeMatch = header.match(/:(.*?);/)
    const mimeType = mimeMatch ? mimeMatch[1] : 'image/png'
    const byteCharacters = atob(base64Data)
    const byteArray = new Uint8Array(byteCharacters.length)
    for (let i = 0; i < byteCharacters.length; i++) {
      byteArray[i] = byteCharacters.charCodeAt(i)
    }
    const blob = new Blob([byteArray], { type: mimeType })

    const formData = new FormData()
    formData.append('thumbnail', blob, 'thumbnail.png')

    api
      .post(`/cad/${cadFileId}/regenerate-thumbnail`, formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
      })
      .then(() => {
        toast.success('Thumbnail captured and saved')
      })
      .catch((err: unknown) => {
        const msg = err instanceof Error ? err.message : 'Unknown error'
        console.error('Thumbnail upload failed', msg)
        toast.error('Failed to save thumbnail')
      })
      .finally(() => {
        didCapture.current = false
        onDone()
      })
  })

  return null
}

// ---------------------------------------------------------------------------
// Error boundary for the GLTF loader inside Suspense
// ---------------------------------------------------------------------------

class GltfErrorBoundary extends Component<
  { children: ReactNode; onError: (msg: string) => void },
  { hasError: boolean }
> {
  constructor(props: { children: ReactNode; onError: (msg: string) => void }) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(): { hasError: boolean } {
    return { hasError: true }
  }

  componentDidCatch(error: Error, _info: ErrorInfo): void {
    this.props.onError(error.message || 'Failed to parse GLTF')
  }

  render(): ReactNode {
    if (this.state.hasError) return null
    return this.props.children
  }
}

// ---------------------------------------------------------------------------
// Loading overlay (shown while model resolves inside Canvas)
// ---------------------------------------------------------------------------

function LoadingOverlay() {
  return (
    <div className="absolute inset-0 flex flex-col items-center justify-center bg-gray-900 text-white gap-3 pointer-events-none z-10">
      <Loader2 size={40} className="animate-spin text-accent" />
      <p className="text-sm text-gray-300">Loading 3D model…</p>
    </div>
  )
}

// ---------------------------------------------------------------------------
// Model loader with resolved tracking
// ---------------------------------------------------------------------------

interface ModelWithReadyProps {
  url: string
  onReady: () => void
}

function ModelWithReady({ url, onReady }: ModelWithReadyProps) {
  const { scene } = useGLTF(url)

  useEffect(() => {
    onReady()
  }, [onReady])

  return <primitive object={scene} />
}

// ---------------------------------------------------------------------------
// Main exported component
// ---------------------------------------------------------------------------

export default function ThreeDViewer({ cadFileId, onClose }: ThreeDViewerProps) {
  const modelUrl = `/api/cad/${cadFileId}/model`

  const [capturing, setCapturing] = useState(false)
  const [loadError, setLoadError] = useState<string | null>(null)
  const [modelReady, setModelReady] = useState(false)

  const handleModelReady = useCallback(() => setModelReady(true), [])
  const handleError = useCallback((msg: string) => setLoadError(msg), [])
  const handleCaptureDone = useCallback(() => setCapturing(false), [])

  return (
    <div className="fixed inset-0 z-50 flex flex-col bg-gray-950">
      {/* ------------------------------------------------------------------ */}
      {/* Toolbar                                                              */}
      {/* ------------------------------------------------------------------ */}
      <div className="flex items-center justify-between px-5 py-3 bg-gray-900 border-b border-gray-800 shrink-0">
        <span className="text-white font-semibold tracking-wide">3D Viewer</span>
        <div className="flex items-center gap-3">
          <button
            onClick={() => setCapturing(true)}
            disabled={capturing || !modelReady || loadError !== null}
            className="flex items-center gap-2 px-4 py-1.5 rounded-md bg-accent hover:bg-accent-hover disabled:opacity-40 disabled:cursor-not-allowed text-white text-sm font-medium transition-colors"
          >
            {capturing ? (
              <Loader2 size={15} className="animate-spin" />
            ) : (
              <Camera size={15} />
            )}
            {capturing ? 'Capturing…' : 'Capture Angle'}
          </button>

          <button
            onClick={onClose}
            className="p-1.5 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 transition-colors"
            aria-label="Close viewer"
          >
            <X size={20} />
          </button>
        </div>
      </div>

      {/* ------------------------------------------------------------------ */}
      {/* Viewport                                                             */}
      {/* ------------------------------------------------------------------ */}
      <div className="relative flex-1">
        {/* Error state */}
        {loadError && (
          <div className="absolute inset-0 flex flex-col items-center justify-center bg-gray-900 text-white gap-4 z-20">
            <AlertTriangle size={48} className="text-red-400" />
            <p className="text-lg font-semibold">Failed to load 3D model</p>
            <p className="text-sm text-gray-400 max-w-sm text-center">{loadError}</p>
            <button
              onClick={onClose}
              className="mt-2 px-4 py-2 rounded-md bg-gray-700 hover:bg-gray-600 text-sm transition-colors"
            >
              Close
            </button>
          </div>
        )}

        {/* Loading overlay – visible until model signals ready */}
        {!modelReady && !loadError && <LoadingOverlay />}

        {/* Three.js Canvas */}
        <Canvas
          camera={{ position: [0, 2, 5], fov: 45 }}
          gl={{ preserveDrawingBuffer: true }}
          style={{ width: '100%', height: '100%', background: '#111827' }}
        >
          {/* Lights */}
          <ambientLight intensity={0.5} />
          <directionalLight position={[5, 10, 7]} intensity={1.0} castShadow />
          <directionalLight position={[-5, -5, -5]} intensity={0.25} />

          {/* GLTF model */}
          <GltfErrorBoundary onError={handleError}>
            <Suspense fallback={null}>
              <ModelWithReady url={modelUrl} onReady={handleModelReady} />
            </Suspense>
          </GltfErrorBoundary>

          {/* Camera controls */}
          <OrbitControls enablePan enableZoom enableRotate minDistance={0.3} maxDistance={100} />

          {/* Environment map for PBR materials */}
          <Environment preset="city" />

          {/* Screenshot capture – only active when triggered */}
          {capturing && (
            <ScreenshotCapture
              enabled={capturing}
              cadFileId={cadFileId}
              onDone={handleCaptureDone}
            />
          )}
        </Canvas>
      </div>
    </div>
  )
}
