import React from 'react'; import type { Session } from '@opencode-ai/sdk/v2'; import type { SessionGroup, SessionNode } from '../types'; import { normalizePath } from '../utils'; import type { MainTab } from '@/stores/useUIStore'; import { useUIStore } from '@/stores/useUIStore'; type ProjectSection = { project: { id: string; normalizedPath: string }; groups: SessionGroup[]; }; type Args = { projectSections: ProjectSection[]; activeProjectId: string | null; activeSessionByProject: Map; setActiveSessionByProject: React.Dispatch>>; currentSessionId: string | null; handleSessionSelect: (sessionId: string, sessionDirectory: string | null, projectId?: string | null) => void; newSessionDraftOpen: boolean; mobileVariant: boolean; openNewSessionDraft: (options?: { directoryOverride?: string | null }) => void; setActiveMainTab: (tab: MainTab) => void; setSessionSwitcherOpen: (open: boolean) => void; }; export const useProjectSessionSelection = (args: Args): void => { const { projectSections, activeProjectId, activeSessionByProject, setActiveSessionByProject, currentSessionId, handleSessionSelect, newSessionDraftOpen, mobileVariant, openNewSessionDraft, setActiveMainTab, setSessionSwitcherOpen, } = args; const projectSessionMeta = React.useMemo(() => { const metaByProject = new Map>(); const firstSessionByProject = new Map(); const visitNodes = ( projectId: string, projectRoot: string, fallbackDirectory: string | null, nodes: SessionNode[], ) => { if (!metaByProject.has(projectId)) { metaByProject.set(projectId, new Map()); } const projectMap = metaByProject.get(projectId)!; nodes.forEach((node) => { const sessionDirectory = normalizePath( node.worktree?.path ?? (node.session as Session & { directory?: string | null }).directory ?? fallbackDirectory ?? projectRoot, ); projectMap.set(node.session.id, { directory: sessionDirectory }); if (!firstSessionByProject.has(projectId)) { firstSessionByProject.set(projectId, { id: node.session.id, directory: sessionDirectory }); } if (node.children.length > 0) { visitNodes(projectId, projectRoot, sessionDirectory, node.children); } }); }; projectSections.forEach((section) => { section.groups.forEach((group) => { visitNodes(section.project.id, section.project.normalizedPath, group.directory, group.sessions); }); }); return { metaByProject, firstSessionByProject }; }, [projectSections]); const previousActiveProjectRef = React.useRef(null); React.useLayoutEffect(() => { if (!activeProjectId) { return; } if (newSessionDraftOpen) { return; } if (useUIStore.getState().isNewWorktreeDialogOpen) { return; } if (previousActiveProjectRef.current === activeProjectId) { return; } const section = projectSections.find((item) => item.project.id === activeProjectId); if (!section) { return; } previousActiveProjectRef.current = activeProjectId; const projectMap = projectSessionMeta.metaByProject.get(activeProjectId); if (currentSessionId && projectMap && projectMap.has(currentSessionId)) { setActiveSessionByProject((prev) => { if (prev.get(activeProjectId) === currentSessionId) { return prev; } const next = new Map(prev); next.set(activeProjectId, currentSessionId); return next; }); return; } if (!projectMap || projectMap.size === 0) { setActiveMainTab('chat'); if (mobileVariant) { setSessionSwitcherOpen(false); } openNewSessionDraft({ directoryOverride: section.project.normalizedPath }); return; } const rememberedSessionId = activeSessionByProject.get(activeProjectId); const remembered = rememberedSessionId && projectMap.has(rememberedSessionId) ? rememberedSessionId : null; const fallback = projectSessionMeta.firstSessionByProject.get(activeProjectId)?.id ?? null; const targetSessionId = remembered ?? fallback; if (!targetSessionId || targetSessionId === currentSessionId) { return; } const targetDirectory = projectMap.get(targetSessionId)?.directory ?? null; handleSessionSelect(targetSessionId, targetDirectory, activeProjectId); }, [ activeProjectId, activeSessionByProject, currentSessionId, handleSessionSelect, newSessionDraftOpen, mobileVariant, openNewSessionDraft, projectSections, projectSessionMeta, setActiveMainTab, setSessionSwitcherOpen, setActiveSessionByProject, ]); React.useEffect(() => { if (!activeProjectId || !currentSessionId) { return; } const projectMap = projectSessionMeta.metaByProject.get(activeProjectId); if (!projectMap || !projectMap.has(currentSessionId)) { return; } setActiveSessionByProject((prev) => { if (prev.get(activeProjectId) === currentSessionId) { return prev; } const next = new Map(prev); next.set(activeProjectId, currentSessionId); return next; }); }, [activeProjectId, currentSessionId, projectSessionMeta, setActiveSessionByProject]); };