import { useState, useEffect, useMemo, useRef, lazy, Suspense } from 'react'
import { FluentProvider, makeStyles, tokens } from '@fluentui/react-components'
import { Sidebar } from './components/Sidebar'
import { DownloadsView } from './views/DownloadsView'
import { Onboarding } from './views/Onboarding'
import { CommandPalette, type PaletteAction } from './components/CommandPalette'
import { LiveRegion } from './components/LiveRegion'
import { Toaster } from './components/ui/Toaster'
import { getTheme, pageBackground } from './lib/theme'
import { useSettings } from './store/settings'
import { useSetup } from './store/setup'
import { useNav } from './store/nav'
import { useDownloads } from './store/downloads'
// Eagerly load the sources store for its startup side-effects (load persisted
// sources + the watched-source / scheduled `--sync` kickoff) and its cross-store
// subscription (coordinator's downloadCompleted → markDownloaded). Before C2 this
// was guaranteed by downloads.ts importing it; now that the cycle is broken, App
// owns the eager load so it survives the views becoming lazy-loaded (PERF8).
import './store/sources'
import { queueSummaryOf } from './lib/queueStats'
import { useResolvedDark } from './store/systemTheme'
import { logError } from './reportError'

// Code-split the non-default tabs (PERF8): Downloads is the launch tab and stays
// eager, but Library/History/Terminal/Settings load their chunks on first visit so
// they (and their slice of Fluent) aren't in the initial bundle. The stores these
// views use are imported eagerly above (`useDownloads` + `./store/sources`, and
// downloads → history), so lazy-loading the *view* never delays store startup.
const LibraryView = lazy(() =>
  import('./views/LibraryView').then((m) => ({ default: m.LibraryView }))
)
const HistoryView = lazy(() =>
  import('./views/HistoryView').then((m) => ({ default: m.HistoryView }))
)
const TerminalView = lazy(() =>
  import('./views/TerminalView').then((m) => ({ default: m.TerminalView }))
)
const SettingsView = lazy(() =>
  import('./views/SettingsView').then((m) => ({ default: m.SettingsView }))
)

// How often to check whether a scheduled ('saved' + due) download should promote
// to the queue. 15s is plenty for the minute-granularity times the picker offers.
const SCHEDULE_TICK_MS = 15_000

const useStyles = makeStyles({
  provider: {
    height: '100vh'
  },
  root: {
    height: '100vh',
    display: 'flex',
    color: tokens.colorNeutralForeground1
  },
  content: {
    flexGrow: 1,
    minWidth: 0,
    // L161: <main> no longer scrolls or pads — each screen owns its scroll region
    // via the shared `page`/`fill` shells (ui/Screen.tsx), so a page scroll and an
    // internal list scroll can never stack. Padding (SPACE.page, UI4) lives on the
    // shells.
    display: 'flex',
    flexDirection: 'column',
    overflow: 'hidden'
  }
})

function App(): React.JSX.Element {
  const styles = useStyles()
  const theme = useSettings((s) => s.theme)
  const accentColor = useSettings((s) => s.accentColor)
  const updateSettings = useSettings((s) => s.update)
  const showTerminal = useSettings((s) => s.enableCustomCommands)
  const isDark = useResolvedDark()
  const tab = useNav((s) => s.tab)
  const setTab = useNav((s) => s.setTab)
  // Active-download count for the sidebar Downloads badge (UX9/UI25), so a run in
  // progress is visible from any tab. A number selector only re-renders when the
  // count actually changes -- not on every progress tick.
  const activeCount = useDownloads(
    (s) => s.items.filter((i) => i.status === 'downloading' || i.status === 'queued').length
  )
  const [paletteOpen, setPaletteOpen] = useState(false)
  // Track the element that was focused before the palette opened so we can restore it on close.
  const prePaletteRef = useRef<Element | null>(null)

  // Ctrl+K: command palette. Ctrl+,: Settings. (W8/W9)
  useEffect(() => {
    function onKey(e: KeyboardEvent): void {
      if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'K')) {
        e.preventDefault()
        prePaletteRef.current = document.activeElement
        setPaletteOpen((o) => !o)
      } else if (e.ctrlKey && e.key === ',') {
        e.preventDefault()
        setTab('settings')
      }
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [])

  // Mirror overall queue progress onto the Windows taskbar. Subscribe to the store
  // directly (not via a selector) so taskbar updates don't re-render the app.
  // Compare against the last IPC call and skip when nothing changed -- the store
  // fires on every progress tick and this avoids one IPC roundtrip per tick (L25).
  useEffect(() => {
    let lastFraction = -1
    let lastMode = ''
    let lastBadge = -1
    function push(items: ReturnType<typeof useDownloads.getState>['items']): void {
      const s = queueSummaryOf(items)
      const mode = s.active ? (s.failed > 0 ? 'error' : 'normal') : 'none'
      if (s.progress === lastFraction && mode === lastMode && s.downloading === lastBadge) return
      lastFraction = s.progress
      lastMode = mode
      lastBadge = s.downloading
      void window.api?.setTaskbarProgress?.({
        fraction: s.progress,
        mode,
        badgeCount: s.downloading
      })
    }
    push(useDownloads.getState().items)
    return useDownloads.subscribe((st) => push(st.items))
  }, [])

  // Rehydrate the persisted download queue on launch (M4) so saved/scheduled and
  // other pending items return from the previous session.
  useEffect(() => {
    useDownloads.getState().hydrate()
  }, [])

  // Subscribe to ffmpeg setup progress and run the initial readiness check, which drives
  // the first-run setup gate below. In an App effect (not at store module load) so it runs
  // after window.api is assigned in every context (real bridge and browser-preview mock).
  useEffect(() => useSetup.getState().init(), [])

  // UX16: focus the URL field on launch so the user can paste + type immediately
  // (Downloads is the default tab). Double-rAF waits for paint + the DownloadBar to
  // register its focuser (L118), matching the command palette's "New download" focus.
  useEffect(() => {
    if (useNav.getState().tab !== 'downloads') return
    requestAnimationFrame(() => requestAnimationFrame(() => useNav.getState().focusUrlField()))
  }, [])

  // Promote scheduled ('saved' + a due scheduledFor) items when their time arrives.
  // The queue is persisted (M4), so a scheduled item survives a quit and fires on the
  // next launch once its time has passed. A 15s tick is plenty for minute-granularity
  // times. Lives here (not at the downloads store's module load) so importing the store
  // in tests/preview doesn't start a stray timer, and the interval is cleared cleanly
  // on unmount (L1).
  useEffect(() => {
    const tick = setInterval(() => {
      const st = useDownloads.getState()
      const now = Date.now()
      if (
        st.items.some(
          (i) => i.status === 'saved' && i.scheduledFor != null && i.scheduledFor <= now
        )
      ) {
        st.promoteDueScheduled()
      }
    }, SCHEDULE_TICK_MS)
    return () => clearInterval(tick)
  }, [])

  // Memoized so CommandPalette sees a stable array reference between renders --
  // only rebuilds when the theme label needs to change (L32).
  const paletteActions = useMemo<PaletteAction[]>(
    () => [
      {
        id: 'go-downloads',
        label: 'Go to Downloads',
        hint: 'Navigate',
        run: () => setTab('downloads')
      },
      { id: 'go-library', label: 'Go to Library', hint: 'Navigate', run: () => setTab('library') },
      { id: 'go-history', label: 'Go to History', hint: 'Navigate', run: () => setTab('history') },
      {
        id: 'go-terminal',
        label: 'Go to Terminal',
        hint: 'Navigate',
        run: () => setTab('terminal')
      },
      {
        id: 'go-settings',
        label: 'Go to Settings',
        // Surface the real shortcut here so the palette doubles as shortcut
        // discovery (UX21).
        hint: 'Ctrl ,',
        run: () => setTab('settings')
      },
      {
        id: 'new-download',
        label: 'New download',
        hint: 'Focus URL',
        run: () => {
          setTab('downloads')
          // Double-rAF waits for React's re-render + paint (so the DownloadBar has
          // mounted and registered its focuser) before requesting focus (L118).
          requestAnimationFrame(() =>
            requestAnimationFrame(() => useNav.getState().focusUrlField())
          )
        }
      },
      {
        id: 'toggle-theme',
        label: isDark ? 'Switch to light theme' : 'Switch to dark theme',
        hint: 'Appearance',
        run: () => updateSettings({ theme: isDark ? 'light' : 'dark' })
      }
    ],
    [isDark, setTab, updateSettings]
  )

  // AeroFetch's own version, shown in the sidebar. Loaded once over IPC.
  const [version, setVersion] = useState('')
  useEffect(() => {
    window.api?.getAppVersion?.().then(setVersion).catch(logError('getAppVersion'))
  }, [])

  const collapsed = useSettings((s) => s.isSidebarCollapsed)
  function toggleCollapsed(): void {
    updateSettings({ isSidebarCollapsed: !collapsed })
  }
  // Gate on `loaded` so a returning user's real settings never get clobbered
  // by a one-frame flash of the (default-false) onboarding state.
  const loaded = useSettings((s) => s.loaded)
  const hasCompletedOnboarding = useSettings((s) => s.hasCompletedOnboarding)
  // Onboarding doubles as the one-time media-tools setup: show it for a fresh user, OR
  // whenever ffmpeg/ffprobe are confirmed missing (an existing user upgrading from a
  // bundled build). `=== false` (not falsy) so the null "still checking" state never
  // flashes the setup screen at a user who actually has ffmpeg.
  const ffmpegReady = useSetup((s) => s.ffmpegReady)
  const showOnboarding = loaded && (!hasCompletedOnboarding || ffmpegReady === false)

  return (
    <FluentProvider
      theme={getTheme(accentColor, isDark ? 'dark' : 'light')}
      className={styles.provider}
    >
      <div
        className={styles.root}
        style={{
          backgroundColor: isDark ? pageBackground.dark : pageBackground.light,
          colorScheme: isDark ? 'dark' : 'light'
        }}
      >
        {showOnboarding ? (
          <Onboarding />
        ) : (
          <>
            <Sidebar
              tab={tab}
              onTabChange={setTab}
              theme={theme}
              isDark={isDark}
              onSetTheme={(mode) => updateSettings({ theme: mode })}
              version={version}
              collapsed={collapsed}
              onToggleCollapsed={toggleCollapsed}
              showTerminal={showTerminal}
              downloadCount={activeCount}
            />

            <main className={styles.content}>
              {tab === 'downloads' && <DownloadsView />}
              <Suspense fallback={null}>
                {tab === 'library' && <LibraryView />}
                {tab === 'history' && <HistoryView />}
                {tab === 'terminal' && <TerminalView />}
                {tab === 'settings' && <SettingsView />}
              </Suspense>
            </main>

            {paletteOpen && (
              <CommandPalette
                actions={paletteActions}
                onClose={() => {
                  setPaletteOpen(false)
                  const el = prePaletteRef.current
                  if (el instanceof HTMLElement) requestAnimationFrame(() => el.focus())
                }}
              />
            )}
          </>
        )}
        <LiveRegion />
        <Toaster />
      </div>
    </FluentProvider>
  )
}

export default App
