import { Email } from '@/hooks/use-courrier'; // Define all possible state types export interface Account { id: string; name: string; email: string; color: string; folders: string[]; } export interface EmailState { accounts: Account[]; selectedAccount: Account | null; selectedFolders: Record; currentFolder: string; emails: Email[]; selectedEmail: Email | null; selectedEmailIds: string[]; isLoading: boolean; isLoadingUnreadCounts: boolean; error: string | null; page: number; perPage: number; totalPages: number; totalEmails: number; mailboxes: string[]; unreadCountMap: Record>; showFolders: boolean; currentAccountId?: string; } // Define all possible action types export type EmailAction = | { type: 'SET_ACCOUNTS', payload: Account[] } | { type: 'SELECT_ACCOUNT', payload: Account } | { type: 'CHANGE_FOLDER', payload: { folder: string, accountId: string } } | { type: 'SET_EMAILS', payload: Email[] } | { type: 'APPEND_EMAILS', payload: Email[] } | { type: 'SELECT_EMAIL', payload: { emailId: string, accountId: string, folder: string, email: Email | null } } | { type: 'TOGGLE_EMAIL_SELECTION', payload: string } | { type: 'TOGGLE_SELECT_ALL' } | { type: 'CLEAR_SELECTED_EMAILS' } | { type: 'SET_LOADING', payload: boolean } | { type: 'SET_LOADING_UNREAD_COUNTS', payload: boolean } | { type: 'SET_ERROR', payload: string | null } | { type: 'SET_PAGE', payload: number } | { type: 'INCREMENT_PAGE' } | { type: 'SET_TOTAL_PAGES', payload: number } | { type: 'SET_TOTAL_EMAILS', payload: number } | { type: 'SET_MAILBOXES', payload: string[] } | { type: 'UPDATE_UNREAD_COUNT', payload: { accountId: string, folder: string, count: number } } | { type: 'SET_UNREAD_COUNTS', payload: Record> } | { type: 'TOGGLE_SHOW_FOLDERS', payload: boolean } | { type: 'MARK_EMAIL_AS_READ', payload: { emailId: string, isRead: boolean, accountId?: string } }; // Initial state export const initialState: EmailState = { accounts: [], selectedAccount: null, selectedFolders: {}, currentFolder: 'INBOX', emails: [], selectedEmail: null, selectedEmailIds: [], isLoading: false, isLoadingUnreadCounts: false, error: null, page: 1, perPage: 20, totalPages: 0, totalEmails: 0, mailboxes: [], unreadCountMap: {}, showFolders: false }; // Helper functions for consistency export const normalizeFolderAndAccount = (folder: string, accountId?: string) => { let normalizedFolder: string; let effectiveAccountId: string = accountId || 'default'; // First, handle the folder format if (folder.includes(':')) { // Extract parts if folder already has a prefix const parts = folder.split(':'); const folderAccountId = parts[0]; normalizedFolder = parts[1]; // If explicit accountId is provided, it ALWAYS takes precedence if (accountId) { console.log(`Using provided accountId (${accountId}) over folder prefix (${folderAccountId})`); effectiveAccountId = accountId; } else { effectiveAccountId = folderAccountId; } } else { // No folder prefix, use the folder name as is normalizedFolder = folder; } return { normalizedFolder, effectiveAccountId, prefixedFolder: `${effectiveAccountId}:${normalizedFolder}` }; }; // Reducer function export function emailReducer(state: EmailState, action: EmailAction): EmailState { console.log(`[EMAIL_REDUCER] Action: ${action.type}`, action); switch (action.type) { case 'SET_ACCOUNTS': return { ...state, accounts: action.payload }; case 'SELECT_ACCOUNT': { // This is a critical action that needs special handling const account = action.payload; const inboxFolder = `${account.id}:INBOX`; console.log(`[EMAIL_REDUCER] Selecting account: ${account.email} (${account.id})`); // Return a completely new state that's atomically consistent return { ...state, selectedAccount: account, currentFolder: inboxFolder, selectedFolders: { ...state.selectedFolders, [account.id]: inboxFolder }, // Clear email selections as part of the atomic account switch selectedEmail: null, selectedEmailIds: [], emails: [], isLoading: true, showFolders: true, page: 1 }; } case 'CHANGE_FOLDER': { const { folder, accountId } = action.payload; // Use our helper to ensure consistent folder/account handling const { normalizedFolder, effectiveAccountId, prefixedFolder } = normalizeFolderAndAccount(folder, accountId); console.log(`[EMAIL_REDUCER] Changing folder to: ${prefixedFolder} (account: ${effectiveAccountId})`); // Return a new state with consistent folder and account info return { ...state, currentFolder: prefixedFolder, selectedFolders: { ...state.selectedFolders, [effectiveAccountId]: prefixedFolder }, // Clear email-specific state when changing folders selectedEmail: null, selectedEmailIds: [], emails: [], isLoading: true, page: 1 }; } case 'SET_EMAILS': // Sort emails by date (newest first) to ensure consistent sorting // First make a copy to avoid mutating the input const unsortedEmails = [...action.payload]; // For debugging - log a few emails before sorting if (unsortedEmails.length > 0) { console.log(`[EMAIL_REDUCER] Sorting ${unsortedEmails.length} emails`); // Log a sample of emails before sorting console.log('[EMAIL_REDUCER] Sample emails before sorting:', unsortedEmails.slice(0, 3).map(e => ({ id: e.id.substring(0, 8), subject: e.subject?.substring(0, 20), date: e.date, timestamp: new Date(e.date).getTime() })) ); } // CRITICAL FIX: Enhanced sorting function that ensures proper date handling const sortedEmails = unsortedEmails.sort((a, b) => { // Convert all dates to timestamps for comparison let dateA: number, dateB: number; try { dateA = a.date instanceof Date ? a.date.getTime() : new Date(a.date).getTime(); } catch (e) { dateA = 0; // Default to oldest if invalid } try { dateB = b.date instanceof Date ? b.date.getTime() : new Date(b.date).getTime(); } catch (e) { dateB = 0; // Default to oldest if invalid } // Handle invalid dates if (isNaN(dateA) && isNaN(dateB)) return 0; if (isNaN(dateA)) return 1; // Put invalid dates at the end if (isNaN(dateB)) return -1; // Sort newest first return dateB - dateA; }); // For debugging - log a few emails after sorting if (sortedEmails.length > 0) { console.log('[EMAIL_REDUCER] Sample emails after sorting:', sortedEmails.slice(0, 3).map(e => ({ id: e.id.substring(0, 8), subject: e.subject?.substring(0, 20), date: e.date, timestamp: new Date(e.date).getTime() })) ); } return { ...state, emails: sortedEmails, isLoading: false }; case 'APPEND_EMAILS': { // Create a set of existing email IDs to avoid duplicates const existingIds = new Set(state.emails.map(email => email.id)); console.log(`[DEBUG-REDUCER] APPEND_EMAILS - Got ${action.payload.length} emails to append, current list has ${state.emails.length}`); // Filter out any duplicates before appending const newEmails = action.payload.filter(email => !existingIds.has(email.id)); // Log appending for debugging console.log(`[DEBUG-REDUCER] Filtered to ${newEmails.length} new non-duplicate emails`); // CRITICAL FIX: If no new emails were found, set isLoading to false but don't change the email list if (newEmails.length === 0) { console.log('[DEBUG-REDUCER] No new emails to append, returning current state with isLoading=false'); return { ...state, isLoading: false }; } // Debug the dates to check sorting if (newEmails.length > 0) { console.log('[DEBUG-REDUCER] Sample new emails before combining:', newEmails.slice(0, 3).map(e => ({ id: e.id.substring(0, 8), subject: e.subject?.substring(0, 20), date: e.date, timestamp: new Date(e.date).getTime() })) ); } // FIXED: Properly combine existing and new emails // We need to ensure we keep ALL emails when appending const combinedEmails = [...state.emails, ...newEmails]; // Sort combined emails by date (newest first) const sortedEmails = combinedEmails.sort( (a, b) => { // Convert all dates to timestamps for comparison let dateA: number, dateB: number; try { dateA = a.date instanceof Date ? a.date.getTime() : new Date(a.date).getTime(); } catch (e) { dateA = 0; // Default to oldest if invalid } try { dateB = b.date instanceof Date ? b.date.getTime() : new Date(b.date).getTime(); } catch (e) { dateB = 0; // Default to oldest if invalid } // Handle invalid dates if (isNaN(dateA) && isNaN(dateB)) return 0; if (isNaN(dateA)) return 1; // Put invalid dates at the end if (isNaN(dateB)) return -1; // Sort newest first return dateB - dateA; } ); console.log(`[DEBUG-REDUCER] Final combined list has ${sortedEmails.length} emails (${state.emails.length} old + ${newEmails.length} new)`); return { ...state, emails: sortedEmails, isLoading: false }; } case 'SELECT_EMAIL': return { ...state, selectedEmail: action.payload.email, // Don't modify selectedEmailIds when just selecting an email for preview }; case 'TOGGLE_EMAIL_SELECTION': { const emailId = action.payload; const isSelected = state.selectedEmailIds.includes(emailId); return { ...state, selectedEmailIds: isSelected ? state.selectedEmailIds.filter(id => id !== emailId) : [...state.selectedEmailIds, emailId] }; } case 'TOGGLE_SELECT_ALL': { // If all emails are already selected, clear the selection const allEmailIds = state.emails.map(email => email.id); const allSelected = allEmailIds.every(id => state.selectedEmailIds.includes(id)); return { ...state, selectedEmailIds: allSelected ? [] : allEmailIds }; } case 'CLEAR_SELECTED_EMAILS': return { ...state, selectedEmailIds: [], selectedEmail: null }; case 'SET_LOADING': return { ...state, isLoading: action.payload }; case 'SET_LOADING_UNREAD_COUNTS': return { ...state, isLoadingUnreadCounts: action.payload }; case 'SET_ERROR': return { ...state, error: action.payload, isLoading: false }; case 'SET_PAGE': return { ...state, page: action.payload }; case 'INCREMENT_PAGE': return { ...state, page: state.page + 1 }; case 'SET_TOTAL_PAGES': return { ...state, totalPages: action.payload }; case 'SET_TOTAL_EMAILS': return { ...state, totalEmails: action.payload }; case 'SET_MAILBOXES': return { ...state, mailboxes: action.payload }; case 'UPDATE_UNREAD_COUNT': { const { accountId, folder, count } = action.payload; return { ...state, unreadCountMap: { ...state.unreadCountMap, [accountId]: { ...(state.unreadCountMap[accountId] || {}), [folder]: count } } }; } case 'SET_UNREAD_COUNTS': return { ...state, unreadCountMap: action.payload }; case 'TOGGLE_SHOW_FOLDERS': return { ...state, showFolders: action.payload }; case 'MARK_EMAIL_AS_READ': { const { emailId, isRead, accountId } = action.payload; // Update emails list const updatedEmails = state.emails.map(email => (email.id === emailId && (!accountId || email.accountId === accountId)) ? { ...email, flags: { ...email.flags, seen: isRead } } : email ); // Update selected email if it matches const updatedSelectedEmail = state.selectedEmail && state.selectedEmail.id === emailId && (!accountId || state.selectedEmail.accountId === accountId) ? { ...state.selectedEmail, flags: { ...state.selectedEmail.flags, seen: isRead } } : state.selectedEmail; return { ...state, emails: updatedEmails, selectedEmail: updatedSelectedEmail }; } default: return state; } }