"use client" import { useCallback, useMemo } from "react" import type { CatalogLabelPresence } from "@/components/gmail/email-label-picker-block" import { resolveLabelPickerVisual } from "@/lib/label-picker-visual" import { effectiveLabels, mergeEmailLabelEdits, mergeEmailNotSpam, } from "@/lib/label-edits" import type { FolderTreeNode } from "@/lib/sidebar-nav-data" import { LABEL_PICKER_EXCLUDE, } from "@/lib/mail-list/label-actions" import { collectTreeLabels, } from "@/components/gmail/email-list/email-list-helpers" import type { EmailListData } from "@/components/gmail/email-list/hooks/use-email-list-data" export function useEmailListLabels(data: EmailListData) { const { allEmails, sidebarNav, labelEdits, notSpamEmailIds, setLabelEdits, mailActions, } = data const collectAllFolderLabels = useCallback((): Set => { const s = new Set() const walk = (nodes: FolderTreeNode[]) => { for (const n of nodes) { s.add(n.label.toLowerCase()) if (n.children?.length) walk(n.children) } } walk(sidebarNav.folderTree) return s }, [sidebarNav.folderTree]) const moveEmailsToTarget = useCallback( (emailIds: string[], targetId: string) => { if (emailIds.length === 0) return const folderLabel = sidebarNav.folderIdToLabel[targetId] const isSystemTarget = ["inbox", "sent", "drafts", "spam", "trash"].includes(targetId) const allFolderLabels = collectAllFolderLabels() setLabelEdits((prev) => { const nextAdd = { ...prev.additions } const nextRem = { ...prev.removals } for (const id of emailIds) { const email = allEmails.find((e) => e.id === id) const currentLabels = effectiveLabels(email, nextAdd, nextRem) if (isSystemTarget) { if (targetId === "inbox") { for (const lab of currentLabels) { if (allFolderLabels.has(lab.toLowerCase())) { const cur = nextRem[id] ?? [] if (!cur.some((l) => l.toLowerCase() === lab.toLowerCase())) { nextRem[id] = [...cur, lab] } if (nextAdd[id]?.length) { nextAdd[id] = nextAdd[id].filter((l) => l.toLowerCase() !== lab.toLowerCase()) if (nextAdd[id].length === 0) delete nextAdd[id] } } } } } else if (folderLabel) { for (const lab of currentLabels) { if (allFolderLabels.has(lab.toLowerCase()) && lab.toLowerCase() !== folderLabel.toLowerCase()) { const cur = nextRem[id] ?? [] if (!cur.some((l) => l.toLowerCase() === lab.toLowerCase())) { nextRem[id] = [...cur, lab] } if (nextAdd[id]?.length) { nextAdd[id] = nextAdd[id].filter((l) => l.toLowerCase() !== lab.toLowerCase()) if (nextAdd[id].length === 0) delete nextAdd[id] } } } if (!currentLabels.some((l) => l.toLowerCase() === folderLabel.toLowerCase())) { nextAdd[id] = [...(nextAdd[id] ?? []), folderLabel] } if (nextRem[id]?.length) { nextRem[id] = nextRem[id].filter((l) => l.toLowerCase() !== folderLabel.toLowerCase()) if (nextRem[id].length === 0) delete nextRem[id] } const inboxIdx = currentLabels.findIndex((l) => l.toLowerCase() === "inbox") if (inboxIdx >= 0 || !email?.labels?.length || email.labels.includes("inbox")) { const cur = nextRem[id] ?? [] if (!cur.some((l) => l.toLowerCase() === "inbox")) { nextRem[id] = [...cur, "inbox"] } } } } return { additions: nextAdd, removals: nextRem } }) if (!isSystemTarget || targetId === "inbox") { mailActions.pushRecentMoveTarget(targetId) } if (isSystemTarget && targetId !== "inbox") { mailActions.hideEmails(emailIds) mailActions.pushRecentMoveTarget(targetId) } }, [allEmails, sidebarNav.folderIdToLabel, collectAllFolderLabels, setLabelEdits, mailActions] ) const catalogLabels = useMemo(() => { const s = new Set() for (const l of collectTreeLabels(sidebarNav.folderTree)) s.add(l) for (const row of sidebarNav.labelRows) s.add(row.label) for (const e of allEmails) { const eff = mergeEmailNotSpam( mergeEmailLabelEdits(e, labelEdits), notSpamEmailIds ) for (const lab of eff.labels ?? []) { if (!LABEL_PICKER_EXCLUDE.has(lab)) s.add(lab) } } return [...s].sort((a, b) => a.localeCompare(b, "fr")) }, [sidebarNav.folderTree, sidebarNav.labelRows, allEmails, labelEdits, notSpamEmailIds]) const resolveLabelVisual = useCallback( (label: string) => resolveLabelPickerVisual(label, { folderTree: sidebarNav.folderTree, labelRows: sidebarNav.labelRows, emailLabelToSidebarFolderId: sidebarNav.emailLabelToSidebarFolderId, }), [ sidebarNav.folderTree, sidebarNav.labelRows, sidebarNav.emailLabelToSidebarFolderId, ] ) const resolveLabelCasing = useCallback( (raw: string) => { const t = raw.trim() if (!t) return "" const hit = catalogLabels.find((c) => c.toLowerCase() === t.toLowerCase()) return hit ?? t }, [catalogLabels] ) const addLabelToEmails = useCallback( (ids: string[], label: string) => { const resolved = resolveLabelCasing(label) if (!resolved || ids.length === 0) return sidebarNav.ensureLabelRowForLabelText(resolved) setLabelEdits((prev) => { const nextAdd = { ...prev.additions } const nextRem = { ...prev.removals } for (const id of ids) { if (nextRem[id]?.length) { nextRem[id] = nextRem[id].filter( (x) => x.toLowerCase() !== resolved.toLowerCase() ) if (nextRem[id].length === 0) delete nextRem[id] } const base = allEmails.find((e) => e.id === id) const merged = effectiveLabels(base, nextAdd, nextRem) if (merged.some((x) => x.toLowerCase() === resolved.toLowerCase())) { continue } nextAdd[id] = [...(nextAdd[id] ?? []), resolved] } return { additions: nextAdd, removals: nextRem } }) }, [resolveLabelCasing, allEmails, sidebarNav, setLabelEdits] ) const getCatalogLabelPresence = useCallback( (ids: string[], catalogLabel: string): CatalogLabelPresence => { const resolved = resolveLabelCasing(catalogLabel) if (!resolved || ids.length === 0) return "none" const lc = resolved.toLowerCase() let n = 0 for (const id of ids) { const e = allEmails.find((x) => x.id === id) const eff = effectiveLabels(e, labelEdits.additions, labelEdits.removals) if (eff.some((l) => l.toLowerCase() === lc)) n++ } if (n === 0) return "none" if (n === ids.length) return "all" return "some" }, [allEmails, labelEdits, resolveLabelCasing] ) const toggleLabelOnEmails = useCallback( (ids: string[], label: string) => { const resolved = resolveLabelCasing(label) if (!resolved || ids.length === 0) return setLabelEdits((prev) => { const presence = (id: string) => { const e = allEmails.find((x) => x.id === id) if (!e) return false return effectiveLabels(e, prev.additions, prev.removals).some( (l) => l.toLowerCase() === resolved.toLowerCase() ) } const allHave = ids.every((id) => presence(id)) const nextAdd = { ...prev.additions } const nextRem = { ...prev.removals } if (allHave) { for (const id of ids) { if (nextAdd[id]?.length) { const filtered = nextAdd[id].filter( (l) => l.toLowerCase() !== resolved.toLowerCase() ) if (filtered.length) nextAdd[id] = filtered else delete nextAdd[id] } const e = allEmails.find((x) => x.id === id) if (!e) continue const still = effectiveLabels(e, nextAdd, nextRem).some( (l) => l.toLowerCase() === resolved.toLowerCase() ) if (still) { const cur = nextRem[id] ?? [] if (!cur.some((l) => l.toLowerCase() === resolved.toLowerCase())) { nextRem[id] = [...cur, resolved] } } else if (nextRem[id]?.length) { const fr = nextRem[id].filter( (l) => l.toLowerCase() !== resolved.toLowerCase() ) if (fr.length) nextRem[id] = fr else delete nextRem[id] } } } else { const anyMissing = ids.some((id) => !presence(id)) if (anyMissing) { queueMicrotask(() => sidebarNav.ensureLabelRowForLabelText(resolved)) } for (const id of ids) { const e = allEmails.find((x) => x.id === id) if (!e) continue const had = effectiveLabels(e, prev.additions, prev.removals).some( (l) => l.toLowerCase() === resolved.toLowerCase() ) if (nextRem[id]?.length) { const fr = nextRem[id].filter( (l) => l.toLowerCase() !== resolved.toLowerCase() ) if (fr.length) nextRem[id] = fr else delete nextRem[id] } if (!had) { if (!nextAdd[id]) nextAdd[id] = [] if (!nextAdd[id].some((l) => l.toLowerCase() === resolved.toLowerCase())) { nextAdd[id] = [...nextAdd[id], resolved] } } } } return { additions: nextAdd, removals: nextRem } }) }, [allEmails, resolveLabelCasing, sidebarNav, setLabelEdits] ) return { collectAllFolderLabels, moveEmailsToTarget, catalogLabels, resolveLabelVisual, resolveLabelCasing, addLabelToEmails, toggleLabelOnEmails, getCatalogLabelPresence, } } export type EmailListLabels = ReturnType