"use client" import { createContext, useContext, useEffect, useMemo, useState, useCallback, useRef, type ReactNode, } from "react" import { toErrorMessage } from "@/lib/app-error" import { getFolder, listFolderConversations } from "@/lib/tauri" import type { AgentType, AgentStats, DbConversationSummary, FolderDetail, } from "@/lib/types" interface SelectedConversation { id: number agentType: AgentType } interface NewConversationState { agentType: AgentType workingDir: string } interface FolderContextValue { folder: FolderDetail | null folderId: number folderLoading: boolean conversations: DbConversationSummary[] loading: boolean refreshing: boolean error: string | null selectedConversation: SelectedConversation | null selectConversation: (id: number, agentType: AgentType) => void clearSelection: () => void newConversation: NewConversationState | null startNewConversation: (agentType: AgentType, workingDir: string) => void cancelNewConversation: () => void stats: AgentStats | null refreshConversations: () => void } const FolderContext = createContext(null) export function useFolderContext() { const ctx = useContext(FolderContext) if (!ctx) { throw new Error("useFolderContext must be used within FolderProvider") } return ctx } function computeStats(conversations: DbConversationSummary[]): AgentStats { const byAgent = new Map() let totalMessages = 0 for (const s of conversations) { byAgent.set(s.agent_type, (byAgent.get(s.agent_type) ?? 0) + 1) totalMessages += s.message_count } return { total_conversations: conversations.length, total_messages: totalMessages, by_agent: Array.from(byAgent.entries()).map(([agent_type, count]) => ({ agent_type, conversation_count: count, })), } } /** Module-level cache — survives component unmounts / page navigations. */ const cache = new Map() interface FolderProviderProps { children: ReactNode folderId: number initialConversationId?: number | null initialAgentType?: AgentType | null } export function FolderProvider({ children, folderId, initialConversationId, initialAgentType, }: FolderProviderProps) { // Folder info const [folder, setFolder] = useState(null) const [folderLoading, setFolderLoading] = useState(true) // Conversations const cacheKey = String(folderId) const [conversations, setConversations] = useState( () => cache.get(cacheKey) ?? [] ) const [loading, setLoading] = useState(() => !cache.has(cacheKey)) const [refreshing, setRefreshing] = useState(false) const [error, setError] = useState(null) const [selectedConversation, setSelectedConversation] = useState(() => { if (initialConversationId != null && initialAgentType) { return { id: initialConversationId, agentType: initialAgentType } } return null }) // Sync selection when URL params change (e.g. navigation) useEffect(() => { if (initialConversationId != null && initialAgentType) { setSelectedConversation({ id: initialConversationId, agentType: initialAgentType, }) } }, [initialConversationId, initialAgentType]) const [newConversation, setNewConversation] = useState(null) const mountedRef = useRef(true) // Fetch folder info useEffect(() => { let cancelled = false setFolderLoading(true) getFolder(folderId) .then((f) => { if (!cancelled) { setFolder(f) setFolderLoading(false) } }) .catch((err) => { console.error("[FolderProvider] getFolder failed:", err) if (!cancelled) setFolderLoading(false) }) return () => { cancelled = true } }, [folderId]) const fetchConversations = useCallback(async () => { const cached = cache.get(cacheKey) if (cached) { setConversations(cached) setLoading(false) setRefreshing(true) } else { setLoading(true) } try { setError(null) const data = await listFolderConversations({ folder_id: folderId, status: null, }) if (!mountedRef.current) return cache.set(cacheKey, data) setConversations(data) } catch (e) { if (!mountedRef.current) return if (!cached) { setError(toErrorMessage(e)) } } finally { if (mountedRef.current) { setLoading(false) setRefreshing(false) } } }, [folderId, cacheKey]) useEffect(() => { fetchConversations() }, [fetchConversations]) useEffect(() => { mountedRef.current = true return () => { mountedRef.current = false } }, []) const selectConversation = useCallback((id: number, agentType: AgentType) => { setSelectedConversation({ id, agentType }) setNewConversation(null) }, []) const clearSelection = useCallback(() => { setSelectedConversation(null) }, []) const startNewConversation = useCallback( (agentType: AgentType, workingDir: string) => { setNewConversation({ agentType, workingDir }) setSelectedConversation(null) }, [] ) const cancelNewConversation = useCallback(() => { setNewConversation(null) }, []) const refreshConversations = useCallback(() => { cache.delete(cacheKey) fetchConversations() }, [cacheKey, fetchConversations]) const stats = useMemo( () => (conversations.length > 0 ? computeStats(conversations) : null), [conversations] ) const value = useMemo( () => ({ folder, folderId, folderLoading, conversations, loading, refreshing, error, selectedConversation, selectConversation, clearSelection, newConversation, startNewConversation, cancelNewConversation, stats, refreshConversations, }), [ folder, folderId, folderLoading, conversations, loading, refreshing, error, selectedConversation, selectConversation, clearSelection, newConversation, startNewConversation, cancelNewConversation, stats, refreshConversations, ] ) return ( {children} ) }