import type React from 'react'
import { memo, useState, useCallback, useEffect, useRef, type ReactNode } from 'react'
import { Show, SignInButton, UserButton, useAuth } from '@clerk/react'
import { Button, Chip, Modal, Spinner, Tabs, Toast, ToggleButton, ToggleButtonGroup, toast } from '@heroui/react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useStatus } from './hooks/useStatus'
import { BackendUnreachable } from './components/BackendUnreachable'
import { SettingsConfigForm } from './components/SettingsConfigForm'
import { LanguageSelector } from './components/LanguageSelector'
import { ThemeSelector } from './components/ThemeSelector'
import { HelpMenu } from './components/HelpMenu'
import { BattlegroupTab } from './tabs/BattlegroupTab'
import { LiveMapTab } from './tabs/LiveMapTab'
import { PlayersTab } from './tabs/PlayersTab'
import { DatabaseTab } from './tabs/DatabaseTab'
import { LogsTab } from './tabs/LogsTab'
import { BlueprintsTab } from './tabs/BlueprintsTab'
import { BasesTab } from './tabs/BasesTab'
import { GuildsTab } from './tabs/GuildsTab'
import { LandsraadTab } from './tabs/LandsraadTab'
import { StorageTab } from './tabs/StorageTab'
import { ServerSettingsTab } from './tabs/ServerSettingsTab'
import { DirectorTab } from './tabs/DirectorTab'
import { MarketTab } from './tabs/MarketTab'
import { WelcomePackageTab } from './tabs/WelcomePackageTab'
import { Icon, SideNav } from './dune-ui'
import { api } from './api/client'
import type { UpdateCheckResult } from './api/client'

const TAB_IDS = [
  'battlegroup',
  'players',
  'database',
  'logs',
  'blueprints',
  'bases',
  'guilds',
  'landsraad',
  'storage',
  'livemap',
  'server',
  'director',
  'market',
  'welcome',
] as const
type TabId = (typeof TAB_IDS)[number]
const DEFAULT_TAB: TabId = 'battlegroup'

function currentTabFromPath(pathname: string): TabId {
  const seg = pathname.replace(/^\//, '').split('/')[0]
  return (TAB_IDS as readonly string[]).includes(seg) ? (seg as TabId) : DEFAULT_TAB
}

type DbSection = 'backups' | 'tables' | 'describe' | 'sample' | 'search' | 'sql'
type WelcomeSection = 'config' | 'packages' | 'grants'
type LayoutMode = 'sidenav' | 'topnav'

// Memoized at module level so identity is stable — prevents all inactive tabs from
// re-rendering whenever AppCore re-renders (e.g. router location change, useStatus poll).
const MBattlegroupTab = memo(BattlegroupTab)
const MLiveMapTab = memo(LiveMapTab)
const MPlayersTab = memo(PlayersTab)
const MDatabaseTab = memo(DatabaseTab)
const MLogsTab = memo(LogsTab)
const MBlueprintsTab = memo(BlueprintsTab)
const MBasesTab = memo(BasesTab)
const MGuildsTab = memo(GuildsTab)
const MLandsraadTab = memo(LandsraadTab)
const MStorageTab = memo(StorageTab)
const MServerSettingsTab = memo(ServerSettingsTab)
const MDirectorTab = memo(DirectorTab)
const MMarketTab = memo(MarketTab)
const MWelcomePackageTab = memo(WelcomePackageTab)

const hasClerk = !!import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

interface AppCoreProps {
  isSignedIn: boolean
}

interface TabPaneProps {
  active: boolean
  children: ReactNode
}

interface ConnectionBadgeProps {
  label: string
  connected: boolean
}

function AppWithAuth() {
  const { isSignedIn } = useAuth()
  return <AppCore isSignedIn={!!isSignedIn} />
}

export const App: React.FC = () => {
  return hasClerk ? <AppWithAuth /> : <AppCore isSignedIn={true} />
}

const AppCore: React.FC<AppCoreProps> = ({ isSignedIn }) => {
  const { status, state: connState } = useStatus()
  const location = useLocation()
  const navigate = useNavigate()
  const { t, i18n } = useTranslation()
  const [reconnecting, setReconnecting] = useState(false)

  const DB_SECTIONS: { key: string, label: string, depth: number }[] = [
    { key: 'db:backups', label: `╰─ ${t('database.sections.backups')}`, depth: 1 },
    { key: 'db:tables', label: `╰─ ${t('database.sections.tables')}`, depth: 1 },
    { key: 'db:describe', label: `╰─ ${t('database.sections.describe')}`, depth: 1 },
    { key: 'db:sample', label: `╰─ ${t('database.sections.sample')}`, depth: 1 },
    { key: 'db:search', label: `╰─ ${t('database.sections.search')}`, depth: 1 },
    { key: 'db:sql', label: `╰─ ${t('database.sections.sql')}`, depth: 1 },
  ]

  const WELCOME_SECTIONS: { key: string, label: string, depth: number }[] = [
    { key: 'welcome:config', label: `╰─ ${t('welcome.sections.config')}`, depth: 1 },
    { key: 'welcome:packages', label: `╰─ ${t('welcome.sections.packages')}`, depth: 1 },
    { key: 'welcome:grants', label: `╰─ ${t('welcome.sections.grants')}`, depth: 1 },
  ]

  // Re-establish backend connections (DB + control plane) without a service
  // restart — used by the header Reconnect button when the DB shows disconnected
  // (e.g. dune-admin came up before the database was ready).
  const handleReconnect = async () => {
    setReconnecting(true)
    try {
      const s = await api.reconnect()
      if (s.db_connected) toast.success(t('app.reconnected'))
      else toast.danger(t('app.reconnectFailed', { error: 'database still unreachable' }))
    }
    catch (e) {
      toast.danger(t('app.reconnectFailed', { error: e instanceof Error ? e.message : String(e) }))
    }
    finally {
      setReconnecting(false)
    }
  }

  // Left-sidebar navigation, grouped to mirror the product's structure
  // (operator tooling today; a Player Portal group lands here later).
  const NAV_GROUPS: { title: string, items: { key: TabId, label: string }[] }[] = [
    {
      title: t('nav.groups.operations'),
      items: [
        { key: 'battlegroup' as TabId, label: t('nav.battlegroup') },
        { key: 'logs' as TabId, label: t('nav.logs') },
        { key: 'database' as TabId, label: t('nav.database') },
        { key: 'server' as TabId, label: t('nav.server') },
        { key: 'director' as TabId, label: t('nav.director') },
      ],
    },
    {
      title: t('nav.groups.playerWorld'),
      items: [
        { key: 'players' as TabId, label: t('nav.players') },
        { key: 'livemap' as TabId, label: t('nav.liveMap') },
        { key: 'storage' as TabId, label: t('nav.storage') },
        { key: 'bases' as TabId, label: t('nav.bases') },
        { key: 'guilds' as TabId, label: t('nav.guilds') },
        { key: 'landsraad' as TabId, label: t('nav.landsraad') },
        { key: 'blueprints' as TabId, label: t('nav.blueprints') },
      ],
    },
    {
      title: t('nav.groups.economy'),
      items: [
        { key: 'market' as TabId, label: t('nav.market') },
        { key: 'welcome' as TabId, label: t('nav.welcome') },
      ],
    },
  ]

  const [layoutMode, setLayoutMode] = useState<LayoutMode>(
    () => (localStorage.getItem('dune_admin_layout') === 'topnav' ? 'topnav' : 'sidenav'),
  )
  const setLayout = useCallback((m: LayoutMode) => {
    localStorage.setItem('dune_admin_layout', m)
    setLayoutMode(m)
  }, [])
  const [dbSection, setDbSection] = useState<DbSection>('backups')
  const [welcomeSection, setWelcomeSection] = useState<WelcomeSection>('config')
  const [showBackendConfig, setShowBackendConfig] = useState(false)
  const [updateInfo, setUpdateInfo] = useState<UpdateCheckResult | null>(null)
  const [showUpdateModal, setShowUpdateModal] = useState(false)
  const [updateChecking, setUpdateChecking] = useState(false)
  const [updateApplying, setUpdateApplying] = useState(false)
  const [formSaving, setFormSaving] = useState(false)
  const formSaveRef = useRef<(() => Promise<void>) | null>(null)

  useEffect(() => {
    const seg = location.pathname.replace(/^\//, '').split('/')[0]
    if (!seg || !(TAB_IDS as readonly string[]).includes(seg)) {
      navigate(`/${DEFAULT_TAB}`, { replace: true })
    }
  }, [location.pathname, navigate])

  const currentTab = currentTabFromPath(location.pathname)

  // Tracks which tabs have been visited at least once — they get mounted and stay
  // mounted (TabPane keeps them hidden), preserving in-tab state and the isActive
  // auto-refresh contract. Unvisited tabs never mount, avoiding the startup query storm.
  const [mounted, setMounted] = useState<Set<TabId>>(() => new Set<TabId>([currentTab]))
  useEffect(() => {
    setMounted((prev) => { // eslint-disable-line react-hooks/set-state-in-effect
      if (prev.has(currentTab)) return prev
      const next = new Set(prev)
      next.add(currentTab)
      return next
    })
  }, [currentTab])

  // Check for a newer release via the backend (it knows this build's version and
  // returns the release-notes URL) — drives the clickable header update widget (#129).
  useEffect(() => {
    api.update.check().then(setUpdateInfo).catch(() => {})
  }, [])

  const checkUpdate = async () => {
    setUpdateChecking(true)
    try {
      setUpdateInfo(await api.update.check())
    }
    catch {
      // silently ignore — user can retry
    }
    finally {
      setUpdateChecking(false)
    }
  }

  const applyUpdate = async (force = false) => {
    setUpdateApplying(true)
    try {
      const result = await api.update.apply(force)
      if (result.updated) {
        toast.success(force ? t('app.reinstalled', { version: result.version ?? 'latest' }) : t('app.updated', { version: result.version ?? 'latest' }))
        setUpdateInfo(null)
        setTimeout(() => {
          window.location.reload()
        }, 1500)
      }
      else {
        toast.info(result.message)
      }
    }
    catch (e) {
      toast.danger(t('app.updateFailed', { message: e instanceof Error ? e.message : String(e) }))
    }
    finally {
      setUpdateApplying(false)
    }
  }

  const renderTab = (id: TabId, node: ReactNode) => (
    <TabPane active={currentTab === id}>
      {mounted.has(id) ? node : null}
    </TabPane>
  )

  // #165: when the SPA never reached the backend, show an informative setup
  // screen instead of an empty, non-working dashboard.
  if (connState === 'error') {
    return <BackendUnreachable onRetry={() => window.location.reload()} />
  }

  return (
    // Keyed on the active language so switching language remounts the content
    // subtree once. The module-level memo() tabs stay mounted and otherwise keep
    // stale-language text on a language change (their props don't change), until
    // an unrelated local state update forces them to re-render (#123).
    <div key={i18n.language} className="h-screen flex flex-col overflow-hidden bg-background">
      <Toast.Provider />

      {/* Header */}
      <header
        className="flex items-center justify-between px-6 py-3 border-b border-border bg-surface shrink-0"
        style={{ background: 'linear-gradient(180deg, var(--surface-secondary) 0%, var(--surface) 100%)' }}
      >
        <div className="flex items-center gap-3">
          <Button
            variant="ghost"
            className="text-xl font-bold uppercase tracking-[0.2em] text-accent px-0 h-auto min-w-0 hover:opacity-80"
            onPress={() => navigate(`/${DEFAULT_TAB}`)}
            aria-label={t('app.goHome')}
          >
            {t('app.title')}
          </Button>
          {status?.control && status.control !== 'none' && <span className="text-xs text-muted">{status.control}</span>}
          {status?.ssh_host && <span className="text-xs text-muted">{status.ssh_host}</span>}
          {status?.db_host && status.control !== 'kubectl' && (
            <span className="text-xs text-muted">{status.db_host}</span>
          )}
          {status?.version && (
            <Button
              variant="ghost"
              className="text-xs text-muted hover:text-foreground px-0 h-auto min-w-0"
              onPress={() => setShowBackendConfig(true)}
              aria-label={t('app.openSettings')}
            >
              v
              {status.version}
            </Button>
          )}
          {updateInfo?.needs_update && (
            <button
              type="button"
              onClick={() => setShowUpdateModal(true)}
              aria-label={t('app.updateAvailable')}
              className="cursor-pointer border-0 bg-transparent p-0"
            >
              <Chip size="sm" color="warning" variant="soft">
                ↑
                {' '}
                {updateInfo.latest.replace(/^v/, '')}
              </Chip>
            </button>
          )}
        </div>

        <div className="flex items-center gap-3">
          {status?.executor === 'ssh' && <ConnectionBadge label="SSH" connected={status.ssh_connected} />}
          <ConnectionBadge label="DB" connected={status?.db_connected ?? false} />
          {status && !status.db_connected && (
            <Button
              size="sm"
              variant="outline"
              isDisabled={reconnecting}
              onPress={handleReconnect}
            >
              {reconnecting ? t('app.reconnecting') : t('app.reconnect')}
            </Button>
          )}
          {status?.pod_ns && (
            <span className="text-xs text-muted">
              ns:
              {status.pod_ns}
            </span>
          )}

          <HelpMenu status={status} />
          <ThemeSelector />
          <LanguageSelector />
          <ToggleButtonGroup
            selectionMode="single"
            disallowEmptySelection
            selectedKeys={[layoutMode]}
            onSelectionChange={(keys) => {
              const next = [...keys][0]
              if (next === 'sidenav' || next === 'topnav') setLayout(next)
            }}
          >
            <ToggleButton id="sidenav" isIconOnly aria-label={t('app.switchToSidenav')}>
              <Icon name="layout-panel-left" />
            </ToggleButton>
            <ToggleButton id="topnav" isIconOnly aria-label={t('app.switchToTopnav')}>
              <Icon name="layout-panel-top" />
            </ToggleButton>
          </ToggleButtonGroup>
          <Button
            size="sm"
            variant="outline"
            aria-label={t('app.configureBackend')}
            onPress={() => setShowBackendConfig((v) => !v)}
            className={showBackendConfig ? 'text-accent border-accent' : ''}
          >
            <Icon name="settings" />
            {' '}
            {t('app.settings')}
          </Button>

          {hasClerk && (
            <>
              <Show when="signed-out">
                <SignInButton>
                  <Button size="sm" variant="outline">
                    {t('app.signIn')}
                  </Button>
                </SignInButton>
              </Show>
              <Show when="signed-in">
                <UserButton />
              </Show>
            </>
          )}
        </div>
      </header>

      {/* Settings modal — structure mirrors BotControlPanel */}
      <Modal>
        <Modal.Backdrop isOpen={showBackendConfig} onOpenChange={(v) => !v && setShowBackendConfig(false)}>
          <Modal.Container size="cover" scroll="outside">
            <Modal.Dialog className="h-[92vh] flex flex-col">
              <Modal.CloseTrigger />
              <Modal.Header>
                <div className="flex items-baseline gap-6 flex-wrap">
                  <Modal.Heading className="text-accent">{t('app.settings')}</Modal.Heading>
                  {status && (
                    <div className="flex items-center gap-4 text-xs text-muted">
                      {status.version && (
                        <span className="font-mono">
                          v
                          {status.version}
                        </span>
                      )}
                      {status.control && status.control !== 'none' && <span>{status.control}</span>}
                      {status.commit && status.commit !== 'unknown' && (
                        <span className="font-mono opacity-60">{status.commit}</span>
                      )}
                    </div>
                  )}
                </div>
              </Modal.Header>

              {/* Body scrolls; form fills it with its own internal tab scroll */}
              <Modal.Body className="flex flex-col overflow-y-auto flex-1 min-h-0 pr-1">
                {showBackendConfig && (
                  <SettingsConfigForm saveRef={formSaveRef} onSavingChange={setFormSaving} />
                )}
              </Modal.Body>

              <Modal.Footer className="flex items-center gap-2">
                {/* Left: update controls — fixed positions so buttons don't shift */}
                <Button
                  size="sm"
                  variant="ghost"
                  onPress={checkUpdate}
                  isDisabled={updateChecking || updateApplying}
                >
                  {updateChecking
                    ? (
                        <>
                          <Spinner size="sm" color="current" />
                          {' '}
                          {t('common.checking')}
                        </>
                      )
                    : t('app.checkUpdates')}
                </Button>
                {updateInfo && !updateInfo.needs_update && (
                  <Button
                    size="sm"
                    variant="ghost"
                    onPress={() => applyUpdate(true)}
                    isDisabled={updateApplying}
                  >
                    {updateApplying ? <Spinner size="sm" color="current" /> : t('app.reinstall')}
                  </Button>
                )}
                {updateInfo?.needs_update && (
                  <Button size="sm" onPress={() => applyUpdate()} isDisabled={updateApplying}>
                    {updateApplying
                      ? <Spinner size="sm" color="current" />
                      : (
                          <span className="font-mono text-xs">
                            v
                            {updateInfo.current}
                            {' → '}
                            v
                            {updateInfo.latest.replace(/^v/, '')}
                          </span>
                        )}
                  </Button>
                )}

                {/* Spacer */}
                <span className="flex-1" />

                {/* Right: save + close */}
                <span className="text-xs text-muted">{t('app.changesNote')}</span>
                <Button
                  size="sm"
                  onPress={() => formSaveRef.current?.()}
                  isDisabled={formSaving}
                >
                  {formSaving
                    ? (
                        <>
                          <Spinner size="sm" color="current" />
                          {' '}
                          {t('common.saving')}
                        </>
                      )
                    : (
                        <>
                          <Icon name="save" />
                          {' '}
                          {t('app.saveApply')}
                        </>
                      )}
                </Button>
                <Button
                  size="sm"
                  variant="tertiary"
                  onPress={() => setShowBackendConfig(false)}
                >
                  {t('common.close')}
                </Button>
              </Modal.Footer>
            </Modal.Dialog>
          </Modal.Container>
        </Modal.Backdrop>
      </Modal>

      {/* Update-available prompt — opened from the header release widget (#129).
          Reuses the backend update check for the release-notes link + Continue/Cancel. */}
      <Modal>
        <Modal.Backdrop isOpen={showUpdateModal} onOpenChange={(v) => !v && setShowUpdateModal(false)}>
          <Modal.Container size="sm">
            <Modal.Dialog>
              <Modal.CloseTrigger />
              <Modal.Header>
                <Modal.Heading className="text-accent">{t('app.updateAvailable')}</Modal.Heading>
              </Modal.Header>
              <Modal.Body className="flex flex-col gap-3">
                <p className="text-sm text-muted">
                  {t('app.updateAvailableBody', {
                    current: updateInfo?.current ?? '',
                    latest: updateInfo?.latest?.replace(/^v/, '') ?? '',
                  })}
                </p>
                {updateInfo?.release_url && (
                  <a
                    href={updateInfo.release_url}
                    target="_blank"
                    rel="noreferrer"
                    className="inline-flex items-center gap-1 text-sm text-accent hover:opacity-80"
                  >
                    <Icon name="external-link" />
                    {' '}
                    {t('app.viewReleaseNotes')}
                  </a>
                )}
              </Modal.Body>
              <Modal.Footer className="flex items-center justify-end gap-2">
                <Button
                  size="sm"
                  variant="tertiary"
                  onPress={() => setShowUpdateModal(false)}
                >
                  {t('common.cancel')}
                </Button>
                <Button
                  size="sm"
                  onPress={() => {
                    setShowUpdateModal(false)
                    void applyUpdate()
                  }}
                  isDisabled={updateApplying}
                >
                  {updateApplying ? <Spinner size="sm" color="current" /> : t('app.updateNow')}
                </Button>
              </Modal.Footer>
            </Modal.Dialog>
          </Modal.Container>
        </Modal.Backdrop>
      </Modal>

      {/* Body: layout-conditional rendering.
          In sidenav mode: grouped left sidebar + content.
          In topnav mode: horizontal nav bar + full-width content.
          All tabs stay mounted (inactive hidden) so per-tab state and isActive
          auto-refresh behavior persist. */}
      {(() => {
        const databaseNode = layoutMode === 'topnav'
          ? <MDatabaseTab section={dbSection} showSubnav onSectionChange={setDbSection} />
          : <MDatabaseTab section={dbSection} />

        const welcomeNode = layoutMode === 'topnav'
          ? <MWelcomePackageTab section={welcomeSection} showSubnav onSectionChange={setWelcomeSection} />
          : <MWelcomePackageTab section={welcomeSection} />

        if (layoutMode === 'sidenav') {
          return (
            <div className="flex-1 flex gap-3 p-3 overflow-hidden min-h-0">
              <nav className="w-60 shrink-0 flex flex-col gap-2 overflow-y-auto">
                {/* Operations: rendered separately so Database can expand DB sub-items inline */}
                <SideNav
                  width="w-full"
                  title={NAV_GROUPS[0].title}
                  items={[
                    ...NAV_GROUPS[0].items.slice(0, 3),
                    ...(currentTab === 'database' ? DB_SECTIONS : []),
                    ...NAV_GROUPS[0].items.slice(3),
                  ] as { key: string, label: string, depth?: number }[]}
                  active={currentTab === 'database' ? `db:${dbSection}` : currentTab}
                  onSelect={(k: string) => {
                    if (k.startsWith('db:')) {
                      setDbSection(k.slice(3) as DbSection)
                      if (currentTab !== 'database') navigate('/database')
                    }
                    else {
                      navigate(`/${k}`)
                    }
                  }}
                />
                {/* Player World: unchanged */}
                <SideNav
                  key={NAV_GROUPS[1].title}
                  width="w-full"
                  title={NAV_GROUPS[1].title}
                  items={NAV_GROUPS[1].items}
                  active={currentTab}
                  onSelect={(k) => navigate(`/${k}`)}
                />
                {/* Economy: expand Welcome sub-items inline */}
                <SideNav
                  width="w-full"
                  title={NAV_GROUPS[2].title}
                  items={[
                    ...NAV_GROUPS[2].items,
                    ...(currentTab === 'welcome' ? WELCOME_SECTIONS : []),
                  ] as { key: string, label: string, depth?: number }[]}
                  active={currentTab === 'welcome' ? `welcome:${welcomeSection}` : currentTab}
                  onSelect={(k: string) => {
                    if (k.startsWith('welcome:')) {
                      setWelcomeSection(k.slice(8) as WelcomeSection)
                      if (currentTab !== 'welcome') navigate('/welcome')
                    }
                    else {
                      navigate(`/${k}`)
                    }
                  }}
                />
              </nav>
              <main className="flex-1 flex flex-col overflow-hidden min-h-0">
                {renderTab('battlegroup', <MBattlegroupTab isActive={currentTab === 'battlegroup'} />)}
                {renderTab('players', <MPlayersTab isActive={currentTab === 'players'} />)}
                {renderTab('database', databaseNode)}
                {renderTab('logs', <MLogsTab control={status?.control} />)}
                {renderTab('blueprints', <MBlueprintsTab isSignedIn={isSignedIn} />)}
                {renderTab('bases', <MBasesTab isSignedIn={isSignedIn} />)}
                {renderTab('guilds', <MGuildsTab isSignedIn={isSignedIn} />)}
                {renderTab('landsraad', <MLandsraadTab />)}
                {renderTab('storage', <MStorageTab />)}
                {renderTab('livemap', <MLiveMapTab isActive={currentTab === 'livemap'} />)}
                {renderTab('server', <MServerSettingsTab />)}
                {renderTab('director', <MDirectorTab />)}
                {renderTab('market', <MMarketTab />)}
                {renderTab('welcome', welcomeNode)}
              </main>
            </div>
          )
        }

        // topnav mode: horizontal nav bar + full-width content area
        return (
          <>
            <Tabs
              selectedKey={currentTab}
              onSelectionChange={(k) => navigate(`/${String(k)}`)}
              className="shrink-0 border-b border-border bg-surface"
            >
              <Tabs.ListContainer className="px-3 py-2 overflow-x-auto">
                <Tabs.List aria-label={t('app.title')}>
                  {NAV_GROUPS.flatMap((g) => g.items).map((item) => (
                    <Tabs.Tab key={item.key} id={item.key}>
                      {item.label}
                      <Tabs.Indicator />
                    </Tabs.Tab>
                  ))}
                </Tabs.List>
              </Tabs.ListContainer>
            </Tabs>
            <div className="flex-1 flex flex-col p-3 overflow-hidden min-h-0">
              <main className="flex-1 flex flex-col overflow-hidden min-h-0">
                {renderTab('battlegroup', <MBattlegroupTab isActive={currentTab === 'battlegroup'} />)}
                {renderTab('players', <MPlayersTab isActive={currentTab === 'players'} />)}
                {renderTab('database', databaseNode)}
                {renderTab('logs', <MLogsTab control={status?.control} />)}
                {renderTab('blueprints', <MBlueprintsTab isSignedIn={isSignedIn} />)}
                {renderTab('bases', <MBasesTab isSignedIn={isSignedIn} />)}
                {renderTab('guilds', <MGuildsTab isSignedIn={isSignedIn} />)}
                {renderTab('landsraad', <MLandsraadTab />)}
                {renderTab('storage', <MStorageTab />)}
                {renderTab('livemap', <MLiveMapTab isActive={currentTab === 'livemap'} />)}
                {renderTab('server', <MServerSettingsTab />)}
                {renderTab('director', <MDirectorTab />)}
                {renderTab('market', <MMarketTab />)}
                {renderTab('welcome', welcomeNode)}
              </main>
            </div>
          </>
        )
      })()}
    </div>
  )
}

function TabPane({ active, children }: TabPaneProps) {
  return (
    <div className={`flex-1 min-h-0 ${active ? 'flex flex-col dune-tab-active' : 'hidden'}`}>
      {children}
    </div>
  )
}

function ConnectionBadge({ label, connected }: ConnectionBadgeProps) {
  return (
    <div className="flex items-center gap-1.5 text-xs">
      <div className={`w-2 h-2 rounded-full ${connected ? 'bg-success' : 'bg-muted/40'}`} />
      <span className={connected ? 'text-foreground' : 'text-muted'}>{label}</span>
    </div>
  )
}
