import { Suspense, lazy, useCallback, useEffect, useRef, useState } from 'react'
import { WorldViewer } from './components/WorldViewer'
import { WorldSidebar } from './components/WorldSidebar'
import { BottomLeftControls, ViewerModeHotkeys } from './components/BottomLeftControls'
import { TouchControls } from './components/TouchControls'
import { useSceneProject } from './modules/scene/useSceneProject'
import { fetchWorlds, loadWorlds } from './utils/worldLoader'
import { useDebugStore } from './store/debug'
import { isEditableTarget } from './utils/dom'
import type { WorldEntry, WorldHoverPreview, WorldObjectAsset, World } from './types/world'

import { UploadOverlay } from './components/UploadOverlay'

const LevaPanel = import.meta.env.DEV
  ? lazy(() => import('leva').then((module) => ({ default: module.Leva })))
  : null
const DebugPanel = import.meta.env.DEV
  ? lazy(() => import('./components/DebugPanel').then((module) => ({ default: module.DebugPanel })))
  : null

export function App() {
  const [worlds, setWorlds] = useState(loadWorlds)
  const [refreshingWorlds, setRefreshingWorlds] = useState(false)
  const [uploading, setUploading] = useState(false)
  const [uploadProgress, setUploadProgress] = useState(0)
  const refreshTimeoutRef = useRef<number | undefined>(undefined)
  const [activeSlug, setActiveSlug] = useState<string | undefined>(worlds[0]?.slug)

  const refreshWorlds = useCallback(async () => {
    if (!import.meta.env.DEV) return
    setRefreshingWorlds(true)
    try {
      const fetched = await fetchWorlds()
      let combined: WorldEntry[] = []
      setWorlds((prev) => {
        const locals = prev.filter(w => w.slug.startsWith('local-'))
        combined = [...locals, ...fetched]
        return combined
      })
      setActiveSlug((prevSlug) => {
        if (combined.length && !combined.find((w) => w.slug === prevSlug)) {
          return combined[0].slug
        }
        return prevSlug
      })
    } catch (error) {
      console.warn('Could not refresh local world assets.', error)
    } finally {
      setRefreshingWorlds(false)
    }
  }, [])

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

  useEffect(() => {
    if (!import.meta.env.DEV) return

    const refreshSoon = () => {
      window.clearTimeout(refreshTimeoutRef.current)
      refreshTimeoutRef.current = window.setTimeout(() => {
        void refreshWorlds()
      }, 150)
    }

    import.meta.hot?.on('models-changed', refreshSoon)
    return () => {
      window.clearTimeout(refreshTimeoutRef.current)
      import.meta.hot?.off('models-changed', refreshSoon)
    }
  }, [refreshWorlds])

  const handleDrop = async (e: React.DragEvent) => {
    e.preventDefault()
    const files = e.dataTransfer.files
    if (!files.length) return
    const file = files[0]
    const lowerName = file.name.toLowerCase()
    if (!lowerName.endsWith('.ply') && !lowerName.endsWith('.spz') && !lowerName.endsWith('.splat') && !lowerName.endsWith('.ksplat') && !lowerName.endsWith('.sog')) return
    
    // Create a local object URL to view the file instantly without uploading it to the server!
    const objectUrl = URL.createObjectURL(file)
    const isSpz = lowerName.endsWith('.spz')
    
    const worldId = 'local-' + Date.now()
    const newWorld: World = {
      world_id: worldId,
      display_name: file.name,
      assets: {
        splats: {
          ply_url: isSpz ? undefined : objectUrl,
          spz_urls: isSpz ? { full_res: objectUrl } : {},
          semantics_metadata: { metric_scale_factor: 1, ground_plane_offset: 0, flip_y: true }
        },
        mesh: { collider_mesh_url: '' },
        imagery: { pano_url: '' },
        thumbnail_url: '',
        caption: ''
      },
      world_marble_url: '',
      tags: [],
      world_prompt: '',
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString()
    }

    const newEntry: WorldEntry = {
      slug: worldId,
      project: { slug: worldId, display_name: file.name },
      world: newWorld,
      worldVersions: [{ index: 1, label: 'v1', complete: true, world: newWorld }],
      objectAssets: [],
      allObjectAssets: [],
      sourceImageVersions: [],
      worldSfxUrls: []
    }

    setWorlds(prev => [newEntry, ...prev])
    setActiveSlug(worldId)
  }

  const handleDragOver = (e: React.DragEvent) => {
    e.preventDefault()
  }

  if (!worlds.length && !uploading) {
    return (
      <div 
        onDrop={handleDrop} 
        onDragOver={handleDragOver} 
        className="flex items-center justify-center h-screen text-white bg-black"
      >
        Drag and drop a .ply, .spz, .splat, .ksplat, or .sog file here to begin
      </div>
    )
  }

  return (
    <div onDrop={handleDrop} onDragOver={handleDragOver} className="w-screen h-screen relative bg-black">
      <LoadedApp
        worlds={worlds}
        activeSlug={activeSlug}
        setActiveSlug={setActiveSlug}
        refreshingWorlds={refreshingWorlds}
        onRefreshWorlds={refreshWorlds}
      />
      {uploading && <UploadOverlay progress={uploadProgress} />}
    </div>
  )
}

function LoadedApp({
  worlds,
  activeSlug,
  setActiveSlug,
  refreshingWorlds,
  onRefreshWorlds,
}: {
  worlds: WorldEntry[]
  activeSlug: string | undefined
  setActiveSlug: (slug: string) => void
  refreshingWorlds: boolean
  onRefreshWorlds: () => void
}) {
  const [editing, setEditing] = useState(false)
  const levaCollapsed = useDebugStore((s) => s.levaCollapsed)
  const setLevaCollapsed = useDebugStore((s) => s.setLevaCollapsed)
  const [uiHidden, setUiHidden] = useState(false)
  const [sceneProjectEnabled, setSceneProjectEnabled] = useState(true)
  const [selectedWorldVersions, setSelectedWorldVersions] = useState<Record<string, number>>({})
  const [hoveredObjectAssetId, setHoveredObjectAssetId] = useState<string | null>(null)
  const [hoveredObjectInstanceId, setHoveredObjectInstanceId] = useState<string | null>(null)
  const [hoveredWorldPreview, setHoveredWorldPreview] = useState<WorldHoverPreview | null>(null)

  const slug = activeSlug ?? worlds[0]?.slug
  const entry = worlds.find((w) => w.slug === slug) ?? worlds[0]
  const showLeva = import.meta.env.VITE_SHOW_LEVA === 'true'
  const uiVisible = !uiHidden
  const defaultWorldVersionIndex = entry?.worldVersions[entry.worldVersions.length - 1]?.index
  const activeWorldVersionIndex = selectedWorldVersions[entry?.slug] ?? defaultWorldVersionIndex
  const activeWorldVersion = entry?.worldVersions.find((version) => version.index === activeWorldVersionIndex)
  const activeWorld = activeWorldVersion?.world ?? entry?.world
  const renderableObjectAssets = entry?.objectAssets.filter((asset) => asset.complete && asset.url) || []
  const renderableAllObjectAssets = entry?.allObjectAssets.filter((asset) => asset.complete && asset.url) || []
  const { sceneProject, sceneProjectReady, updateSceneProject } = useSceneProject(entry?.slug || '', '', entry?.sceneProject)
  const sceneProjectActive = Boolean(sceneProject && sceneProjectEnabled)

  useEffect(() => {
    setSceneProjectEnabled(true)
    setHoveredObjectAssetId(null)
    setHoveredObjectInstanceId(null)
    setHoveredWorldPreview(null)
  }, [entry?.slug])

  const handleObjectHover = useCallback((asset: WorldObjectAsset, hovering: boolean, instanceId?: string) => {
    setHoveredObjectAssetId((current) => {
      if (hovering) return asset.assetId
      return current === asset.assetId ? null : current
    })
    setHoveredObjectInstanceId((current) => {
      if (hovering) return instanceId ?? null
      return current === instanceId ? null : current
    })
  }, [])

  const handleWorldHover = useCallback((preview: WorldHoverPreview, hovering: boolean) => {
    setHoveredWorldPreview((current) => {
      if (hovering) return preview
      return current?.slug === preview.slug ? null : current
    })
  }, [])

  useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      if (isEditableTarget(event.target)) return
      if (event.code !== 'Backquote') return
      event.preventDefault()
      setUiHidden((hidden) => !hidden)
    }

    window.addEventListener('keydown', onKeyDown)
    return () => window.removeEventListener('keydown', onKeyDown)
  }, [])

  if (!entry) return null

  return (
    <div className="relative w-screen h-screen bg-black overflow-hidden select-none [&_*]:focus:outline-none [&_*]:focus-visible:outline-none [&_*]:focus:ring-0 [&_*]:focus-visible:ring-0">
      <ViewerModeHotkeys />
      {!editing && LevaPanel && DebugPanel && showLeva && uiVisible && (
        <div className="hidden md:block">
          <Suspense fallback={null}>
            <LevaPanel
              collapsed={{ collapsed: levaCollapsed, onChange: setLevaCollapsed }}
              theme={{ sizes: { rootWidth: '380px', controlWidth: '180px' } }}
            />
            <DebugPanel />
          </Suspense>
        </div>
      )}
      <WorldViewer
        world={activeWorld}
        slug={entry.slug}
        sourceImageUrl={entry.sourceImageUrl}
        hoveredWorldPreview={hoveredWorldPreview}
        objectAssets={renderableObjectAssets}
        allObjectAssets={renderableAllObjectAssets}
        worldSfxUrls={entry.worldSfxUrls}
        sceneProject={editing || sceneProjectEnabled ? sceneProject : undefined}
        sceneProjectReady={sceneProjectReady}
        hoveredObjectAssetId={hoveredObjectAssetId}
        hoveredObjectInstanceId={hoveredObjectInstanceId}
        editing={editing}
        setEditing={setEditing}
        uiVisible={uiVisible}
        onObjectHover={handleObjectHover}
        onSceneProjectSaved={updateSceneProject}
        onRefreshWorlds={onRefreshWorlds}
        refreshingWorlds={refreshingWorlds}
      />
      {uiVisible && (
        <div className={`fixed inset-x-4 top-4 sm:left-4 sm:right-auto ${editing ? 'z-30' : 'z-10'}`}>
          <WorldSidebar
            worlds={worlds}
            activeSlug={entry.slug}
            setActiveSlug={setActiveSlug}
            setEditing={setEditing}
            compact={editing}
            activeSceneProject={sceneProject}
            activeSceneProjectEnabled={sceneProjectActive}
            onActiveSceneProjectToggle={() => setSceneProjectEnabled((enabled) => !enabled)}
            activeWorldVersionIndex={activeWorldVersionIndex}
            hoveredObjectAssetId={hoveredObjectAssetId}
            hoveredObjectInstanceId={hoveredObjectInstanceId}
            onObjectHover={handleObjectHover}
            onWorldHover={handleWorldHover}
            onActiveWorldVersionChange={(index) => setSelectedWorldVersions((versions) => ({
              ...versions,
              [entry.slug]: index,
            }))}
          />
        </div>
      )}
      {!editing && uiVisible && (
        <>
          <TouchControls />
        </>
      )}
      {uiVisible && (
        <div className="fixed inset-x-0 bottom-4 z-20 flex justify-center px-4 sm:left-4 sm:right-auto sm:justify-start sm:px-0">
          <BottomLeftControls />
        </div>
      )}
    </div>
  )
}
