/**
 * Root application component that orchestrates all stores, effects, and panel wiring.
 *
 * AppContent initializes the four Zustand stores (sessions, agents, repos, profiles) on mount,
 * polls git status every 2 seconds for the active session, computes derived branch status,
 * and builds a memoized panel map that Layout renders into the drag-to-resize shell. It also
 * manages file navigation with unsaved-changes guards and global keyboard shortcuts.
 * The outer App component wraps AppContent in the PanelProvider context.
 */
import { useEffect, useState, useCallback, useMemo, useRef } from 'react'
import Layout from './layout/Layout'
import NewSessionDialog from './features/sessions/NewSessionDialog'
import PanelPicker from './shared/components/PanelPicker'
import ProfileChip from './features/profiles/ProfileChip'
import HelpModal from './shared/components/HelpModal'
import ShortcutsModal from './shared/components/ShortcutsModal'
import { useSessionStore, type Session, type SessionStatus, type LayoutSizes } from './store/sessions'
import { useUpdateStore } from './shared/hooks/useUpdateState'
import { useAgentStore } from './store/agents'
import { useRepoStore } from './store/repos'
import { useProfileStore } from './store/profiles'
import { PanelProvider, PANEL_IDS } from './panels'
import ErrorBoundary from './shared/components/ErrorBoundary'
import ErrorDetailModal from './shared/components/ErrorDetailModal'
import { useGitPolling } from './features/git/hooks/useGitPolling'
import { useFileNavigation } from './panels/fileViewer/hooks/useFileNavigation'
import { useSessionLifecycle } from './features/sessions/hooks/useSessionLifecycle'
import { useAgentChatStore } from './store/agentChat'
import { useAppCallbacks } from './shared/hooks/useAppCallbacks'
import { usePanelsMap } from './hooks/usePanelsMap'
import { useHelpMenu } from './shared/hooks/useHelpMenu'
import { useSessionKeyboardCallbacks } from './shared/hooks/useSessionKeyboardCallbacks'
import { focusSearchInput } from './shared/utils/focusHelpers'
import { useMenuButton } from './shared/hooks/useMenuButton'
import CrashRecoveryBanner from './shared/components/CrashRecoveryBanner'
import { DialogErrorBanner } from './shared/components/ErrorBanner'
import ExperimentalPlatformModal from './shared/components/ExperimentalPlatformModal'

// Re-export types for backwards compatibility
export type { Session, SessionStatus }

const DEFAULT_LAYOUT_SIZES: LayoutSizes = {
  explorerWidth: 256,
  fileViewerSize: 300,
  userTerminalHeight: 192,
  diffPanelWidth: 320,
  tutorialPanelWidth: 320,
}

function UnsavedChangesDialog({ onCancel, onDiscard, onSave }: {
  onCancel: () => void; onDiscard: () => void; onSave: () => void
}) {
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
      <div className="bg-bg-secondary border border-border rounded-lg shadow-xl p-4 max-w-sm mx-4">
        <h3 className="text-sm font-medium text-text-primary mb-2">Unsaved Changes</h3>
        <p className="text-xs text-text-secondary mb-4">
          You have unsaved changes. What would you like to do?
        </p>
        <div className="flex gap-2 justify-end">
          <button onClick={onCancel} className="px-3 py-1.5 text-xs rounded bg-bg-tertiary text-text-secondary hover:text-text-primary transition-colors">Cancel</button>
          <button onClick={onDiscard} className="px-3 py-1.5 text-xs rounded bg-red-600/20 text-red-400 hover:bg-red-600/30 transition-colors">Discard</button>
          <button onClick={onSave} className="px-3 py-1.5 text-xs rounded bg-accent text-white hover:bg-accent/80 transition-colors">Save</button>
        </div>
      </div>
    </div>
  )
}

function DuplicateSessionModal({ info, onDismiss }: {
  info: { name: string; wasArchived: boolean }; onDismiss: () => void
}) {
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
      <div className="bg-bg-secondary border border-border rounded-lg shadow-xl p-4 max-w-sm mx-4">
        <p className="text-sm text-text-primary mb-4">
          {info.wasArchived
            ? <>Restored archived session <span className="font-medium">{info.name}</span></>
            : <>Switched to existing session <span className="font-medium">{info.name}</span></>
          }
        </p>
        <div className="flex justify-end">
          <button
            onClick={onDismiss}
            className="px-3 py-1.5 text-xs rounded bg-accent text-white hover:bg-accent/80 transition-colors"
          >
            OK
          </button>
        </div>
      </div>
    </div>
  )
}

function GitMissingBanner() {
  const { gitAvailable } = useRepoStore()
  if (gitAvailable !== false) return null
  return (
    <div className="bg-red-900/30 border-b border-red-500/30 px-4 py-2 text-xs text-red-300 flex items-center gap-2">
      <span className="font-medium">git is not installed.</span>
      <span className="text-red-400">Broomy requires git to manage repositories.</span>
      <button onClick={() => window.shell.openExternal('https://git-scm.com/downloads')} className="text-accent hover:underline ml-1">Download git</button>
    </div>
  )
}

function GhMissingBanner() {
  const { ghAvailable } = useRepoStore()
  if (ghAvailable !== false) return null
  return (
    <div className="bg-yellow-900/30 border-b border-yellow-500/30 px-4 py-2 text-xs text-yellow-300 flex items-center gap-2">
      <span className="font-medium">GitHub CLI (gh) is not installed.</span>
      <span className="text-yellow-400">Install it for authentication, issues, and PR features.</span>
      <button onClick={() => window.shell.openExternal('https://cli.github.com')} className="text-accent hover:underline ml-1">Install gh</button>
    </div>
  )
}

function TopBanners({ configLoadError, repoLoadError, appError, onDismissAppError }: {
  configLoadError: string | null; repoLoadError: string | null; appError: string | null; onDismissAppError: () => void
}) {
  return (
    <>
      <CrashRecoveryBanner />
      <GitMissingBanner />
      <GhMissingBanner />
      {configLoadError && <DialogErrorBanner error={configLoadError} onDismiss={() => useSessionStore.setState({ configLoadError: null })} />}
      {repoLoadError && <DialogErrorBanner error={repoLoadError} onDismiss={() => useRepoStore.setState({ loadError: null })} />}
      {appError && <DialogErrorBanner error={appError} onDismiss={onDismissAppError} />}
    </>
  )
}

/** Refresh PR status on startup (once) and whenever an agent finishes work (working → idle). */
function usePrAutoRefresh({ isLoading, sessions, refreshPrStatus, updatePrState, updateFeedbackStatus, updateChecksStatus }: {
  isLoading: boolean
  sessions: Session[]
  refreshPrStatus: () => Promise<void>
  updatePrState: (sessionId: string, prState: import('./features/git/branchStatus').PrState, prNumber?: number, prUrl?: string) => void
  updateFeedbackStatus: (sessionId: string, hasFeedback: boolean) => void
  updateChecksStatus: (sessionId: string, checksStatus: 'passed' | 'failed' | 'pending' | 'none') => void
}) {
  const hasRefreshedOnStartup = useRef(false)
  useEffect(() => {
    if (!isLoading && sessions.length > 0 && !hasRefreshedOnStartup.current) {
      hasRefreshedOnStartup.current = true
      void refreshPrStatus()
    }
  }, [isLoading, sessions.length, refreshPrStatus])

  // Derive a stable key from session unread states to avoid running on unrelated session changes
  const unreadKey = useMemo(() => sessions.map(s => `${s.id}:${s.isUnread}`).join(','), [sessions])
  const sessionsRef = useRef(sessions)
  sessionsRef.current = sessions
  const prevUnread = useRef<Record<string, boolean>>({})
  useEffect(() => {
    for (const session of sessionsRef.current) {
      const wasUnread = prevUnread.current[session.id] ?? false
      if (session.isUnread && !wasUnread) {
        void (async () => {
          try {
            const prResult = await window.gh.prStatus(session.directory)
            if (prResult) {
              updatePrState(session.id, prResult.state, prResult.number, prResult.url)
              if (prResult.state === 'OPEN') {
                const [checks, feedback] = await Promise.all([
                  window.gh.prChecksStatus(session.directory).catch(() => 'none' as const),
                  window.gh.prFeedbackStatus(session.directory, prResult.number).catch(() => false),
                ])
                updateChecksStatus(session.id, checks)
                updateFeedbackStatus(session.id, feedback)
              } else {
                updateChecksStatus(session.id, 'none')
                updateFeedbackStatus(session.id, false)
              }
            } else {
              updatePrState(session.id, null)
              updateChecksStatus(session.id, 'none')
              updateFeedbackStatus(session.id, false)
            }
          } catch { /* ignore */ }
        })()
      }
      prevUnread.current[session.id] = session.isUnread
    }
  }, [unreadKey, updatePrState, updateFeedbackStatus, updateChecksStatus])
}

function AppContent() {
  const sessions = useSessionStore(s => s.sessions)
  const activeSessionId = useSessionStore(s => s.activeSessionId)
  const isLoading = useSessionStore(s => s.isLoading)
  const configLoadError = useSessionStore(s => s.configLoadError)
  const sidebarWidth = useSessionStore(s => s.sidebarWidth)
  const toolbarPanels = useSessionStore(s => s.toolbarPanels)
  const globalPanelVisibility = useSessionStore(s => s.globalPanelVisibility)
  const {
    loadSessions, addSession, addInitializingSession, finalizeSession, failSession, removeSession,
    setActiveSession, togglePanel, toggleGlobalPanel, setSidebarWidth, setToolbarPanels,
    selectFile, setExplorerFilter, setFileViewerPosition, updateLayoutSize, markSessionRead,
    markHasHadCommits, clearHasHadCommits, updateBranchStatus, updateSessionBranch, updatePrState, updateReviewStatus,
    updateFeedbackStatus, updateChecksStatus, archiveSession,
    unarchiveSession, setPanelVisibility, closeCommandsEditor,
  } = useMemo(() => useSessionStore.getState(), [])
  const { agents, loadAgents } = useAgentStore()
  const { repos, loadRepos, loadError: repoLoadError, checkGhAvailability, checkGitAvailability } = useRepoStore()
  const { currentProfileId, profiles, loadProfiles, switchProfile } = useProfileStore()
  const { showHelpModal, setShowHelpModal, showShortcutsModal, setShowShortcutsModal } = useHelpMenu(currentProfileId)
  const currentProfile = profiles.find((p) => p.id === currentProfileId)
  const activeSession = sessions.find((s) => s.id === activeSessionId)
  const [showNewSessionDialog, setShowNewSessionDialog] = useState(false)
  const [showPanelPicker, setShowPanelPicker] = useState(false)
  const [duplicateSessionInfo, setDuplicateSessionInfo] = useState<{ name: string; wasArchived: boolean } | null>(null)
  const [appError, setAppError] = useState<string | null>(null)
  const { activeSessionGitStatus, activeSessionGitStatusResult, selectedFileStatus, fetchGitStatus } =
    useGitPolling({ sessions, activeSession, repos, markHasHadCommits, clearHasHadCommits, updateBranchStatus, updateSessionBranch, updatePrState })

  const {
    openFileInDiffMode, scrollToLine, searchHighlight, diffBaseRef, diffCurrentRef, diffLabel,
    setIsFileViewerDirty, pendingNavigation, navigateToFile, navigationToken,
    handlePendingSave, handlePendingDiscard, handlePendingCancel,
    registerSaveFunction, unregisterSaveFunction,
  } = useFileNavigation({
    activeSessionId: activeSessionId ?? null,
    activeSessionSelectedFilePath: activeSession?.selectedFilePath ?? null,
    selectFile,
  })

  // Session lifecycle hook
  const {
    activeDirectoryExists,
    handleSwitchProfile,
  } = useSessionLifecycle({
    sessions,
    activeSession,
    activeSessionId: activeSessionId ?? null,
    currentProfileId,
    currentProfile,
    profiles,
    loadProfiles,
    loadSessions,
    loadAgents,
    loadRepos,
    checkGhAvailability, checkGitAvailability,
    switchProfile,
    markSessionRead,
    updateReviewStatus,
  })

  const {
    handleNewSession, handleNewSessionComplete, handleCancelNewSession, handleDeleteSession,
    handleStartBranchSession, handleStartExistingBranchSession, refreshPrStatus,
    getAgentCommand, getAgentEnv, getRepoIsolation, getAgentConnectionMode, getAgentModel, getAgentEffort, getAgentSkipApproval, handleLayoutSizeChange,
    handleFileViewerPositionChange, handleSelectSession, handleTogglePanel, handleToggleFileViewer,
  } = useAppCallbacks({
    sessions, activeSessionId, agents, repos, addSession, addInitializingSession,
    finalizeSession, failSession, removeSession, setActiveSession,
    togglePanel, updateLayoutSize, setFileViewerPosition, updatePrState,
    updateFeedbackStatus, updateChecksStatus, updateReviewStatus,
    setShowNewSessionDialog, onSessionAlreadyExists: setDuplicateSessionInfo, onError: setAppError,
  })

  const setActiveTerminalTab = useSessionStore((state) => state.setActiveTerminalTab)
  const {
    handleNextSession, handlePrevSession, handleFocusSessionList,
    handleFocusSessionSearch, handleArchiveSession, handleToggleSettings, handleShowShortcuts,
    handleNextTerminalTab, handlePrevTerminalTab,
  } = useSessionKeyboardCallbacks({
    sessions, activeSessionId: activeSessionId ?? null, globalPanelVisibility,
    toggleGlobalPanel, archiveSession, unarchiveSession, handleSelectSession, setShowShortcutsModal,
    setActiveTerminalTab,
  })

  const handleExplorerTab = useCallback((filter: string) => {
    if (!activeSessionId) return
    if (!activeSession?.panelVisibility[PANEL_IDS.EXPLORER]) togglePanel(activeSessionId, PANEL_IDS.EXPLORER)
    setExplorerFilter(activeSessionId, filter as Parameters<typeof setExplorerFilter>[1])
    if (filter === 'search') {
      focusSearchInput()
    }
  }, [activeSessionId, activeSession, togglePanel, setExplorerFilter])

  const { isMac, platform, handleMenuButtonClick } = useMenuButton({
    setShowPanelPicker, setShowHelpModal, setShowShortcutsModal,
  })

  // Panels map hook
  const panelsMap = usePanelsMap({
    sessions, activeSessionId, activeSession,
    activeSessionGitStatus, activeSessionGitStatusResult, selectedFileStatus,
    navigateToFile, openFileInDiffMode, scrollToLine, searchHighlight,
    diffBaseRef, diffCurrentRef, diffLabel, navigationToken, setIsFileViewerDirty,
    registerSaveFunction, unregisterSaveFunction,
    handleSelectSession, handleNewSession,
    removeSession: (id, deleteWorktree) => { handleDeleteSession(id, deleteWorktree) },
    refreshPrStatus, archiveSession, unarchiveSession,
    handleToggleFileViewer, handleFileViewerPositionChange,
    fetchGitStatus, getAgentCommand, getAgentEnv, getRepoIsolation, getAgentConnectionMode, getAgentModel, getAgentEffort, getAgentSkipApproval,
    globalPanelVisibility, toggleGlobalPanel, selectFile, setExplorerFilter,
    updatePrState, updateFeedbackStatus, updateChecksStatus,
    setPanelVisibility, setToolbarPanels, closeCommandsEditor, repos,
  })

  // Refresh PR status on startup and when agents finish work
  usePrAutoRefresh({ isLoading, sessions, refreshPrStatus, updatePrState, updateFeedbackStatus, updateChecksStatus })

  if (isLoading) {
    return (
      <div className="h-screen w-screen bg-bg-primary flex items-center justify-center">
        <div className="text-text-secondary">Loading...</div>
      </div>
    )
  }

  return (
    <>
      <Layout
        topBanner={<TopBanners configLoadError={configLoadError} repoLoadError={repoLoadError} appError={appError} onDismissAppError={() => setAppError(null)} />}
        panels={panelsMap}
        panelVisibility={activeSession?.panelVisibility ?? {}}
        globalPanelVisibility={globalPanelVisibility}
        fileViewerPosition={activeSession?.fileViewerPosition ?? 'top'}
        sidebarWidth={sidebarWidth}
        layoutSizes={activeSession?.layoutSizes ?? DEFAULT_LAYOUT_SIZES}
        onSidebarWidthChange={setSidebarWidth}
        onLayoutSizeChange={handleLayoutSizeChange}
        errorMessage={activeSession && !activeDirectoryExists ? `Folder not found: ${activeSession.directory}` : null}
        title={activeSession ? activeSession.name : undefined}
        profileChip={<ProfileChip onSwitchProfile={handleSwitchProfile} />}
        activeSessionId={activeSessionId}
        onTogglePanel={handleTogglePanel}
        onToggleGlobalPanel={toggleGlobalPanel}
        onOpenPanelPicker={isMac ? () => setShowPanelPicker(true) : undefined}
        platform={platform} onMenuButtonClick={!isMac ? handleMenuButtonClick : undefined}
        onSearchFiles={() => handleExplorerTab('search')}
        onNewSession={handleNewSession}
        onNextSession={handleNextSession}
        onPrevSession={handlePrevSession}
        onFocusSessionList={handleFocusSessionList}
        onFocusSessionSearch={handleFocusSessionSearch}
        onArchiveSession={handleArchiveSession}
        onToggleSettings={handleToggleSettings}
        onShowShortcuts={handleShowShortcuts}
        onNextTerminalTab={handleNextTerminalTab}
        onPrevTerminalTab={handlePrevTerminalTab}
        onExplorerTab={handleExplorerTab}
      />

      {/* New Session Dialog */}
      {showNewSessionDialog && (
        <NewSessionDialog
          onComplete={handleNewSessionComplete}
          onCancel={handleCancelNewSession}
          onStartBranch={handleStartBranchSession}
          onStartExistingBranch={handleStartExistingBranchSession}
        />
      )}

      {/* Panel Picker */}
      {showPanelPicker && (
        <PanelPicker
          toolbarPanels={toolbarPanels}
          onToolbarPanelsChange={setToolbarPanels}
          onClose={() => setShowPanelPicker(false)}
        />
      )}

      {pendingNavigation && (
        <UnsavedChangesDialog onCancel={handlePendingCancel} onDiscard={handlePendingDiscard} onSave={handlePendingSave} />
      )}

      {/* Help Modal */}
      {showHelpModal && (
        <HelpModal onClose={() => setShowHelpModal(false)} />
      )}

      {/* Shortcuts Modal */}
      {showShortcutsModal && (
        <ShortcutsModal onClose={() => setShowShortcutsModal(false)} />
      )}

      {/* Duplicate Session Info Modal */}
      {duplicateSessionInfo && (
        <DuplicateSessionModal info={duplicateSessionInfo} onDismiss={() => setDuplicateSessionInfo(null)} />
      )}

      {/* Experimental Platform Modal (Windows/Linux) */}
      <ExperimentalPlatformModal />
    </>
  )
}

function App() {
  const toolbarPanels = useSessionStore(s => s.toolbarPanels)
  const setToolbarPanels = useSessionStore(s => s.setToolbarPanels)

  // Expose stores for Playwright screenshot manipulation
  useEffect(() => {
    (window as unknown as Record<string, unknown>).__sessionStore = useSessionStore
    ;(window as unknown as Record<string, unknown>).__updateStore = useUpdateStore
    ;(window as unknown as Record<string, unknown>).__repoStore = useRepoStore
    ;(window as unknown as Record<string, unknown>).__agentChatStore = useAgentChatStore
  }, [])

  return (
    <ErrorBoundary>
      <PanelProvider
        toolbarPanels={toolbarPanels}
        onToolbarPanelsChange={setToolbarPanels}
      >
        <AppContent />
        <ErrorDetailModal />
      </PanelProvider>
    </ErrorBoundary>
  )
}

export default App
