"use client" import { useCallback, useEffect, useMemo, useRef, useState, } from "react" import { useSearchParams, useRouter } from "next/navigation" import { useQueryClient } from "@tanstack/react-query" import { buildLabelTextToNavColorClass } from "@/components/gmail/mail-label-pills" import { useMessages, useMailSearch } from "@/lib/api/hooks/use-mail-queries" import { useUpdateFlags, useUpdateLabels, useDeleteMessage, } from "@/lib/api/hooks/use-mail-mutations" import type { ApiMessageSummary, PaginatedResponse } from "@/lib/api/types" import type { Email, EmailAttachment } from "@/lib/email-data" import { isThreadHeadMessage, } from "@/lib/mail-thread" import { repairSnippet } from "@/lib/mail-mime-body" import { useScheduledMail } from "@/lib/scheduled-mail-context" import { useMailStore } from "@/lib/stores/mail-store" import { useScheduledStore } from "@/lib/stores/scheduled-store" import { usePersistHydrated } from "@/hooks/use-persist-hydrated" import { useIsMd } from "@/hooks/use-md-breakpoint" import { sortEmailsForInbox } from "@/lib/mail-settings/sort-emails" import { useMailSettingsStore } from "@/lib/stores/mail-settings-store" import { useActiveAccount } from "@/lib/stores/account-store" import { useMailSearchStore } from "@/lib/stores/mail-search-store" import { emailMatchesInboxTab, type MailFolderFilterCtx, type MailNavFolderMaps, } from "@/lib/mail-folder-filter" import { getMailNavFolderLabel, inboxTabDisplayLabel, } from "@/lib/sidebar-nav-data" import { buildInboxCategoryTabIcons } from "@/lib/inbox-category-tabs" import { INBOX_ALL_TAB, SEARCH_FOLDER_ID, inboxTabShowsInactiveMeta, normalizeInboxTabSegment, } from "@/lib/mail-url" import { parseSearchParams, buildSearchUrl, type SearchParams, } from "@/lib/mail-search/search-params" import { useSidebarNav, registerNavEmailSync } from "@/lib/sidebar-nav-context" import { useMoveTargets } from "@/components/gmail/move-to-menu-items" import { buildListMailIndex } from "@/components/gmail/email-list/list-mail-index" import { useComposeActions, useComposeDrafts, } from "@/lib/compose-context" import { type EmailListProps, buildInboxTabBarItems, } from "@/components/gmail/email-list/email-list-helpers" import { useMailListPullRefresh } from "@/hooks/use-mail-list-pull-refresh" import { ensureVcLogosCollection } from "@/lib/register-vc-logos" import { attachmentsForEmailList } from "@/lib/attachment-display" import { resolveParsedCalendarInvitation } from "@/lib/resolve-email-calendar-invitation" import { resolveEmailInboxCategoryTabs } from "@/lib/inbox-category-tabs" import { cleanSenderName } from "@/lib/sender-display" import { threadStoreId } from "@/lib/mail-settings/list-row-id" import { useIsXs } from "@/hooks/use-xs" import { useTouchNav } from "@/hooks/use-touch-nav" import type { MessageSearchFilter } from "@/lib/api/types" import { mailFlagIsRead, mailFlagIsStarred, mailFlagIsImportant, mailFlagsWithRead, mailFlagsWithStarred, mailFlagsWithImportant, } from "@/lib/mail-flags" import { LIST_PAGE_SIZE, type ListPageSize } from "@/lib/mail-list-page-size" function apiMessageToEmail(msg: ApiMessageSummary): Email { const sender = msg.from[0]?.name || msg.from[0]?.address || "" const senderEmail = msg.from[0]?.address || "" return { id: msg.id, sender, senderEmail, subject: msg.subject, preview: repairSnippet(msg.snippet) ?? msg.snippet, date: msg.date, read: mailFlagIsRead(msg.flags), starred: mailFlagIsStarred(msg.flags), important: mailFlagIsImportant(msg.flags, msg.labels), spam: msg.labels.includes("spam"), hasAttachment: msg.has_attachments, labels: msg.labels, threadHeadId: msg.thread_id ?? msg.id, threadMessageIds: [msg.id], isThreadHead: true, } } export function useEmailListData({ selectedFolder, inboxTab, listPage, openMailId, splitView = false, onMailRouteNavigate, onFolderUnreadCountsChange, }: EmailListProps) { const isViewMode = openMailId !== null && !splitView const showSplitReadingPane = splitView && openMailId !== null const isSearchMode = selectedFolder === SEARCH_FOLDER_ID const searchRouter = useRouter() const searchAccount = useActiveAccount() const setAdvancedOpen = useMailSearchStore((s) => s.setAdvancedOpen) const urlSearchParams = useSearchParams() const searchParams = useMemo( () => (isSearchMode ? parseSearchParams(urlSearchParams) : null), [isSearchMode, urlSearchParams] ) const setSearchFilter = useCallback( (patch: Partial) => { if (!searchParams) return searchRouter.push(buildSearchUrl({ ...searchParams, ...patch })) }, [searchParams, searchRouter] ) const toggleSearchFilter = useCallback( (key: keyof SearchParams, value: string) => { if (!searchParams) return const next = { ...searchParams } if (key === "has") { const arr = [...next.has] if (arr.includes(value)) next.has = arr.filter((v) => v !== value) else next.has = [...arr, value] } else if (key === "excludeChats") { next.excludeChats = !next.excludeChats } else { const cur = (next as Record)[key] ;(next as Record)[key] = cur === value ? "" : value } searchRouter.push(buildSearchUrl(next)) }, [searchParams, searchRouter] ) const { savedThreadReplyDrafts } = useComposeDrafts() const { openCompose, openComposeWithInitial, closeAllInlineComposes, pruneInlineComposesToOpenThread, } = useComposeActions() const { scheduledEmails, snoozedEmails, requestDeleteScheduled, requestArchiveScheduled, requestSnoozeScheduled, requestToggleReadScheduled, requestRescheduleScheduled, requestGetScheduledEditPayload, requestSendScheduledNow, requestSnoozeMailboxEmail, requestRestoreSnoozedToInbox, } = useScheduledMail() const scheduledPersistHydrated = usePersistHydrated(useScheduledStore) const accountId = searchAccount?.id const queryClient = useQueryClient() const listPageSize = useMailSettingsStore((s) => s.listPageSize) const setListPageSize = useMailSettingsStore((s) => s.setListPageSize) const effectiveApiFolder = useMemo(() => { if (isSearchMode) return "__search__" if (selectedFolder === "scheduled" || selectedFolder === "snoozed") return "__local__" if (selectedFolder === "inbox") return "inbox" return selectedFolder }, [selectedFolder, isSearchMode]) const searchFilter = useMemo(() => { if (!isSearchMode || !searchParams) return null return { q: searchParams.q || undefined, from: searchParams.from || undefined, label: searchParams.in !== "all" ? searchParams.in : undefined, account_id: accountId, date_from: searchParams.after || undefined, date_to: searchParams.before || undefined, has_attachment: searchParams.has.includes("attachment") ? true : undefined, } }, [isSearchMode, searchParams, accountId]) const messagesQuery = useMessages( effectiveApiFolder === "__search__" || effectiveApiFolder === "__local__" ? "inbox" : effectiveApiFolder, accountId, listPage, listPageSize ) const searchQuery = useMailSearch(searchFilter) const updateFlags = useUpdateFlags() const updateLabels = useUpdateLabels() const deleteMessage = useDeleteMessage() const apiMessages: ApiMessageSummary[] = useMemo(() => { if (isSearchMode) return searchQuery.data?.data ?? [] if (effectiveApiFolder === "__local__") return [] return messagesQuery.data?.data ?? [] }, [isSearchMode, effectiveApiFolder, searchQuery.data, messagesQuery.data]) const apiEmails: Email[] = useMemo( () => apiMessages.map(apiMessageToEmail), [apiMessages] ) const apiMessagesById = useMemo( () => new Map(apiMessages.map((m) => [m.id, m])), [apiMessages] ) const allEmails = useMemo(() => { if (selectedFolder === "scheduled" && scheduledPersistHydrated) { return scheduledEmails.map((entry) => ({ id: entry.id, sender: entry.to[0]?.name ?? "Destinataire", senderEmail: entry.to[0]?.address, subject: entry.subject || "(Sans objet)", preview: "", body: "", date: entry.scheduled_at ?? entry.created_at, read: true, starred: false, important: false, labels: ["scheduled"], scheduledSendAt: entry.scheduled_at, scheduledToName: entry.to[0]?.name, })) } if (selectedFolder === "snoozed" && scheduledPersistHydrated) { return snoozedEmails } return apiEmails }, [ selectedFolder, scheduledPersistHydrated, scheduledEmails, snoozedEmails, apiEmails, ]) const emailById = useMemo( () => new Map(allEmails.map((e) => [e.id, e])), [allEmails] ) const isLoading = isSearchMode ? searchQuery.isLoading : messagesQuery.isLoading const error = isSearchMode ? searchQuery.error : messagesQuery.error const isFetching = isSearchMode ? searchQuery.isFetching : messagesQuery.isFetching const sidebarNav = useSidebarNav() const navMaps = useMemo( () => ({ folderIdToLabel: sidebarNav.folderIdToLabel, folderTree: sidebarNav.folderTree, labelRows: sidebarNav.labelRows, }), [sidebarNav.folderIdToLabel, sidebarNav.folderTree, sidebarNav.labelRows] ) const inboxCategoryTabIconsCatalog = useMemo( () => buildInboxCategoryTabIcons(sidebarNav.labelRows), [sidebarNav.labelRows] ) const inboxTabBarItems = useMemo( () => buildInboxTabBarItems(sidebarNav.labelRows), [sidebarNav.labelRows] ) const listRowLabelBgByTextLower = useMemo( () => buildLabelTextToNavColorClass(sidebarNav.folderTree, sidebarNav.labelRows), [sidebarNav.folderTree, sidebarNav.labelRows] ) const [rescheduleTarget, setRescheduleTarget] = useState<{ id: string value: string panelOpen: boolean } | null>(null) const rescheduleDismissTimeoutsRef = useRef< Map> >(new Map()) const scheduleReschedulePopoverDismiss = useCallback((rowId: string) => { const existing = rescheduleDismissTimeoutsRef.current.get(rowId) if (existing) clearTimeout(existing) const t = setTimeout(() => { rescheduleDismissTimeoutsRef.current.delete(rowId) setRescheduleTarget((p) => (p?.id === rowId ? null : p)) }, 280) rescheduleDismissTimeoutsRef.current.set(rowId, t) }, []) useEffect(() => { const m = rescheduleDismissTimeoutsRef.current return () => { for (const t of m.values()) clearTimeout(t) m.clear() } }, []) useEffect(() => { ensureVcLogosCollection() }, []) const [cmScheduledRescheduleValue, setCmScheduledRescheduleValue] = useState("") const handleEditScheduledMail = useCallback( async (id: string) => { const payload = await requestGetScheduledEditPayload(id) if (!payload) return openComposeWithInitial({ to: payload.to, subject: payload.subject, bodyHtml: payload.bodyHtml, editingScheduledId: id, scheduledSendAtIso: payload.sendAtIso, focusToOnMount: false, focusBodyOnMount: true, }) }, [requestGetScheduledEditPayload, openComposeWithInitial] ) useEffect(() => { if (!openMailId) { closeAllInlineComposes() } else { const msg = emailById.get(openMailId) pruneInlineComposesToOpenThread(msg ? threadStoreId(msg) : openMailId) } }, [ openMailId, emailById, closeAllInlineComposes, pruneInlineComposesToOpenThread, ]) const conversationMode = useMailSettingsStore((s) => s.conversationMode) const inboxSort = useMailSettingsStore((s) => s.inboxSort) const density = useMailSettingsStore((s) => s.density) const isMd = useIsMd() const readOverrides = useMemo>(() => ({}), []) const starredEmails = useMemo(() => [], []) const importantEmails = useMemo(() => [], []) const labelEdits = useMemo(() => ({ additions: {} as Record, removals: {} as Record }), []) const hiddenEmailIds = useMemo(() => [], []) const notSpamEmailIds = useMemo(() => [], []) const setReadOverrides = useCallback( (updater: (prev: Record) => Record) => { const changes = updater({}) for (const [id, isRead] of Object.entries(changes)) { const msg = apiMessagesById.get(id) if (!msg) continue const alreadyRead = mailFlagIsRead(msg.flags) if (isRead === alreadyRead) continue const flags = mailFlagsWithRead(msg.flags, isRead) updateFlags.mutate({ id, flags }) } }, [apiMessagesById, updateFlags] ) const setLabelEdits = useCallback( (updater: (prev: { additions: Record; removals: Record }) => { additions: Record; removals: Record }) => { const result = updater({ additions: {}, removals: {} }) for (const [id, additions] of Object.entries(result.additions)) { const msg = apiMessagesById.get(id) if (!msg) continue const newLabels = [...new Set([...msg.labels, ...additions])] const removals = result.removals[id] ?? [] const finalLabels = newLabels.filter( (l) => !removals.some((r) => r.toLowerCase() === l.toLowerCase()) ) updateLabels.mutate({ id, labels: finalLabels }) } for (const [id, removals] of Object.entries(result.removals)) { if (result.additions[id]) continue const msg = apiMessagesById.get(id) if (!msg) continue const finalLabels = msg.labels.filter( (l) => !removals.some((r) => r.toLowerCase() === l.toLowerCase()) ) updateLabels.mutate({ id, labels: finalLabels }) } }, [apiMessagesById, updateLabels] ) const mailActions = useMemo(() => ({ markSeen: (id: string) => useMailStore.getState().markSeen(id), pushRecentMoveTarget: (targetId: string) => useMailStore.getState().pushRecentMoveTarget(targetId), hideEmail: (id: string) => deleteMessage.mutate({ id }), hideEmails: (ids: string[]) => { for (const id of ids) deleteMessage.mutate({ id }) }, markNotSpam: (id: string) => { const msg = apiMessagesById.get(id) if (!msg) return const newLabels = msg.labels.filter((l) => l !== "spam") if (!newLabels.includes("inbox")) newLabels.push("inbox") updateLabels.mutate({ id, labels: newLabels }) }, unhideEmail: (_id: string) => { /* no-op - API manages visibility */ }, toggleStar: (id: string) => { const msg = apiMessagesById.get(id) if (!msg) return const starred = mailFlagIsStarred(msg.flags) updateFlags.mutate({ id, flags: mailFlagsWithStarred(msg.flags, !starred) }) }, toggleImportant: (id: string) => { const msg = apiMessagesById.get(id) if (!msg) return const important = mailFlagIsImportant(msg.flags, msg.labels) updateFlags.mutate({ id, flags: mailFlagsWithImportant(msg.flags, !important) }) }, }), [deleteMessage, updateLabels, updateFlags, apiMessagesById]) useEffect(() => { registerNavEmailSync({ renameLabel: (_from, _to) => { queryClient.invalidateQueries({ queryKey: ["messages"] }) }, removeLabel: (_label) => { queryClient.invalidateQueries({ queryKey: ["messages"] }) }, }) return () => registerNavEmailSync(null) }, [queryClient]) const [labelPickerQuery, setLabelPickerQuery] = useState("") const recentMoveTargets = useMailStore((s) => s.recentMoveTargets) const [mobileVisibleCount, setMobileVisibleCount] = useState(LIST_PAGE_SIZE) const isXs = useIsXs() const touchNav = useTouchNav() const seenEmailIdsRaw = useMailStore((s) => s.seenEmailIds) const seenEmailIds = useMemo(() => new Set(seenEmailIdsRaw), [seenEmailIdsRaw]) const handleRefreshMessages = useCallback(async () => { await queryClient.invalidateQueries({ queryKey: ["messages"] }) }, [queryClient]) const { isRefreshing, setIsRefreshing, listViewportRef, pullContentRef, pullIconRef, } = useMailListPullRefresh({ enabled: isXs && !isViewMode, isViewMode, onRefresh: handleRefreshMessages, }) const handleManualRefresh = useCallback(async () => { if (isRefreshing) return setIsRefreshing(true) try { await handleRefreshMessages() } finally { setIsRefreshing(false) } }, [isRefreshing, handleRefreshMessages, setIsRefreshing]) const markEmailSeen = useCallback((id: string) => { useMailStore.getState().markSeen(id) }, []) const folderFilterCtx = useMemo( () => ({ starredEmailIds: starredEmails, importantEmailIds: importantEmails, }), [starredEmails, importantEmails] ) const filteredEmails = useMemo(() => { if (selectedFolder !== "inbox") return allEmails return allEmails.filter((e) => emailMatchesInboxTab(e, inboxTab, folderFilterCtx, navMaps) ) }, [allEmails, selectedFolder, inboxTab, folderFilterCtx, navMaps]) const displayListEmails = useMemo(() => { let rows = filteredEmails if (conversationMode) { rows = rows.filter(isThreadHeadMessage) } return sortEmailsForInbox( rows, inboxSort, { readOverrides: {}, starredIds: [], importantIds: [], }, { conversationMode, byId: emailById } ) }, [ filteredEmails, conversationMode, inboxSort, emailById, ]) const inboxCategoryTabLabel = useMemo( () => inboxTabDisplayLabel( inboxTab, sidebarNav.labelRows, sidebarNav.folderIdToLabel ), [inboxTab, sidebarNav.labelRows, sidebarNav.folderIdToLabel] ) const mobileUnreadCount = useMemo( () => displayListEmails.filter((e) => !e.read).length, [displayListEmails] ) const mobileFolderLabel = useMemo(() => { if (isSearchMode) return "Résultats de recherche" const inboxTabNorm = normalizeInboxTabSegment(inboxTab) return selectedFolder === "inbox" && inboxTabNorm !== "primary" ? inboxCategoryTabLabel : getMailNavFolderLabel(selectedFolder, sidebarNav.folderIdToLabel) }, [ selectedFolder, inboxTab, inboxCategoryTabLabel, sidebarNav.folderIdToLabel, isSearchMode, ]) const paginationTotal = useMemo(() => { if (isSearchMode) return searchQuery.data?.pagination?.total if (effectiveApiFolder === "__local__") return allEmails.length return messagesQuery.data?.pagination?.total }, [isSearchMode, effectiveApiFolder, searchQuery.data, messagesQuery.data, allEmails.length]) const totalPages = useMemo( () => Math.max( 1, Math.ceil((paginationTotal ?? displayListEmails.length) / listPageSize) ), [paginationTotal, displayListEmails.length, listPageSize] ) const paginationRangeStart = useMemo(() => { if (displayListEmails.length === 0) return 0 return (listPage - 1) * listPageSize + 1 }, [displayListEmails.length, listPage, listPageSize]) const paginationRangeEnd = useMemo(() => { if (displayListEmails.length === 0) return 0 if (effectiveApiFolder !== "__local__" && !isSearchMode) { const total = paginationTotal ?? displayListEmails.length return Math.min(listPage * listPageSize, total) } return Math.min(listPage * listPageSize, displayListEmails.length) }, [ displayListEmails.length, listPage, listPageSize, paginationTotal, effectiveApiFolder, isSearchMode, ]) const handleListPageSizeChange = useCallback( (size: ListPageSize) => { if (size === listPageSize) return setListPageSize(size) onMailRouteNavigate({ page: 1 }) }, [listPageSize, setListPageSize, onMailRouteNavigate] ) const pagedEmails = useMemo(() => { if (effectiveApiFolder !== "__local__" && !isSearchMode) { return displayListEmails } const start = (listPage - 1) * listPageSize return displayListEmails.slice(start, start + listPageSize) }, [displayListEmails, listPage, effectiveApiFolder, isSearchMode, listPageSize]) const listEmails = useMemo(() => { if (isXs && !isViewMode) { return displayListEmails.slice(0, mobileVisibleCount) } return pagedEmails }, [isXs, isViewMode, displayListEmails, mobileVisibleCount, pagedEmails]) const listMailIndex = useMemo(() => buildListMailIndex(allEmails), [allEmails]) const listRowExtras = useMemo(() => { const invitationById = new Map< string, ReturnType >() const attachmentsById = new Map() const categoryTabsById = new Map< string, ReturnType >() const subtreeIdsCache = new Map() const showCategoryTabIcons = selectedFolder === "inbox" && normalizeInboxTabSegment(inboxTab) === INBOX_ALL_TAB for (const e of listEmails) { invitationById.set(e.id, resolveParsedCalendarInvitation(e)) attachmentsById.set(e.id, attachmentsForEmailList(e)) if (showCategoryTabIcons) { const tabs = resolveEmailInboxCategoryTabs( e, folderFilterCtx, navMaps, inboxCategoryTabIconsCatalog, subtreeIdsCache ) if (tabs.length > 0) categoryTabsById.set(e.id, tabs) } } return { invitationById, attachmentsById, categoryTabsById } }, [ listEmails, selectedFolder, inboxTab, folderFilterCtx, navMaps, inboxCategoryTabIconsCatalog, ]) useEffect(() => { if (isXs) return if (listPage > totalPages) { onMailRouteNavigate({ page: totalPages }) } }, [isXs, listPage, totalPages, onMailRouteNavigate]) useEffect(() => { if (isXs && !isViewMode) return listViewportRef.current?.scrollTo(0, 0) }, [listPage, selectedFolder, inboxTab, isXs, isViewMode, listViewportRef]) useEffect(() => { if (!isXs) return setMobileVisibleCount(LIST_PAGE_SIZE) listViewportRef.current?.scrollTo(0, 0) }, [selectedFolder, inboxTab, isXs, listViewportRef]) useEffect(() => { const root = listViewportRef.current if (!root || !isXs || isViewMode) return const onScroll = () => { if (mobileVisibleCount >= displayListEmails.length) return const nearBottom = root.scrollTop + root.clientHeight >= root.scrollHeight - 120 if (nearBottom) { setMobileVisibleCount((prev) => Math.min(prev + LIST_PAGE_SIZE, displayListEmails.length) ) } } root.addEventListener("scroll", onScroll, { passive: true }) return () => root.removeEventListener("scroll", onScroll) }, [isXs, isViewMode, mobileVisibleCount, displayListEmails.length, listViewportRef]) const moveTargets = useMoveTargets({ folderTree: sidebarNav.folderTree, recentMoveTargets, currentFolderId: selectedFolder, }) const folderUnreadCounts = useMemo>(() => ({}), []) const seenSerialized = useMemo( () => [...seenEmailIds].sort().join(","), [seenEmailIds] ) const { unseenInTabById, tabUnseenSenderLineById } = useMemo(() => { const seen = new Set( seenSerialized.length > 0 ? seenSerialized.split(",") : [] ) const subtreeIdsCache = new Map() const counts: Record = {} const preview: Record = {} for (const tab of inboxTabBarItems) { const rows: Email[] = [] for (const e of allEmails) { if (seen.has(e.id)) continue if ( !emailMatchesInboxTab( e, tab.id, folderFilterCtx, navMaps, subtreeIdsCache ) ) { continue } rows.push(e) } counts[tab.id] = rows.length if (inboxTabShowsInactiveMeta(tab.id)) { const chain: string[] = [] const used = new Set() for (const e of rows) { const n = cleanSenderName(e.sender).trim() if (!n || used.has(n)) continue used.add(n) chain.push(n) if (chain.length >= 6) break } preview[tab.id] = chain.join(", ") } } return { unseenInTabById: counts, tabUnseenSenderLineById: preview } }, [ seenSerialized, allEmails, inboxTabBarItems, folderFilterCtx, navMaps, ]) useEffect(() => { onFolderUnreadCountsChange?.(folderUnreadCounts) }, [folderUnreadCounts, onFolderUnreadCountsChange]) const listToolbarMode = splitView || !isViewMode const compactInboxTabs = isXs || splitView const activeInboxTabId = useMemo( () => normalizeInboxTabSegment(inboxTab), [inboxTab] ) const pageIds = useMemo(() => listEmails.map((e) => e.id), [listEmails]) const listRowsDep = listEmails.map((e) => e.id).join(",") const effectiveRead = useCallback( (email: Email) => email.read, [] ) const effectiveStarred = useCallback( (email: Email) => email.starred, [] ) const markAllInViewAsRead = useCallback(() => { for (const e of displayListEmails) { if (e.read) continue const msg = apiMessagesById.get(e.id) if (!msg) continue if (!mailFlagIsRead(msg.flags)) { updateFlags.mutate({ id: e.id, flags: mailFlagsWithRead(msg.flags, true) }) } } }, [displayListEmails, apiMessagesById, updateFlags]) return { selectedFolder, inboxTab, listPage, openMailId, splitView, isViewMode, showSplitReadingPane, isSearchMode, searchRouter, searchAccount, setAdvancedOpen, searchParams, setSearchFilter, toggleSearchFilter, savedThreadReplyDrafts, openCompose, openComposeWithInitial, allEmails, emailById, sidebarNav, navMaps, inboxCategoryTabIconsCatalog, inboxTabBarItems, listRowLabelBgByTextLower, rescheduleTarget, setRescheduleTarget, rescheduleDismissTimeoutsRef, scheduleReschedulePopoverDismiss, cmScheduledRescheduleValue, setCmScheduledRescheduleValue, handleEditScheduledMail, starredEmails, importantEmails, readOverrides, conversationMode, inboxSort, density, isMd, labelEdits, mailActions, setReadOverrides, setLabelEdits, labelPickerQuery, setLabelPickerQuery, hiddenEmailIds, notSpamEmailIds, recentMoveTargets, mobileVisibleCount, isXs, touchNav, seenEmailIds, isRefreshing, listViewportRef, pullContentRef, pullIconRef, handleManualRefresh, markEmailSeen, folderFilterCtx, filteredEmails, displayListEmails, inboxCategoryTabLabel, mobileUnreadCount, mobileFolderLabel, paginationTotal, listPageSize, paginationRangeStart, paginationRangeEnd, handleListPageSizeChange, totalPages, pagedEmails, listEmails, listMailIndex, listRowExtras, moveTargets, folderUnreadCounts, unseenInTabById, tabUnseenSenderLineById, listToolbarMode, compactInboxTabs, activeInboxTabId, pageIds, listRowsDep, effectiveRead, effectiveStarred, markAllInViewAsRead, requestDeleteScheduled, requestArchiveScheduled, requestSnoozeScheduled, requestToggleReadScheduled, requestRescheduleScheduled, requestSendScheduledNow, requestSnoozeMailboxEmail, requestRestoreSnoozedToInbox, isLoading, error, isFetching, } } export type EmailListData = ReturnType