/**
 * Session state management -- the largest and most central Zustand store.
 *
 * Each session represents an AI coding agent working in a git repository. The store
 * manages session CRUD, per-session panel visibility, layout sizes, terminal tabs,
 * agent activity monitoring (working/idle/unread), branch status, PR state tracking,
 * and session archiving. State mutations are applied in-memory instantly, then
 * persisted to the config file via a 500ms debounced save to avoid excessive I/O.
 * Runtime-only fields (status, isUnread, lastMessage, branchStatus) are never written
 * to disk.
 */
import { create } from 'zustand'
import { PANEL_IDS, DEFAULT_TOOLBAR_PANELS } from '../panels/system/types'
import type { BranchStatus, PrState, StatusChip } from '../features/git/branchStatus'
import {
  debouncedSave,
  syncLegacyFields,
} from './sessionPersistence'
import { createTerminalTabActions } from './sessionTerminalTabs'
import { createPanelActions } from './sessionPanelActions'
import { createBranchActions } from './sessionBranchActions'
import { createCoreActions, DEFAULT_SIDEBAR_WIDTH } from './sessionCoreActions'

export type { BranchStatus, PrState, StatusChip }

export type SessionStatus = 'working' | 'idle' | 'error' | 'initializing'
export type FileViewerPosition = 'top' | 'left'

// Terminal tab types
export interface TerminalTab {
  id: string
  name: string
  isolated?: boolean
}

export interface TerminalTabsState {
  tabs: TerminalTab[]
  activeTabId: string | null
}

export interface LayoutSizes {
  explorerWidth: number
  fileViewerSize: number // height when top, width when left
  userTerminalHeight: number
  diffPanelWidth: number
  tutorialPanelWidth: number
}

export type ExplorerFilter = 'files' | 'source-control' | 'search' | 'recent' | 'review'

// Panel visibility map type
export type PanelVisibility = Record<string, boolean>

export interface Session {
  id: string
  name: string
  directory: string
  branch: string
  status: SessionStatus
  agentId: string | null
  repoId?: string
  issueNumber?: number
  issueTitle?: string
  issueUrl?: string
  // Review session fields
  sessionType?: 'default' | 'review'
  reviewStatus?: 'pending' | 'reviewed'
  prNumber?: number
  prTitle?: string
  prUrl?: string
  prBaseBranch?: string
  // Per-session UI state (persisted) - generic panel visibility
  panelVisibility: PanelVisibility
  // Legacy fields kept for backwards compat - computed from panelVisibility
  showExplorer: boolean
  showFileViewer: boolean
  showDiff: boolean
  selectedFilePath: string | null
  planFilePath: string | null
  fileViewerPosition: FileViewerPosition
  layoutSizes: LayoutSizes
  explorerFilter: ExplorerFilter
  // Agent monitoring state (runtime only, not persisted)
  lastMessage: string | null
  lastMessageTime: number | null
  isUnread: boolean
  workingStartTime: number | null // When the current working period began
  // Agent PTY ID (runtime only, set by Terminal.tsx)
  agentPtyId?: string
  // Commands editor: when set, file viewer area shows the commands editor for this directory
  commandsEditorDirectory?: string | null
  // Recently opened files (runtime, most recent first)
  recentFiles: string[]
  // Search history (persisted, most recent first)
  searchHistory: string[]
  // User terminal tabs (persisted)
  terminalTabs: TerminalTabsState
  // Track whether this session has ever had commits ahead of remote (persisted)
  hasHadCommits?: boolean
  // Branch status (runtime, derived)
  branchStatus: BranchStatus
  // PR metadata (runtime, derived from GitHub API)
  hasFeedback: boolean
  checksStatus: 'passed' | 'failed' | 'pending' | 'none'
  // Unified status chip (runtime, derived) — single source of truth for both sidebar and SCM
  statusChip: StatusChip
  // PR state tracking (persisted)
  lastKnownPrState?: PrState
  lastKnownPrNumber?: number
  lastKnownPrUrl?: string
  // Archive state (persisted)
  isArchived: boolean
  // Stage state machine — drives command visibility (persisted)
  stage: string
  // Agent SDK session ID for resume (persisted)
  sdkSessionId?: string
  // Whether this session was loaded from config (runtime only, not persisted)
  isRestored: boolean
  // Error from background initialization (runtime only, not persisted)
  initError?: string | null
}

// Global panel visibility (sidebar, settings, tutorial)
const DEFAULT_GLOBAL_PANEL_VISIBILITY: PanelVisibility = {
  [PANEL_IDS.SIDEBAR]: true,
  [PANEL_IDS.SETTINGS]: false,
  [PANEL_IDS.TUTORIAL]: false,
}

interface SessionStore {
  sessions: Session[]
  activeSessionId: string | null
  isLoading: boolean
  configLoadError: string | null
  // Global panel state
  showSidebar: boolean
  showSettings: boolean
  sidebarWidth: number
  toolbarPanels: string[]
  globalPanelVisibility: PanelVisibility

  // Actions
  loadSessions: (profileId?: string) => Promise<void>
  addSession: (directory: string, agentId: string | null, extra?: { repoId?: string; issueNumber?: number; issueTitle?: string; issueUrl?: string; name?: string; sessionType?: 'default' | 'review'; prNumber?: number; prTitle?: string; prUrl?: string; prBaseBranch?: string; lastKnownPrState?: PrState }) => Promise<import('./sessionCoreActions').DuplicateSessionResult | undefined>
  removeSession: (id: string) => void
  setActiveSession: (id: string | null) => void
  // Generic panel actions
  togglePanel: (sessionId: string, panelId: string) => void
  toggleGlobalPanel: (panelId: string) => void
  setPanelVisibility: (sessionId: string, panelId: string, visible: boolean) => void
  setToolbarPanels: (panels: string[]) => void
  // UI state actions (backwards compat aliases)
  toggleSidebar: () => void
  setSidebarWidth: (width: number) => void
  toggleExplorer: (id: string) => void
  toggleFileViewer: (id: string) => void
  setPlanFile: (id: string, path: string | null) => void
  selectFile: (id: string, filePath: string, openInDiffMode?: boolean) => void
  setFileViewerPosition: (id: string, position: FileViewerPosition) => void
  updateLayoutSize: (id: string, key: keyof LayoutSizes, value: number) => void
  setExplorerFilter: (id: string, filter: ExplorerFilter) => void
  // Agent monitoring actions
  updateAgentMonitor: (id: string, update: { status?: SessionStatus; lastMessage?: string }) => void
  markSessionRead: (id: string) => void
  // Terminal tab actions
  addTerminalTab: (sessionId: string, name?: string, isolated?: boolean) => string
  removeTerminalTab: (sessionId: string, tabId: string) => void
  renameTerminalTab: (sessionId: string, tabId: string, name: string) => void
  reorderTerminalTabs: (sessionId: string, tabs: TerminalTab[]) => void
  setActiveTerminalTab: (sessionId: string, tabId: string) => void
  closeOtherTerminalTabs: (sessionId: string, tabId: string) => void
  closeTerminalTabsToRight: (sessionId: string, tabId: string) => void
  // Commands editor actions
  openCommandsEditor: (sessionId: string, directory: string) => void
  closeCommandsEditor: (sessionId: string) => void
  // Agent PTY tracking (runtime only)
  setAgentPtyId: (sessionId: string, ptyId: string) => void
  // Agent SDK session ID (persisted for resume)
  setSdkSessionId: (sessionId: string, sdkSessionId: string) => void
  // Branch status actions
  markHasHadCommits: (sessionId: string) => void
  clearHasHadCommits: (sessionId: string) => void
  updateBranchStatus: (sessionId: string, status: BranchStatus) => void
  updateSessionBranch: (sessionId: string, branch: string) => void
  updatePrState: (sessionId: string, prState: PrState, prNumber?: number, prUrl?: string) => void
  updateReviewStatus: (sessionId: string, reviewStatus: 'pending' | 'reviewed') => void
  updateFeedbackStatus: (sessionId: string, hasFeedback: boolean) => void
  updateChecksStatus: (sessionId: string, checksStatus: 'passed' | 'failed' | 'pending' | 'none') => void
  // Stage state machine
  setSessionStage: (sessionId: string, stage: string) => void
  // Search history actions
  addSearchHistory: (sessionId: string, query: string) => void
  removeSearchHistoryItem: (sessionId: string, query: string) => void
  // Archive actions
  archiveSession: (sessionId: string) => void
  unarchiveSession: (sessionId: string) => void
  // Instant setup actions
  addInitializingSession: (params: { directory: string; branch: string; agentId: string | null; extra?: { repoId?: string; issueNumber?: number; issueTitle?: string; issueUrl?: string; name?: string } }) => string
  finalizeSession: (id: string) => void
  failSession: (id: string, error: string) => void
}

export const useSessionStore = create<SessionStore>((set, get) => {
  const terminalTabActions = createTerminalTabActions(get, set)
  const panelActions = createPanelActions(get, set)
  const branchActions = createBranchActions(get, set)
  const coreActions = createCoreActions(get, set)

  return {
  sessions: [],
  activeSessionId: null,
  isLoading: true,
  configLoadError: null,
  showSidebar: true,
  showSettings: false,
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
  toolbarPanels: [...DEFAULT_TOOLBAR_PANELS],
  globalPanelVisibility: { ...DEFAULT_GLOBAL_PANEL_VISIBILITY },

  // Core actions (delegated)
  ...coreActions,

  // Panel actions (delegated)
  ...panelActions,

  setPlanFile: (id: string, path: string | null) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) =>
      s.id === id ? { ...s, planFilePath: path } : s
    )
    set({ sessions: updatedSessions })
    // planFilePath is runtime-only state, no need to persist
  },

  selectFile: (id: string, filePath: string) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) => {
      if (s.id !== id) return s
      const newVisibility = {
        ...s.panelVisibility,
        [PANEL_IDS.FILE_VIEWER]: true,
      }
      // Track in recent files (move to front, cap at 50)
      const recentFiles = [filePath, ...s.recentFiles.filter(f => f !== filePath)].slice(0, 50)
      return syncLegacyFields({
        ...s,
        selectedFilePath: filePath,
        commandsEditorDirectory: null,
        panelVisibility: newVisibility,
        recentFiles,
      })
    })
    set({ sessions: updatedSessions })
    debouncedSave()
  },

  setFileViewerPosition: (id: string, position: FileViewerPosition) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) =>
      s.id === id ? { ...s, fileViewerPosition: position } : s
    )
    set({ sessions: updatedSessions })
    debouncedSave()
  },

  updateLayoutSize: (id: string, key: keyof LayoutSizes, value: number) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) =>
      s.id === id ? { ...s, layoutSizes: { ...s.layoutSizes, [key]: value } } : s
    )
    set({ sessions: updatedSessions })
    debouncedSave()
  },

  setExplorerFilter: (id: string, filter: ExplorerFilter) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) =>
      s.id === id ? { ...s, explorerFilter: filter } : s
    )
    set({ sessions: updatedSessions })
    debouncedSave()
  },

  updateAgentMonitor: (id: string, update: { status?: SessionStatus; lastMessage?: string }) => {
    const { sessions } = get()
    const session = sessions.find(s => s.id === id)
    if (!session) return
    if (session.status === 'initializing') return
    // Bail out if nothing would change (e.g. setting status to 'working' when already 'working')
    if (update.status !== undefined && update.status === session.status && update.lastMessage === undefined) return
    const updatedSessions = sessions.map((s) => {
      if (s.id !== id) return s
      const changes: Partial<Session> = {}
      if (update.status !== undefined) {
        changes.status = update.status
      }
      if (update.lastMessage !== undefined) {
        changes.lastMessage = update.lastMessage
        changes.lastMessageTime = Date.now()
      }
      // Track when working period starts
      if (update.status === 'working' && s.status !== 'working') {
        changes.workingStartTime = Date.now()
      }
      // Mark as unread when transitioning from working to idle,
      // but only if the agent was working for at least 3 seconds.
      // This filters out brief notifications (e.g. usage threshold alerts)
      // that would otherwise cause false "unread" alerts.
      if (update.status === 'idle' && s.status === 'working') {
        const workingDuration = Date.now() - (s.workingStartTime ?? Date.now())
        if (workingDuration >= 3000) {
          changes.isUnread = true
          // Notify source control to refresh PR status after agent finishes work
          if (typeof document !== 'undefined') {
            document.dispatchEvent(new CustomEvent('broomy:agent-finished'))
          }
        }
        changes.workingStartTime = null
      }
      return { ...s, ...changes }
    })
    set({ sessions: updatedSessions })
    // Don't persist runtime monitoring state
  },

  markSessionRead: (id: string) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) =>
      s.id === id ? { ...s, isUnread: false } : s
    )
    set({ sessions: updatedSessions })
    // Don't persist runtime monitoring state
  },

  // Terminal tab actions (delegated)
  ...terminalTabActions,

  setAgentPtyId: (sessionId: string, ptyId: string) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) =>
      s.id === sessionId ? { ...s, agentPtyId: ptyId } : s
    )
    set({ sessions: updatedSessions })
    // Don't persist - runtime only
  },

  setSdkSessionId: (sessionId: string, sdkSessionId: string) => {
    const { sessions } = get()
    const existing = sessions.find((s) => s.id === sessionId)
    if (existing?.sdkSessionId === sdkSessionId) return
    const updatedSessions = sessions.map((s) =>
      s.id === sessionId ? { ...s, sdkSessionId } : s
    )
    set({ sessions: updatedSessions })
    debouncedSave()
  },

  setSessionStage: (sessionId: string, stage: string) => {
    const { sessions } = get()
    const updated = sessions.map(s => s.id === sessionId ? { ...s, stage } : s)
    set({ sessions: updated })
    debouncedSave()
  },

  // Search history actions
  addSearchHistory: (sessionId: string, query: string) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) => {
      if (s.id !== sessionId) return s
      const history = [query, ...s.searchHistory.filter(q => q !== query)].slice(0, 50)
      return { ...s, searchHistory: history }
    })
    set({ sessions: updatedSessions })
    debouncedSave()
  },

  removeSearchHistoryItem: (sessionId: string, query: string) => {
    const { sessions } = get()
    const updatedSessions = sessions.map((s) => {
      if (s.id !== sessionId) return s
      return { ...s, searchHistory: s.searchHistory.filter(q => q !== query) }
    })
    set({ sessions: updatedSessions })
    debouncedSave()
  },

  // Branch & lifecycle actions (delegated)
  ...branchActions,
}})
