import type { Email } from "@/lib/email-data" import { folderTree as defaultFolderTree, sidebarNavFolderIdToLabel as defaultSidebarNavFolderIdToLabel, defaultNavLabelRowsSnapshot, type LabelRowItem, } from "@/lib/sidebar-nav-data" import { collectSubtreeFolderIds } from "@/lib/sidebar-nav-maps" import type { FolderTreeNode } from "@/lib/sidebar-nav-maps" export type MailFolderFilterCtx = { starredEmailIds: string[] importantEmailIds: string[] } /** Carte navigation dynamique (sidebar). Si absent, valeurs par défaut du module. */ export type MailNavFolderMaps = { folderIdToLabel: Record folderTree: FolderTreeNode[] labelRows: LabelRowItem[] } function hasFutureScheduledSend(email: Email): boolean { if (!email.scheduledSendAt) return false const t = new Date(email.scheduledSendAt).getTime() if (!Number.isFinite(t)) return false return t > Date.now() } function effectiveStarred(email: Email, ctx: MailFolderFilterCtx): boolean { return ctx.starredEmailIds.includes(email.id) || email.starred } function effectiveImportant(email: Email, ctx: MailFolderFilterCtx): boolean { return ctx.importantEmailIds.includes(email.id) || email.important } function isInInbox(email: Email): boolean { if (email.deleted) return false if (email.spam) return false if (hasFutureScheduledSend(email)) return false const ls = email.labels if (!ls?.length) return true return ls.includes("inbox") } function labelRowForNavId( folderId: string, rows: readonly LabelRowItem[] ): LabelRowItem | undefined { return rows.find((r) => r.id === folderId) } function resolveNavMaps(maps?: MailNavFolderMaps | null): MailNavFolderMaps { if (maps) return maps return { folderIdToLabel: defaultSidebarNavFolderIdToLabel as Record, folderTree: defaultFolderTree, labelRows: defaultNavLabelRowsSnapshot, } } function emailHasAnyLabel(email: Email, labels: string[]): boolean { const ls = email.labels if (!ls?.length) return false const lower = new Set(ls.map((x) => x.toLowerCase())) return labels.some((lab) => lower.has(lab.toLowerCase())) } function matchesFolderLabelRow( email: Email, folderId: string, maps: MailNavFolderMaps ): boolean { const row = labelRowForNavId(folderId, maps.labelRows) if (row && row.enabled === false) return false const label = maps.folderIdToLabel[folderId] if (!label) return false return emailHasAnyLabel(email, [label]) } /** Dossier hiérarchique : ce nœud ou n'importe quel sous-dossier (décompte unique par mail). */ function matchesLabelNav( email: Email, folderId: string, maps: MailNavFolderMaps, subtreeIdsCache?: Map ): boolean { let subtreeIds: string[] | null if (subtreeIdsCache) { if (!subtreeIdsCache.has(folderId)) { subtreeIdsCache.set( folderId, collectSubtreeFolderIds(maps.folderTree, folderId) ) } subtreeIds = subtreeIdsCache.get(folderId) ?? null } else { subtreeIds = collectSubtreeFolderIds(maps.folderTree, folderId) } if (subtreeIds) { return subtreeIds.some((id) => matchesFolderLabelRow(email, id, maps)) } return matchesFolderLabelRow(email, folderId, maps) } /** Onglet Principale : boîte sans libellés système « exclude » actifs. */ export function emailMatchesInboxPrimaryTab( email: Email, ctx: MailFolderFilterCtx, maps: MailNavFolderMaps, subtreeIdsCache?: Map ): boolean { if (!emailMatchesFolder(email, "inbox", ctx, maps, subtreeIdsCache)) return false if (email.deleted) return false if (hasFutureScheduledSend(email)) return false for (const row of maps.labelRows) { if (row.enabled === false) continue if (!row.excludeFromPrincipal) continue if (emailHasAnyLabel(email, [row.label])) return false } return true } export function emailMatchesFolder( email: Email, folderId: string, ctx: MailFolderFilterCtx, maps?: MailNavFolderMaps | null, /** Réutiliser entre appels (ex. `computeFolderUnreadCounts`) pour éviter de rescanner l’arbre à chaque mail. */ subtreeIdsCache?: Map ): boolean { const nav = resolveNavMaps(maps) if (email.deleted && folderId !== "trash") { return false } switch (folderId) { case "inbox": return isInInbox(email) case "starred": return effectiveStarred(email, ctx) case "snoozed": return (email.labels?.includes("snoozed") ?? false) && !email.deleted case "important": return effectiveImportant(email, ctx) case "sent": return (email.labels?.includes("sent") ?? false) && !email.deleted case "drafts": return (email.labels?.includes("drafts") ?? false) && !email.deleted case "scheduled": return !email.deleted && hasFutureScheduledSend(email) case "spam": return ( !email.deleted && (email.spam === true || (email.labels?.includes("spam") ?? false)) ) case "trash": return email.deleted === true default: break } const row = labelRowForNavId(folderId, nav.labelRows) if (row && row.enabled === false) return false if (nav.folderIdToLabel[folderId]) { return matchesLabelNav(email, folderId, nav, subtreeIdsCache) } if (email.labels?.includes(folderId)) return true return false }