import { useState } from 'react'
import { Tooltip } from '@radix-ui/themes'
import { ArrowCounterClockwise, ButterflyIcon, CheckSquareIcon, FileTextIcon, FolderOpenIcon, GlobeHemisphereWestIcon, ImageSquareIcon, ListIcon, PencilSimpleIcon, PersonIcon, QuestionMarkIcon, SpeakerHigh, SpeakerSlash, SquareIcon, TerminalWindowIcon } from '@phosphor-icons/react'
import type { WorldEntry, WorldHoverPreview, WorldObjectAsset, WorldSceneProject } from '../types/world'
import { type ControllerMode, useDebugStore } from '../store/debug'
import { useAudioStore } from '../store/audio'
import { ViewerQuality } from '../types/world'
import { AppButton } from './AppButton'
import { ChromeThumbnail, chrome } from './AppChrome'

const CONTROLLER_MODES: readonly { mode: ControllerMode; label: string }[] = [
  { mode: 'fly', label: 'Fly' },
  { mode: 'fps', label: 'FPS' },
]

const QUALITY_MODES = [
  { mode: ViewerQuality.Low, label: 'Low' },
  { mode: ViewerQuality.High, label: 'High' },
] as const

function nextMode<T>(items: readonly { mode: T }[], current: T) {
  const index = items.findIndex((item) => item.mode === current)
  return items[(index + 1) % items.length].mode
}

interface Props {
  worlds: WorldEntry[]
  activeSlug: string
  setActiveSlug: (slug: string) => void
  setEditing: (editing: boolean) => void
  compact?: boolean
  activeSceneProject?: WorldSceneProject
  activeSceneProjectEnabled: boolean
  onActiveSceneProjectToggle: () => void
  activeWorldVersionIndex?: number
  hoveredObjectAssetId?: string | null
  hoveredObjectInstanceId?: string | null
  onObjectHover?: (asset: WorldObjectAsset, hovering: boolean, instanceId?: string) => void
  onWorldHover?: (preview: WorldHoverPreview, hovering: boolean) => void
  onActiveWorldVersionChange: (index: number) => void
}

export function WorldSidebar({
  worlds,
  activeSlug,
  setActiveSlug,
  setEditing,
  compact = false,
  activeSceneProject,
  activeSceneProjectEnabled,
  onActiveSceneProjectToggle,
  activeWorldVersionIndex,
  hoveredObjectAssetId,
  hoveredObjectInstanceId,
  onObjectHover,
  onWorldHover,
  onActiveWorldVersionChange,
}: Props) {
  const [menuOpen, setMenuOpen] = useState(false)
  const butterfliesEnabled = useDebugStore((s) => s.butterfliesEnabled)
  const setButterfliesEnabled = useDebugStore((s) => s.setButterfliesEnabled)
  const canOpenLocalFolders = import.meta.env.DEV

  const selectWorld = (slug: string) => {
    setActiveSlug(slug)
    setMenuOpen(false)
  }

  const openClaudeTerminal = () => {
    fetch('/__open-claude-terminal').catch((error) => {
      console.warn('Could not open Claude terminal.', error)
    })
  }

  const openWorldFolder = (slug: string) => {
    fetch(`/__open-world-folder?slug=${encodeURIComponent(slug)}`).catch((error) => {
      console.warn(`Could not open world folder for "${slug}".`, error)
    })
  }

  const openAssetFolder = (slug: string, target: 'scene' | 'world-asset' | 'object-asset', asset?: string) => {
    const params = new URLSearchParams({ slug, target })
    if (asset) params.set('asset', asset)
    fetch(`/__open-world-folder?${params.toString()}`).catch((error) => {
      console.warn(`Could not open ${target} folder for "${slug}".`, error)
    })
  }

  return (
    <aside className={`${chrome.enter} ${compact ? 'w-64 max-w-[calc(100vw-2rem)]' : 'w-full sm:w-64 max-h-[80vh]'} flex flex-col gap-1 whitespace-nowrap text-sm`}>
      <div className={`${chrome.bar} flex flex-shrink-0 items-center justify-between px-2 py-1 text-sm font-medium font-mono`}>
        <AppButton
          onClick={() => {
            if (!compact) setMenuOpen((open) => !open)
          }}
          className="min-w-0 flex-1 gap-2 px-1 truncate font-mono text-white opacity-100 hover:bg-transparent"
          aria-expanded={compact ? undefined : menuOpen}
        >
          {!compact && <ListIcon size={16} weight="regular" className="text-white/60 sm:hidden" />}
          <span>Loaded Models</span>{activeSlug && <span className="text-white/40 sm:hidden md:hidden">/ {activeSlug}</span>}
        </AppButton>
        <AppButton
          onClick={() => setButterfliesEnabled(!butterfliesEnabled)}
          active={butterfliesEnabled}
          className={`h-7 w-7 justify-center p-1 text-white ${butterfliesEnabled ? 'bg-white/15' : ''}`}
          aria-label={butterfliesEnabled ? 'Hide butterflies' : 'Show butterflies'}
          aria-pressed={butterfliesEnabled}
          title={butterfliesEnabled ? 'Hide butterflies' : 'Show butterflies'}
        >
          <ButterflyIcon size={16} weight={butterfliesEnabled ? 'fill' : 'regular'} />
        </AppButton>
        <a
          href="https://github.com/Jeprary/SparkSplat"
          target="_blank"
          rel="noreferrer"
          className="inline-flex h-7 w-7 items-center justify-center rounded p-1 text-white opacity-80 transition-[background-color,opacity] hover:bg-white/10 hover:opacity-100"
          aria-label="Open SparkSplat GitHub repository"
          title="View source on GitHub"
        >
          <span className="text-sm leading-none"><QuestionMarkIcon size={16} weight="regular" /></span>
        </a>
      </div>

      <SidebarViewerControls />

      {!compact && <div
        className={`
          ${chrome.panel} min-h-0 flex flex-1 flex-col gap-1 overflow-hidden p-1.5
          transition-[opacity,transform,max-height] duration-200 ease-out sm:max-h-[calc(90vh-3rem)] sm:translate-y-0 sm:opacity-100
          ${menuOpen ? 'max-h-[calc(90vh-3rem)] translate-y-0 opacity-100' : 'max-h-0 -translate-y-2 opacity-0 pointer-events-none sm:pointer-events-auto'}
        `}
      >
        <div className="w-full min-w-0 flex-1 overflow-y-auto overflow-x-hidden">
          <div className="flex min-w-0 flex-col gap-0 pr-1">
            {worlds.map(({ slug, project, world, worldVersions, objectAssets, sceneProject, sourceImageUrl }) => {
              const isActive = slug === activeSlug
              const name = project.display_name || slug
              const projectLoaded = isActive ? activeSceneProject : sceneProject
              const latestVersion = worldVersions[worldVersions.length - 1]
              const selectedVersionIndex = isActive ? activeWorldVersionIndex : latestVersion?.index
              const selectedVersion = worldVersions.find((version) => version.index === selectedVersionIndex) ?? latestVersion
              const displayWorld = selectedVersion?.world ?? world
              const hasSplatFile = Boolean(displayWorld && (displayWorld.assets.splats.ply_url || Object.values(displayWorld.assets.splats.spz_urls || {}).some(Boolean)))
              const hasWorldRow = Boolean(selectedVersion || hasSplatFile)
              const worldLoading = Boolean(selectedVersion && !selectedVersion.complete && isLoadingStatus(selectedVersion.status))
              const sourcePreview: WorldHoverPreview = {
                slug,
                imageUrl: sourceImageUrl,
                alt: `${name} source image`,
              }
              const activeWorldPreview: WorldHoverPreview = {
                slug,
                imageUrl: selectedVersion?.plateImageUrl,
                alt: `${name} world generation image`,
              }
              return (
                <div key={slug} className="rounded">
                  <div
                    className={`
                      min-w-0 flex items-center gap-1 rounded
                      ${isActive ? 'border-white/50 bg-white/20' : ''}
                    `}
                    onMouseEnter={() => {
                      if (!isActive) onWorldHover?.(sourcePreview, true)
                    }}
                    onMouseLeave={() => {
                      if (!isActive) onWorldHover?.(sourcePreview, false)
                    }}
                  >
                    <AppButton
                      onClick={() => selectWorld(slug)}
                      active={isActive}
                      className={`
                        min-w-0 flex flex-1 items-center rounded py-2 text-left
                        ${isActive ? 'hover:bg-transparent' : ''}
                      `}
                    >
                      <span className="block min-w-0 flex-1 truncate text-sm font-medium leading-tight text-white">{slug}</span>
                    </AppButton>

                    {canOpenLocalFolders && isActive && (
                      <Tooltip
                        content={`Open the local folder for ${name}`}
                        delayDuration={0}
                        side="right"
                      >
                        <AppButton
                          onClick={() => openWorldFolder(slug)}
                          className="h-8 w-8 flex-shrink-0 justify-center text-white"
                          aria-label={`Open local folder for ${name}`}
                          title={`Open local folder for ${name}`}
                        >
                          <FolderOpenIcon size={15} weight="regular" />
                        </AppButton>
                      </Tooltip>
                    )}
                  </div>

                  <div
                    className={`
                      overflow-hidden transition-all duration-300 ease-in-out
                      ${isActive ? 'max-h-[32rem]' : 'max-h-0'}
                    `}
                  >
                    <div className="mt-1 flex min-w-0 flex-col">
                      {isActive && projectLoaded && (
                        <div className="group flex min-w-0 items-center gap-1 rounded px-2 py-1 text-left text-white/80">
                          <span className="relative flex h-7 w-7 flex-shrink-0 items-center justify-center rounded bg-white/10 text-white/45 ring-1 ring-white/10">
                            <FileTextIcon size={14} weight="regular" />
                            <FileExtensionBadge extension=".json" />
                          </span>
                          <span className="min-w-0 flex-1 truncate text-xs font-medium leading-tight text-white/80">
                            scene.json
                          </span>
                          {canOpenLocalFolders && (
                            <AppButton
                              onClick={() => openAssetFolder(slug, 'scene')}
                              className="h-7 w-7 flex-shrink-0 justify-center p-1 text-white opacity-0 transition-opacity group-hover:opacity-90 focus-visible:opacity-100 hover:opacity-100"
                              aria-label={`Open world folder for ${name}`}
                              title={`Open world folder for ${name}`}
                            >
                              <FolderOpenIcon size={14} weight="regular" />
                            </AppButton>
                          )}
                          <AppButton
                            onClick={onActiveSceneProjectToggle}
                            active={activeSceneProjectEnabled}
                            className="h-6 w-6 flex-shrink-0 justify-center p-1 text-white/70"
                            aria-label={activeSceneProjectEnabled ? 'Disable scene.json' : 'Enable scene.json'}
                            aria-pressed={activeSceneProjectEnabled}
                            title={activeSceneProjectEnabled ? 'Disable scene.json' : 'Enable scene.json'}
                          >
                            {activeSceneProjectEnabled ? (
                              <CheckSquareIcon size={14} weight="bold" />
                            ) : (
                              <SquareIcon size={14} weight="regular" />
                            )}
                          </AppButton>
                        </div>
                      )}
                      {hasWorldRow && (
                        <div
                          className="group flex min-w-0 items-center gap-1 rounded"
                          onMouseEnter={() => onWorldHover?.(activeWorldPreview, true)}
                          onMouseLeave={() => onWorldHover?.(activeWorldPreview, false)}
                        >
                          <div className="min-w-0 flex flex-1 items-center gap-2 rounded px-2 py-1 text-left text-white opacity-80">
                            <span className="relative flex h-7 w-7 flex-shrink-0">
                              {activeWorldPreview.imageUrl ? (
                                <ChromeThumbnail thumbnailUrl={activeWorldPreview.imageUrl} alt={activeWorldPreview.alt} />
                              ) : (
                                <span className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded bg-white/10 text-white/45 ring-1 ring-white/10">
                                  <GlobeHemisphereWestIcon size={14} weight="regular" />
                                </span>
                              )}
                              {hasSplatFile && <FileExtensionBadge extension={displayWorld?.assets.splats.ply_url ? (displayWorld.assets.splats.ply_url.match(/\.[a-zA-Z]+$/)?.[0]?.toLowerCase() ?? ".ply") : ".spz"} />}
                            </span>
                            <span className="min-w-0 flex-1 text-white/85 text-xs font-semibold leading-tight truncate">
                              {slug}
                            </span>
                            {worldLoading && <LoadingSpinner />}
                            {isActive && worldVersions.length > 1 && selectedVersion && (
                              <select
                                value={selectedVersion.index}
                                className="h-5 flex-shrink-0 rounded border border-white/10 bg-white/5 px-1 text-[10px] leading-none text-white/60"
                                aria-label={`Select world version for ${name}`}
                                onChange={(event) => onActiveWorldVersionChange(Number(event.target.value))}
                              >
                                {worldVersions.map((version) => (
                                  <option key={version.index} value={version.index}>
                                    {version.label}
                                  </option>
                                ))}
                              </select>
                            )}
                            {!isActive && worldVersions.length > 1 && (
                              <span className="flex-shrink-0 rounded border border-white/10 bg-white/5 px-1.5 py-0.5 text-[10px] leading-none text-white/45">
                                {worldVersions.length}
                              </span>
                            )}
                          </div>
                          {canOpenLocalFolders && (
                            <AppButton
                              onClick={() => openAssetFolder(slug, 'world-asset')}
                              className="h-7 w-7 flex-shrink-0 justify-center p-1 text-white opacity-0 transition-opacity group-hover:opacity-90 focus-visible:opacity-100 hover:opacity-100"
                              aria-label={`Open world asset folder for ${name}`}
                              title={`Open world asset folder for ${name}`}
                            >
                              <FolderOpenIcon size={14} weight="regular" />
                            </AppButton>
                          )}
                        </div>
                      )}
                      {objectAssets.map((obj) => {
                        const objectLoading = !obj.complete && isLoadingStatus(obj.status)
                        const objectPreview: WorldHoverPreview = {
                          slug: obj.assetId,
                          imageUrl: obj.referenceImageUrl ?? obj.thumbnailUrl,
                          alt: `${obj.name} reference image`,
                        }
                        return (
                          <div
                            key={obj.assetId}
                            className={`flex min-w-0 items-center gap-2 rounded px-2 py-1 text-left group ${
                              hoveredObjectAssetId === obj.assetId && !hoveredObjectInstanceId ? 'bg-white/10 opacity-100' : ''
                            } ${obj.complete ? '' : 'opacity-80'}`}
                            onMouseEnter={() => {
                              if (obj.complete) {
                                onObjectHover?.(obj, true)
                              } else {
                                onWorldHover?.(objectPreview, true)
                              }
                            }}
                            onMouseLeave={() => {
                              if (obj.complete) {
                                onObjectHover?.(obj, false)
                              } else {
                                onWorldHover?.(objectPreview, false)
                              }
                            }}
                          >
                            <span className="relative flex h-7 w-7 flex-shrink-0">
                              <ChromeThumbnail thumbnailUrl={obj.thumbnailUrl ?? obj.referenceImageUrl} alt={obj.name} />
                              {obj.complete && <FileExtensionBadge extension=".obj" />}
                            </span>
                            <span className="min-w-0 flex-1">
                              <span className="block text-white/80 text-xs font-medium leading-tight truncate">
                                {obj.name}
                              </span>
                            </span>
                            {objectLoading ? (
                              <LoadingSpinner />
                            ) : obj.index !== undefined && obj.index > 0 && obj.variantLabel && (
                              <span className="flex-shrink-0 rounded border border-white/10 bg-white/5 px-1.5 py-0.5 text-[10px] leading-none text-white/50">
                                {obj.variantLabel}
                              </span>
                            )}
                            {canOpenLocalFolders && (
                              <AppButton
                                onClick={() => openAssetFolder(slug, 'object-asset', obj.baseObjectId)}
                                className="h-7 w-7 flex-shrink-0 justify-center p-1 text-white opacity-0 transition-opacity group-hover:opacity-90 focus-visible:opacity-100 hover:opacity-100"
                                aria-label={`Open asset folder for ${obj.name}`}
                                title={`Open asset folder for ${obj.name}`}
                              >
                                <FolderOpenIcon size={14} weight="regular" />
                              </AppButton>
                            )}
                          </div>
                        )
                      })}
                    </div>
                  </div>
                </div>
              )
            })}
          </div>
        </div>
      </div>}
    </aside>
  )
}

function SidebarViewerControls() {
  const muted = useAudioStore((s) => s.muted)
  const toggleMuted = useAudioStore((s) => s.toggleMuted)
  const resetObjects = useDebugStore((s) => s.resetObjects)
  const controllerMode = useDebugStore((s) => s.controllerMode)
  const setControllerMode = useDebugStore((s) => s.setControllerMode)
  const viewerQuality = useDebugStore((s) => s.viewerQuality)
  const setViewerQuality = useDebugStore((s) => s.setViewerQuality)
  const currentControllerMode = CONTROLLER_MODES.find((item) => item.mode === controllerMode) ?? CONTROLLER_MODES[0]
  const currentQuality = QUALITY_MODES.find((item) => item.mode === viewerQuality) ?? QUALITY_MODES[0]

  return (
    <div className={`${chrome.bar} flex flex-shrink-0 items-center gap-1 text-sm font-medium font-mono`}>
      <Tooltip content="Reset" delayDuration={0} side="right">
        <AppButton
          onClick={resetObjects}
          className="h-7 w-7 justify-center p-1 text-white"
          aria-label="Reset"
          title="Reset"
        >
          <ArrowCounterClockwise size={16} weight="bold" />
        </AppButton>
      </Tooltip>
      <Tooltip content={muted ? 'Unmute' : 'Mute'} delayDuration={0} side="right">
        <AppButton
          onClick={toggleMuted}
          className="h-7 w-7 justify-center p-1 text-white"
          aria-label={muted ? 'Unmute' : 'Mute'}
          title={muted ? 'Unmute' : 'Mute'}
        >
          {muted ? <SpeakerSlash size={16} weight="fill" /> : <SpeakerHigh size={16} weight="fill" />}
        </AppButton>
      </Tooltip>
      <Tooltip content="Change controller" delayDuration={0} side="right">
        <AppButton
          onClick={() => setControllerMode(nextMode(CONTROLLER_MODES, controllerMode))}
          className="h-7 w-20 justify-center gap-1 flex border flex-1 border-white/20 text-white"
          aria-label="Change controller"
          title="Change controller"
        >
          <PersonIcon size={15} weight="fill" className="flex-shrink-0 text-white/45" />
          <span>{currentControllerMode.label}</span>
        </AppButton>
      </Tooltip>
      <Tooltip content="Change quality" delayDuration={0} side="right">
        <AppButton
          onClick={() => setViewerQuality(nextMode(QUALITY_MODES, viewerQuality))}
          className="h-7 w-16 justify-center gap-1 flex border flex-1 border-white/20 text-white"
          aria-label="Change quality"
          title="Change quality"
        >
          <ImageSquareIcon size={15} weight="fill" className="flex-shrink-0 text-white/45" />
          <span>{currentQuality.label}</span>
        </AppButton>
      </Tooltip>
    </div>
  )
}

function FileExtensionBadge({ extension }: { extension: string }) {
  return (
    <span className="pointer-events-none absolute bottom-0 right-0 rounded-sm bg-black/70 px-px font-mono text-[6px] font-semibold leading-[7px] text-white/70 ring-1 ring-white/10">
      {extension}
    </span>
  )
}

function LoadingSpinner() {
  return (
    <span className="pointer-events-none h-3 w-3 flex-shrink-0 rounded-full border border-white/25 border-t-white/90 animate-spin" />
  )
}

function isLoadingStatus(status?: string) {
  if (!status) return true
  return ['queued', 'submitted', 'running', 'processing', 'in_progress'].includes(status.toLowerCase())
}
