/**
 * Store index - combines all slices and exports the unified store.
 */

import { api } from '@renderer/api';
import { cleanupStale as cleanupCommentReadState } from '@renderer/services/commentReadStorage';
import { create } from 'zustand';

import { createChangeReviewSlice } from './slices/changeReviewSlice';
import { createCliInstallerSlice } from './slices/cliInstallerSlice';
import { createConfigSlice } from './slices/configSlice';
import { createConnectionSlice } from './slices/connectionSlice';
import { createContextSlice } from './slices/contextSlice';
import { createConversationSlice } from './slices/conversationSlice';
import { createEditorSlice } from './slices/editorSlice';
import { createExtensionsSlice } from './slices/extensionsSlice';
import { createNotificationSlice } from './slices/notificationSlice';
import { createScheduleSlice } from './slices/scheduleSlice';
import { createPaneSlice } from './slices/paneSlice';
import { createProjectSlice } from './slices/projectSlice';
import { createRepositorySlice } from './slices/repositorySlice';
import { createSessionDetailSlice } from './slices/sessionDetailSlice';
import { createSessionSlice } from './slices/sessionSlice';
import { createSubagentSlice } from './slices/subagentSlice';
import { createTabSlice } from './slices/tabSlice';
import { createTabUISlice } from './slices/tabUISlice';
import { createTeamSlice } from './slices/teamSlice';
import { createUISlice } from './slices/uiSlice';
import { createUpdateSlice } from './slices/updateSlice';

import type { DetectedError } from '../types/data';
import type { AppState } from './types';
import type {
  CliInstallerProgress,
  LeadContextUsage,
  ScheduleChangeEvent,
  TeamChangeEvent,
  ToolApprovalEvent,
  ToolApprovalRequest,
  UpdaterStatus,
} from '@shared/types';

// =============================================================================
// Store Creation
// =============================================================================

export const useStore = create<AppState>()((...args) => ({
  ...createProjectSlice(...args),
  ...createRepositorySlice(...args),
  ...createSessionSlice(...args),
  ...createSessionDetailSlice(...args),
  ...createSubagentSlice(...args),
  ...createTeamSlice(...args),
  ...createConversationSlice(...args),
  ...createTabSlice(...args),
  ...createTabUISlice(...args),
  ...createPaneSlice(...args),
  ...createUISlice(...args),
  ...createNotificationSlice(...args),
  ...createConfigSlice(...args),
  ...createConnectionSlice(...args),
  ...createContextSlice(...args),
  ...createUpdateSlice(...args),
  ...createChangeReviewSlice(...args),
  ...createCliInstallerSlice(...args),
  ...createEditorSlice(...args),
  ...createScheduleSlice(...args),
  ...createExtensionsSlice(...args),
}));

// =============================================================================
// Re-exports
// =============================================================================

// =============================================================================
// Store Initialization - Subscribe to IPC Events
// =============================================================================

/**
 * Initialize notification event listeners and fetch initial notification count.
 * Call this once when the app starts (e.g., in App.tsx useEffect).
 */
export function initializeNotificationListeners(): () => void {
  void cleanupCommentReadState();
  const cleanupFns: (() => void)[] = [];
  useStore.getState().subscribeProvisioningProgress();
  cleanupFns.push(() => {
    useStore.getState().unsubscribeProvisioningProgress();
  });
  // Initial data fetches. Config loads first (needed for theme), then the rest
  // run in parallel (no data dependencies between them). UV_THREADPOOL_SIZE=16
  // prevents thread pool saturation even with concurrent I/O on Windows.
  // Components also fire these from useEffect — loading guards in each action
  // prevent duplicate IPC calls (whichever caller starts first wins).
  void (async () => {
    // Config: fast (in-memory read) — needed for theme before first paint.
    await useStore.getState().fetchConfig();

    // Remaining fetches have no data dependency on each other — run in parallel
    // to avoid blocking teams/notifications behind a slow repository scan.
    await Promise.all([
      useStore.getState().fetchRepositoryGroups(),
      useStore.getState().fetchAllTasks(),
      useStore.getState().fetchTeams(),
      useStore.getState().fetchNotifications(),
      useStore.getState().fetchSchedules(),
    ]);
  })();

  // CLI status check is non-critical for initial render (spawns child processes
  // + iterates PATH directories with stat() calls — heavy on Windows).
  // Defer on Windows; run immediately elsewhere so status is available quickly.
  let cliStatusTimer: ReturnType<typeof setTimeout> | null = null;
  if (api.cliInstaller) {
    // On macOS/Linux, run immediately so the Dashboard can render status fast.
    // On Windows, keep the existing defer to avoid competing with initial scans.
    type NavigatorWithUserAgentData = Navigator & { userAgentData?: { platform?: string } };
    const nav: NavigatorWithUserAgentData | null =
      typeof navigator !== 'undefined' ? (navigator as NavigatorWithUserAgentData) : null;
    // Prefer UA-CH when available; fall back to deprecated-but-still-supported navigator.platform.
    // eslint-disable-next-line sonarjs/deprecation -- navigator.platform is deprecated but needed as fallback
    const platform: string = nav?.userAgentData?.platform ?? nav?.platform ?? nav?.userAgent ?? '';
    const isWindows = platform.toLowerCase().includes('win');
    const delayMs = isWindows ? 3000 : 0;
    cliStatusTimer = setTimeout(() => {
      void useStore.getState().fetchCliStatus();
      cliStatusTimer = null;
    }, delayMs);
  }
  cleanupFns.push(() => {
    if (cliStatusTimer) clearTimeout(cliStatusTimer);
  });
  const pendingSessionRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
  const pendingProjectRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
  let teamRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();

  let teamListRefreshTimer: ReturnType<typeof setTimeout> | null = null;
  let globalTasksRefreshTimer: ReturnType<typeof setTimeout> | null = null;
  const SESSION_REFRESH_DEBOUNCE_MS = 150;
  const PROJECT_REFRESH_DEBOUNCE_MS = 300;
  const TEAM_REFRESH_THROTTLE_MS = 800;
  const TEAM_LIST_REFRESH_THROTTLE_MS = 2000;
  const GLOBAL_TASKS_REFRESH_THROTTLE_MS = 500;
  const getBaseProjectId = (projectId: string | null | undefined): string | null => {
    if (!projectId) return null;
    const separatorIndex = projectId.indexOf('::');
    return separatorIndex >= 0 ? projectId.slice(0, separatorIndex) : projectId;
  };

  const scheduleSessionRefresh = (projectId: string, sessionId: string): void => {
    const key = `${projectId}/${sessionId}`;
    // Throttle (not trailing debounce): keep at most one pending refresh per session.
    // Debounce can delay updates indefinitely while the file is continuously appended.
    if (pendingSessionRefreshTimers.has(key)) {
      return;
    }
    const timer = setTimeout(() => {
      pendingSessionRefreshTimers.delete(key);
      const state = useStore.getState();
      void state.refreshSessionInPlace(projectId, sessionId);
    }, SESSION_REFRESH_DEBOUNCE_MS);
    pendingSessionRefreshTimers.set(key, timer);
  };

  const scheduleProjectRefresh = (projectId: string): void => {
    const existingTimer = pendingProjectRefreshTimers.get(projectId);
    if (existingTimer) {
      clearTimeout(existingTimer);
    }
    const timer = setTimeout(() => {
      pendingProjectRefreshTimers.delete(projectId);
      const state = useStore.getState();
      void state.refreshSessionsInPlace(projectId);
    }, PROJECT_REFRESH_DEBOUNCE_MS);
    pendingProjectRefreshTimers.set(projectId, timer);
  };

  // Listen for new notifications from main process
  if (api.notifications?.onNew) {
    const cleanup = api.notifications.onNew((_event: unknown, error: unknown) => {
      // Cast the error to DetectedError type
      const notification = error as DetectedError;
      if (notification?.id) {
        // Keep list in sync immediately; unread count is synced via notification:updated/fetch.
        useStore.setState((state) => {
          if (state.notifications.some((n) => n.id === notification.id)) {
            return {};
          }
          return { notifications: [notification, ...state.notifications].slice(0, 200) };
        });
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // Listen for notification updates from main process
  if (api.notifications?.onUpdated) {
    const cleanup = api.notifications.onUpdated(
      (_event: unknown, payload: { total: number; unreadCount: number }) => {
        const unreadCount =
          typeof payload.unreadCount === 'number' && Number.isFinite(payload.unreadCount)
            ? Math.max(0, Math.floor(payload.unreadCount))
            : 0;
        useStore.setState({ unreadCount });
      }
    );
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // Navigate to error when user clicks a native OS notification
  if (api.notifications?.onClicked) {
    const cleanup = api.notifications.onClicked((_event: unknown, data: unknown) => {
      const error = data as DetectedError;
      if (error?.id && error?.sessionId && error?.projectId) {
        useStore.getState().navigateToError(error);
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // fetchNotifications() is called in the parallel init chain above.

  /**
   * Check if a session is visible in any pane (not just the focused pane's active tab).
   * This ensures file change and task-list listeners refresh sessions shown in any split pane.
   */
  const isSessionVisibleInAnyPane = (sessionId: string): boolean => {
    const { paneLayout } = useStore.getState();
    return paneLayout.panes.some(
      (pane) =>
        pane.activeTabId != null &&
        pane.tabs.some(
          (tab) =>
            tab.id === pane.activeTabId && tab.type === 'session' && tab.sessionId === sessionId
        )
    );
  };

  const isTeamVisibleInAnyPane = (teamName: string): boolean => {
    const { paneLayout } = useStore.getState();
    return paneLayout.panes.some((pane) => {
      if (!pane.activeTabId) return false;
      return pane.tabs.some(
        (tab) => tab.id === pane.activeTabId && tab.type === 'team' && tab.teamName === teamName
      );
    });
  };

  // Listen for task-list file changes to refresh currently viewed session metadata
  if (api.onTodoChange) {
    const cleanup = api.onTodoChange((event) => {
      if (!event.sessionId || event.type === 'unlink') {
        return;
      }

      const state = useStore.getState();
      const isViewingSession =
        state.selectedSessionId === event.sessionId || isSessionVisibleInAnyPane(event.sessionId);

      if (isViewingSession) {
        // Find the project ID from any pane's tab that shows this session
        const allTabs = state.getAllPaneTabs();
        const sessionTab = allTabs.find(
          (t) => t.type === 'session' && t.sessionId === event.sessionId
        );
        if (sessionTab?.projectId) {
          scheduleSessionRefresh(sessionTab.projectId, event.sessionId);
        }
      }

      // Refresh project sessions list if applicable
      const activeTab = state.getActiveTab();
      const activeProjectId =
        activeTab?.type === 'session' && typeof activeTab.projectId === 'string'
          ? activeTab.projectId
          : null;
      if (activeProjectId && activeProjectId === state.selectedProjectId) {
        scheduleProjectRefresh(activeProjectId);
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // Listen for file changes to auto-refresh current session and detect new sessions
  if (api.onFileChange) {
    const cleanup = api.onFileChange((event) => {
      // Skip unlink events
      if (event.type === 'unlink') {
        return;
      }

      const state = useStore.getState();
      const selectedProjectId = state.selectedProjectId;
      const selectedProjectBaseId = getBaseProjectId(selectedProjectId);
      const eventProjectBaseId = getBaseProjectId(event.projectId);
      const matchesSelectedProject =
        !!selectedProjectId &&
        (eventProjectBaseId == null || selectedProjectBaseId === eventProjectBaseId);
      const isTopLevelSessionEvent = !event.isSubagent;
      const isUnknownSessionInSidebar =
        event.sessionId == null ||
        !state.sessions.some((session) => session.id === event.sessionId);
      const shouldRefreshForPotentialNewSession =
        isTopLevelSessionEvent &&
        matchesSelectedProject &&
        isUnknownSessionInSidebar &&
        (event.type === 'add' || (state.connectionMode === 'local' && event.type === 'change'));

      // Refresh sidebar session list only when a truly new top-level session appears.
      // Local fs.watch can report "change" before/without "add" for newly created files.
      if (shouldRefreshForPotentialNewSession) {
        if (matchesSelectedProject && selectedProjectId) {
          scheduleProjectRefresh(selectedProjectId);
        }
      }

      // Keep opened session view in sync on content changes.
      // Some local writers emit rename/add for in-place updates, so include "add".
      if ((event.type === 'change' || event.type === 'add') && selectedProjectId) {
        const activeSessionId = state.selectedSessionId;
        const eventSessionId = event.sessionId;
        const isViewingEventSession =
          !!eventSessionId &&
          (activeSessionId === eventSessionId || isSessionVisibleInAnyPane(eventSessionId));
        const shouldFallbackRefreshActiveSession =
          matchesSelectedProject && !eventSessionId && !!activeSessionId;
        const sessionIdToRefresh =
          (isViewingEventSession ? eventSessionId : null) ??
          (shouldFallbackRefreshActiveSession ? activeSessionId : null);

        if (sessionIdToRefresh) {
          const allTabs = state.getAllPaneTabs();
          const visibleSessionTab = allTabs.find(
            (tab) => tab.type === 'session' && tab.sessionId === sessionIdToRefresh
          );
          const refreshProjectId = visibleSessionTab?.projectId ?? selectedProjectId;

          // Use refreshSessionInPlace to avoid flickering and preserve UI state
          scheduleSessionRefresh(refreshProjectId, sessionIdToRefresh);
        }
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  if (api.teams?.onTeamChange) {
    const cleanup = api.teams.onTeamChange((_event: unknown, event: TeamChangeEvent) => {
      // Immediate in-memory update for lead activity — no filesystem refresh needed
      if (event.type === 'lead-activity' && event.detail) {
        const nextActivity = event.detail as 'active' | 'idle' | 'offline';
        useStore.setState((prev) => {
          const nextState: Partial<typeof prev> = {
            leadActivityByTeam: {
              ...prev.leadActivityByTeam,
              [event.teamName]: nextActivity,
            },
          };

          // Keep TeamDetailView in sync: it historically relied on selectedTeamData.isAlive,
          // which isn't refreshed for lead-activity events.
          if (prev.selectedTeamName === event.teamName && prev.selectedTeamData) {
            nextState.selectedTeamData = {
              ...prev.selectedTeamData,
              isAlive: nextActivity !== 'offline',
            };
          }

          // Clear context data when lead goes offline
          if (nextActivity === 'offline') {
            nextState.leadContextByTeam = { ...prev.leadContextByTeam };
            delete nextState.leadContextByTeam[event.teamName];
          }

          return nextState as typeof prev;
        });
        return;
      }

      // Immediate in-memory update for lead context usage — no filesystem refresh needed
      if (event.type === 'lead-context' && event.detail) {
        try {
          const ctx = JSON.parse(event.detail) as LeadContextUsage;
          useStore.setState((prev) => ({
            ...prev,
            leadContextByTeam: { ...prev.leadContextByTeam, [event.teamName]: ctx },
          }));
        } catch {
          /* ignore malformed detail */
        }
        return;
      }

      // Member spawn status change: fetch updated spawn statuses for the team.
      if (event.type === 'member-spawn') {
        void useStore.getState().fetchMemberSpawnStatuses(event.teamName);
        return;
      }

      // Live lead-message events: only refresh the visible team detail, not team/task lists.
      // This keeps the refresh lightweight and prevents one noisy team from starving another.
      if (event.type === 'lead-message') {
        if (!event?.teamName || !isTeamVisibleInAnyPane(event.teamName)) {
          return;
        }
        if (teamRefreshTimers.has(event.teamName)) {
          return;
        }
        const timer = setTimeout(() => {
          teamRefreshTimers.delete(event.teamName);
          const current = useStore.getState();
          void current.refreshTeamData(event.teamName);
        }, TEAM_REFRESH_THROTTLE_MS);
        teamRefreshTimers.set(event.teamName, timer);
        return;
      }

      // Throttled refresh of summary list (keeps TeamListView current without flooding).
      if (!teamListRefreshTimer) {
        teamListRefreshTimer = setTimeout(() => {
          teamListRefreshTimer = null;
          void useStore.getState().fetchTeams();
        }, TEAM_LIST_REFRESH_THROTTLE_MS);
      }

      // Throttled refresh of global tasks list for sidebar.
      if (!globalTasksRefreshTimer) {
        globalTasksRefreshTimer = setTimeout(() => {
          globalTasksRefreshTimer = null;
          void useStore.getState().fetchAllTasks();
        }, GLOBAL_TASKS_REFRESH_THROTTLE_MS);
      }

      if (!event?.teamName || !isTeamVisibleInAnyPane(event.teamName)) {
        return;
      }

      // Per-team throttle (not debounce): keep at most one pending detail refresh per team.
      // Debounce would delay indefinitely while inbox messages keep arriving.
      if (teamRefreshTimers.has(event.teamName)) {
        return;
      }

      const timer = setTimeout(() => {
        teamRefreshTimers.delete(event.teamName);
        const current = useStore.getState();
        void current.refreshTeamData(event.teamName);
      }, TEAM_REFRESH_THROTTLE_MS);
      teamRefreshTimers.set(event.teamName, timer);
    });

    if (typeof cleanup === 'function') {
      cleanupFns.push(() => {
        cleanup();
        for (const t of teamRefreshTimers.values()) clearTimeout(t);
        teamRefreshTimers = new Map();
        if (teamListRefreshTimer) {
          clearTimeout(teamListRefreshTimer);
          teamListRefreshTimer = null;
        }
        if (globalTasksRefreshTimer) {
          clearTimeout(globalTasksRefreshTimer);
          globalTasksRefreshTimer = null;
        }
      });
    }
  }

  // Tool approval events from CLI control_request protocol
  if (api.teams?.onToolApprovalEvent) {
    const cleanup = api.teams.onToolApprovalEvent((_event: unknown, data: unknown) => {
      const event = data as ToolApprovalEvent;
      if ('autoResolved' in event && event.autoResolved) {
        // Timeout or auto-allow resolved in main — remove from UI
        useStore.setState((s) => ({
          pendingApprovals: s.pendingApprovals.filter(
            (a) => !(a.runId === event.runId && a.requestId === event.requestId)
          ),
        }));
      } else if ('dismissed' in event && event.dismissed) {
        const dismiss = event;
        useStore.setState((s) => ({
          pendingApprovals: s.pendingApprovals.filter(
            (a) => !(a.teamName === dismiss.teamName && a.runId === dismiss.runId)
          ),
        }));
      } else {
        const request = event as ToolApprovalRequest;
        useStore.setState((s) => ({
          pendingApprovals: [...s.pendingApprovals, request],
        }));
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }

    // Sync saved tool approval settings to main process on startup
    const savedSettings = useStore.getState().toolApprovalSettings;
    api.teams.updateToolApprovalSettings?.(savedSettings).catch(() => {
      // Silently ignore — settings will use defaults until next update
    });
  }

  // Listen for editor file change events (chokidar watcher → renderer)
  if (api.editor?.onEditorChange) {
    const cleanup = api.editor.onEditorChange((event) => {
      const state = useStore.getState();
      if (state.editorProjectPath) {
        state.handleExternalFileChange(event);
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // Listen for schedule change events from main process
  if (api.schedules?.onScheduleChange) {
    const cleanup = api.schedules.onScheduleChange((_event: unknown, data: unknown) => {
      const event = data as ScheduleChangeEvent;
      if (event?.scheduleId) {
        void useStore.getState().applyScheduleChange(event.scheduleId);
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // fetchCliStatus() is deferred 5s after app start (heavy on Windows).

  // Listen for CLI installer progress events from main process
  let cliCompletedRevertTimer: ReturnType<typeof setTimeout> | null = null;
  if (api.cliInstaller?.onProgress) {
    const cleanup = api.cliInstaller.onProgress((_event: unknown, data: unknown) => {
      const progress = data as CliInstallerProgress;

      // Clear any pending auto-revert timer on new events
      if (progress.type !== 'completed' && cliCompletedRevertTimer) {
        clearTimeout(cliCompletedRevertTimer);
        cliCompletedRevertTimer = null;
      }

      const detail = progress.detail ?? null;

      switch (progress.type) {
        case 'checking':
          useStore.setState({ cliInstallerState: 'checking', cliInstallerDetail: detail });
          break;
        case 'downloading':
          useStore.setState({
            cliInstallerState: 'downloading',
            cliDownloadProgress: progress.percent ?? 0,
            cliDownloadTransferred: progress.transferred ?? 0,
            cliDownloadTotal: progress.total ?? 0,
            cliInstallerDetail: detail,
          });
          break;
        case 'verifying':
          useStore.setState({ cliInstallerState: 'verifying', cliInstallerDetail: detail });
          break;
        case 'installing': {
          // Accumulate log lines and raw chunks for xterm.js rendering
          const prevLogs = useStore.getState().cliInstallerLogs;
          const prevRaw = useStore.getState().cliInstallerRawChunks;
          const newLogs = detail ? [...prevLogs, detail].slice(-50) : prevLogs;
          const newRaw = progress.rawChunk ? [...prevRaw, progress.rawChunk].slice(-200) : prevRaw;
          useStore.setState({
            cliInstallerState: 'installing',
            cliInstallerDetail: detail,
            cliInstallerLogs: newLogs,
            cliInstallerRawChunks: newRaw,
          });
          break;
        }
        case 'completed':
          useStore.setState({
            cliInstallerState: 'completed',
            cliCompletedVersion: progress.version ?? null,
            cliInstallerDetail: null,
          });
          // Re-fetch status after install and auto-revert to idle after 3s
          void useStore.getState().fetchCliStatus();
          cliCompletedRevertTimer = setTimeout(() => {
            cliCompletedRevertTimer = null;
            // Only revert if still in 'completed' state (not overwritten by a new install)
            if (useStore.getState().cliInstallerState === 'completed') {
              useStore.setState({ cliInstallerState: 'idle' });
            }
          }, 3000);
          break;
        case 'error':
          useStore.setState({
            cliInstallerState: 'error',
            cliInstallerError: progress.error ?? 'Unknown error',
          });
          break;
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(() => {
        cleanup();
        if (cliCompletedRevertTimer) {
          clearTimeout(cliCompletedRevertTimer);
          cliCompletedRevertTimer = null;
        }
      });
    }
  }

  // Listen for updater status events from main process
  if (api.updater?.onStatus) {
    const cleanup = api.updater.onStatus((_event: unknown, status: unknown) => {
      const s = status as UpdaterStatus;
      switch (s.type) {
        case 'checking':
          useStore.setState({ updateStatus: 'checking' });
          break;
        case 'available':
          useStore.setState({
            updateStatus: 'available',
            availableVersion: s.version ?? null,
            releaseNotes: s.releaseNotes ?? null,
            showUpdateDialog: true,
          });
          break;
        case 'not-available':
          useStore.setState({ updateStatus: 'not-available' });
          break;
        case 'downloading':
          useStore.setState({
            updateStatus: 'downloading',
            downloadProgress: s.progress?.percent ?? 0,
          });
          break;
        case 'downloaded':
          useStore.setState({
            updateStatus: 'downloaded',
            downloadProgress: 100,
            availableVersion: s.version ?? useStore.getState().availableVersion,
          });
          break;
        case 'error':
          useStore.setState({
            updateStatus: 'error',
            updateError: s.error ?? 'Unknown error',
          });
          break;
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // Listen for SSH connection status changes from main process
  // NOTE: Only syncs connection status here. Data fetching is handled by
  // connectionSlice.connectSsh/disconnectSsh and contextSlice.switchContext.
  if (api.ssh?.onStatus) {
    const cleanup = api.ssh.onStatus((_event: unknown, status: unknown) => {
      const s = status as { state: string; host: string | null; error: string | null };
      useStore
        .getState()
        .setConnectionStatus(
          s.state as 'disconnected' | 'connecting' | 'connected' | 'error',
          s.host,
          s.error
        );
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // Listen for context changes from main process (e.g., SSH disconnect)
  if (api.context?.onChanged) {
    const cleanup = api.context.onChanged((_event: unknown, data: unknown) => {
      const { id } = data as { id: string; type: string };
      const currentContextId = useStore.getState().activeContextId;
      if (id !== currentContextId) {
        // Main process switched context externally (e.g., SSH disconnect)
        // Trigger renderer-side context switch to sync state
        void useStore.getState().switchContext(id);
      }
    });
    if (typeof cleanup === 'function') {
      cleanupFns.push(cleanup);
    }
  }

  // Return cleanup function
  return () => {
    for (const timer of pendingSessionRefreshTimers.values()) {
      clearTimeout(timer);
    }
    pendingSessionRefreshTimers.clear();
    for (const timer of pendingProjectRefreshTimers.values()) {
      clearTimeout(timer);
    }
    pendingProjectRefreshTimers.clear();
    cleanupFns.forEach((fn) => fn());
  };
}
