import type { Recipient } from "@/lib/api/types" import { cleanSenderName } from "@/lib/sender-display" export function normalizeMailAddress(addr: string): string { return addr.trim().toLowerCase().replace(/^<|>$/g, "") } function parseAngleAddr(value: string): { name: string; email: string } { const trimmed = value.trim() const match = trimmed.match(/^(.*?)\s*<([^>]+)>$/) if (match) { return { name: match[1]?.trim() ?? "", email: match[2]?.trim() ?? "", } } if (trimmed.includes("@") && !trimmed.includes(" ")) { return { name: "", email: trimmed.replace(/^<|>$/g, "") } } return { name: trimmed, email: "" } } /** Normalizes API from[] when IMAP/sync left name or address empty or combined. */ export function resolveMessageFrom( from: Recipient[] | undefined, options?: { selfEmails?: Iterable selfDisplayName?: string } ): { name: string; email: string } { const first = from?.[0] let name = first?.name?.trim() ?? "" let email = first?.address?.trim() ?? "" if (!email && name) { const parsed = parseAngleAddr(name) name = parsed.name email = parsed.email } if (!name && email) { const parsed = parseAngleAddr(email) if (parsed.email) { email = parsed.email name = parsed.name } } email = email.replace(/^<|>$/g, "") const selfSet = new Set( options?.selfEmails ? [...options.selfEmails].map(normalizeMailAddress).filter(Boolean) : [] ) if (email && selfSet.has(normalizeMailAddress(email)) && options?.selfDisplayName) { name = options.selfDisplayName } else if (email && !name) { const local = email.split("@")[0] ?? email name = local.replace(/[._-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) } return { name: cleanSenderName(name || email), email, } } export function collectSelfMailEmails( accounts: { email: string }[] | undefined, identities: { email: string }[] | undefined, platformEmail?: string | null ): string[] { const out = new Set() for (const a of accounts ?? []) { if (a.email) out.add(normalizeMailAddress(a.email)) } for (const i of identities ?? []) { if (i.email) out.add(normalizeMailAddress(i.email)) } if (platformEmail) out.add(normalizeMailAddress(platformEmail)) return [...out] } export function isMessageFromSelf( from: Recipient[] | undefined, selfEmails: Iterable ): boolean { const { email } = resolveMessageFrom(from) if (!email) return false const norm = normalizeMailAddress(email) for (const s of selfEmails) { if (norm === normalizeMailAddress(s)) return true } return false } function recipientDisplayName(r: Recipient): string { const name = r.name?.trim() if (name) return cleanSenderName(name) const addr = r.address?.trim().replace(/^<|>$/g, "") if (!addr) return "" const local = addr.split("@")[0] ?? addr return cleanSenderName( local.replace(/[._-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) ) } const RECIPIENT_SUMMARY_NAMES = 3 function selfAddressSet(selfEmails: Iterable): Set { return new Set([...selfEmails].map(normalizeMailAddress).filter(Boolean)) } /** To/Cc rows that match one of the user's mail addresses. */ export function selfRecipientsInMessage( to: Recipient[] | undefined, cc: Recipient[] | undefined, selfEmails: Iterable ): Recipient[] { const self = selfAddressSet(selfEmails) const seen = new Set() const out: Recipient[] = [] for (const r of [...(to ?? []), ...(cc ?? [])]) { if (!r.address) continue const norm = normalizeMailAddress(r.address) if (!self.has(norm) || seen.has(norm)) continue seen.add(norm) out.push(r) } return out } /** External recipients (excludes self addresses). */ export function externalRecipients( to: Recipient[] | undefined, cc: Recipient[] | undefined, selfEmails: Iterable ): Recipient[] { const self = selfAddressSet(selfEmails) return [...(to ?? []), ...(cc ?? [])].filter( (r) => r.address && !self.has(normalizeMailAddress(r.address)) ) } /** "moi" with the mailbox that received/sent (Gmail-style precision). */ export function formatSelfMoiLabel(r: Recipient): string { const email = r.address?.trim().replace(/^<|>$/g, "") return email ? `moi <${email}>` : "moi" } /** Gmail-style one-line: à moi | à Name | à A, B, C + N autres */ export function formatMessageToSummary( to: Recipient[] | undefined, cc: Recipient[] | undefined, selfEmails: Iterable ): string { const recipients = externalRecipients(to, cc, selfEmails) const selfOnly = selfRecipientsInMessage(to, cc, selfEmails) if (recipients.length === 0) { if (selfOnly.length === 1) { return `à ${formatSelfMoiLabel(selfOnly[0]!)}` } if (selfOnly.length > 1) { return `à ${selfOnly.map(formatSelfMoiLabel).join(", ")}` } return "à moi" } if (recipients.length === 1) { const label = recipientDisplayName(recipients[0]!) return label ? `à ${label}` : "à moi" } if (recipients.length <= RECIPIENT_SUMMARY_NAMES) { return `à ${recipients.map((r) => recipientDisplayName(r)).join(", ")}` } const shown = recipients .slice(0, RECIPIENT_SUMMARY_NAMES) .map((r) => recipientDisplayName(r)) .join(", ") const rest = recipients.length - RECIPIENT_SUMMARY_NAMES return `à ${shown} + ${rest} autre${rest > 1 ? "s" : ""}` } export function formatRecipientMailbox(r: Recipient): string { const { name, email } = resolveMessageFrom([r]) if (name && email) return `${name} <${email}>` return email || name } /** All To/Cc including self, for the details popover. */ export function formatAllRecipientsLine( to: Recipient[] | undefined, cc: Recipient[] | undefined, selfEmails: Iterable ): string { const self = new Set([...selfEmails].map(normalizeMailAddress).filter(Boolean)) const parts: string[] = [] for (const r of to ?? []) { if (!r.address) continue if (self.has(normalizeMailAddress(r.address))) { parts.push(formatSelfMoiLabel(r)) } else { parts.push(formatRecipientMailbox(r)) } } for (const r of cc ?? []) { if (!r.address) continue const box = formatRecipientMailbox(r) parts.push(parts.length ? `Cc: ${box}` : box) } if (parts.length > 0) return parts.join(", ") const selfOnly = selfRecipientsInMessage(to, cc, selfEmails) if (selfOnly[0]) return formatSelfMoiLabel(selfOnly[0]) return "moi" }