import { useEffect, useRef } from 'react'
import { createPortal } from 'react-dom'
import * as THREE from 'three'
import { useCesiumViewer } from '../cesium/cesiumContext'
import { useMapStore } from '../store/mapStore'
import { useSplatStore } from '../store/splatStore'
import { syncSplatCamera } from './useSplatCamera'
import { getSplatDownloadUrl } from './splatLoader'
import { buildSplatWorldMatrix } from '../cesium/geoUtils'
import { fetchSplatDetail } from '../api/splats'

// Only render the splat when the camera is below this altitude
const RENDER_HEIGHT = 500

export function SplatRenderer() {
  const viewer = useCesiumViewer()
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const splatViewerRef = useRef<unknown>(null)
  const camerRef = useRef<THREE.PerspectiveCamera>(new THREE.PerspectiveCamera())
  const activeSplatId = useMapStore((s) => s.activeSplatId)
  const cameraHeight = useMapStore((s) => s.cameraHeight)
  const { setSplatDetail, splatCache } = useSplatStore()

  // Keep canvas dimensions in sync with Cesium canvas
  useEffect(() => {
    const cesiumCanvas = viewer.canvas
    const overlayCanvas = canvasRef.current
    if (!overlayCanvas) return

    function syncSize() {
      overlayCanvas!.width = cesiumCanvas.width
      overlayCanvas!.height = cesiumCanvas.height
      camerRef.current.aspect = cesiumCanvas.width / cesiumCanvas.height
      camerRef.current.updateProjectionMatrix()
    }

    syncSize()
    const observer = new ResizeObserver(syncSize)
    observer.observe(cesiumCanvas)
    return () => observer.disconnect()
  }, [viewer])

  // Load / unload splat when activeSplatId changes
  useEffect(() => {
    if (!activeSplatId) {
      disposeSplatViewer()
      return
    }

    let cancelled = false

    async function loadSplat() {
      if (!activeSplatId) return

      // Fetch detail if not cached
      let detail = splatCache.get(activeSplatId)
      if (!detail) {
        detail = await fetchSplatDetail(activeSplatId)
        if (cancelled) return
        setSplatDetail(activeSplatId, detail)
      }

      if (!detail.location || !detail.is_published) return

      const url = await getSplatDownloadUrl(activeSplatId)
      if (cancelled) return

      // Dynamically import the library to keep initial bundle lean
      const { Viewer: GaussianViewer } = await import('@mkkellogg/gaussian-splats-3d')
      if (cancelled) return

      const canvas = canvasRef.current
      if (!canvas) return

      disposeSplatViewer()

      const gViewer = new GaussianViewer({
        selfDrivenMode: false,
        useBuiltInControls: false,
        renderer: new THREE.WebGLRenderer({ canvas, alpha: true }),
        camera: camerRef.current,
      })

      // Geo-anchor the splat
      const [lon, lat] = detail.location.coordinates
      const alt = detail.altitude ?? 0
      const heading = detail.heading ?? 0
      const worldMatrix = buildSplatWorldMatrix(lon, lat, alt, heading)

      await gViewer.addSplatScene(url, {
        progressiveLoad: true,
        onProgress: () => { /* optional: update progress UI */ },
      })

      if (cancelled) {
        gViewer.dispose()
        return
      }

      // Apply geo-anchor transform to the loaded scene
      const scene = gViewer.splatMesh
      if (scene) {
        scene.matrixAutoUpdate = false
        scene.matrix.copy(worldMatrix)
        scene.matrixWorld.copy(worldMatrix)
      }

      splatViewerRef.current = gViewer
    }

    loadSplat().catch(console.error)

    return () => {
      cancelled = true
    }
  }, [activeSplatId]) // eslint-disable-line react-hooks/exhaustive-deps

  // Drive the splat render loop from Cesium's postRender event
  useEffect(() => {
    const remove = viewer.scene.postRender.addEventListener(() => {
      const gViewer = splatViewerRef.current as import('@mkkellogg/gaussian-splats-3d').Viewer | null
      if (!gViewer || cameraHeight > RENDER_HEIGHT) return

      const canvas = canvasRef.current
      if (!canvas) return

      syncSplatCamera(viewer, camerRef.current, canvas)
      gViewer.update()
      gViewer.render()
    })
    return () => remove()
  }, [viewer, cameraHeight])

  function disposeSplatViewer() {
    const gViewer = splatViewerRef.current as { dispose?: () => void } | null
    if (gViewer?.dispose) gViewer.dispose()
    splatViewerRef.current = null

    // Clear the overlay canvas
    const canvas = canvasRef.current
    if (canvas) {
      const ctx = canvas.getContext('2d')
      ctx?.clearRect(0, 0, canvas.width, canvas.height)
    }
  }

  // Overlay canvas portal — sits above the Cesium canvas, no pointer events
  const overlayCanvas = (
    <canvas
      ref={canvasRef}
      style={{
        position: 'fixed',
        inset: 0,
        pointerEvents: 'none',
        zIndex: 10,
      }}
    />
  )

  return createPortal(overlayCanvas, document.body)
}
