import type { Email, ConversationMessage } from "@/lib/email-data" import type { Contact, ComposeOpenPreset, ThreadComposeKind, } from "@/lib/compose-context" import { readCoarsePointerMatches } from "@/hooks/use-touch-nav" import { readXsMatches } from "@/hooks/use-xs" import { DEFAULT_IDENTITIES, SIGNATURES } from "@/lib/compose-context" import { formatMailDetailDate } from "@/lib/mail-date" import { cleanSenderName } from "@/lib/sender-display" function appendDefaultSignature(html: string): string { const identity = DEFAULT_IDENTITIES[0] const sigId = identity.defaultSignatureId const sig = sigId ? SIGNATURES.find((s) => s.id === sigId) : null if (!sig) return html return `${html}

--

${sig.html}
` } /** Signature juste après la zone de saisie, avant le bloc « message transféré ». */ function insertSignatureBeforeForwardedBlock(html: string): string { const identity = DEFAULT_IDENTITIES[0] const sigId = identity.defaultSignatureId const sig = sigId ? SIGNATURES.find((s) => s.id === sigId) : null if (!sig) return html const sigBlock = `

--

${sig.html}
` const lead = "

" if (html.startsWith(lead)) { return `${lead}${sigBlock}${html.slice(lead.length)}` } return `${lead}${sigBlock}${html}` } function escapeHtml(text: string): string { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) } function myEmailSet(): Set { return new Set(DEFAULT_IDENTITIES.map((i) => i.email.toLowerCase())) } function contactKey(email: string): string { return email.trim().toLowerCase() } /** Tous les expéditeurs des messages du fil (hors « moi »), ordre chronologique conservé. */ export function collectThreadParticipants(email: Email): Contact[] { const my = myEmailSet() const seen = new Set() const out: Contact[] = [] const push = (name: string, addrRaw: string) => { const addr = addrRaw.trim() if (!addr) return const k = contactKey(addr) if (my.has(k) || seen.has(k)) return seen.add(k) out.push({ name: cleanSenderName(name) || addr, email: addr, }) } for (const m of email.conversation ?? []) { push(m.sender, m.senderEmail) } const mainAddr = email.senderEmail ?? `${cleanSenderName(email.sender).toLowerCase().replace(/\s+/g, ".")}@example.com` push(email.sender, mainAddr) return out } function lastMessage(email: Email): ConversationMessage | null { const conv = email.conversation ?? [] if (conv.length === 0) return null return conv[conv.length - 1]! } function replySubject(subject: string): string { const s = subject.trim() if (/^re\s*:/i.test(s)) return s return `Re: ${s}` } function forwardSubject(subject: string): string { const s = subject.trim() if (/^(fwd|fw|tr)\s*:/i.test(s)) return s return `Fwd: ${s}` } function buildReferences(email: Email): string[] { const ids: string[] = [] for (const m of email.conversation ?? []) { ids.push(``) } ids.push(``) return ids } function inReplyToFor(email: Email): string { const last = lastMessage(email) if (last) return `` return `` } function formatQuoteDate( dateIso: string, senderEmail: string, senderName: string ): string { const who = senderName && senderName !== senderEmail ? `${escapeHtml(senderName)} <${escapeHtml(senderEmail)}>` : escapeHtml(senderEmail) return `Le ${escapeHtml(formatMailDetailDate(dateIso))}, ${who} a écrit :` } function quotedBlock(html: string): string { return `
${html}
` } function replyQuotedHtml(email: Email): string { const last = lastMessage(email) if (last) { const line = formatQuoteDate(last.date, last.senderEmail, cleanSenderName(last.sender)) return `


${line}
${quotedBlock(last.body)}` } const addr = email.senderEmail ?? `${cleanSenderName(email.sender).toLowerCase().replace(/\s+/g, ".")}@example.com` const line = formatQuoteDate(email.date, addr, cleanSenderName(email.sender)) const body = email.body ?? `

${escapeHtml(email.preview)}

` return `


${line}
${quotedBlock(body)}` } function forwardParticipantsLine(email: Email): string { const parts = collectThreadParticipants(email) if (parts.length === 0) return "To: (inconnu)" return `To: ${parts.map((p) => `${p.name} <${p.email}>`).join(", ")}` } /** Corps HTML du fil (messages précédents + message principal), sans en-tête « Forwarded ». */ function forwardConversationHtml(email: Email): string { const blocks: string[] = [] for (const m of email.conversation ?? []) { blocks.push( `
` + `
De : ${escapeHtml(cleanSenderName(m.sender))} <${escapeHtml(m.senderEmail)}>
` + `Date : ${escapeHtml(formatMailDetailDate(m.date))}
${m.body}
` ) } const mainAddr = email.senderEmail ?? `${cleanSenderName(email.sender).toLowerCase().replace(/\s+/g, ".")}@example.com` blocks.push( `
` + `
De : ${escapeHtml(cleanSenderName(email.sender))} <${escapeHtml(mainAddr)}>
` + `Date : ${escapeHtml(formatMailDetailDate(email.date))}
` + `${email.body ?? `

${escapeHtml(email.preview)}

`}
` ) return blocks.join("") } function forwardBodyHtml(email: Email): string { const mainAddr = email.senderEmail ?? `${cleanSenderName(email.sender).toLowerCase().replace(/\s+/g, ".")}@example.com` const header = `

---------- Forwarded message ---------

` + `

` + `De : ${escapeHtml(cleanSenderName(email.sender))} <${escapeHtml(mainAddr)}>
` + `Date : ${escapeHtml(formatMailDetailDate(email.date))}
` + `Objet : ${escapeHtml(email.subject)}
` + `${escapeHtml(forwardParticipantsLine(email))}

` return `

${header}${forwardConversationHtml(email)}` } /** Tablette / tactile : composition plein écran (dock maximisé) au lieu d’inline sous le fil. */ export function withTouchFullscreenComposePreset( preset: ComposeOpenPreset ): ComposeOpenPreset { if (typeof window === "undefined" || !readCoarsePointerMatches()) { return preset } return { ...preset, placement: "dock", maximized: true, } } /** * Preset pour une fenêtre de composition inline (réponse / transfert) rattachée au fil `email`. */ export function buildThreadComposePreset( email: Email, kind: ThreadComposeKind ): ComposeOpenPreset { const participants = collectThreadParticipants(email) const last = lastMessage(email) const lastContact: Contact | null = last ? { name: cleanSenderName(last.sender), email: last.senderEmail } : participants.length > 0 ? participants[participants.length - 1]! : null const threading = { inReplyTo: inReplyToFor(email), references: buildReferences(email), } if (kind === "forward") { const baseBody = insertSignatureBeforeForwardedBlock(forwardBodyHtml(email)) return { to: [], cc: [], bcc: [], subject: forwardSubject(email.subject), bodyHtml: baseBody, placement: "inline", threadEmailId: email.id, threadKind: "forward", focusToOnMount: true, focusBodyOnMount: false, threading, } } if (kind === "reply") { const to = lastContact ? [lastContact] : [] return { to, cc: [], bcc: [], subject: replySubject(email.subject), bodyHtml: appendDefaultSignature(replyQuotedHtml(email)), placement: "inline", threadEmailId: email.id, threadKind: "reply", focusToOnMount: false, focusBodyOnMount: true, threading, } } /* replyAll */ const to: Contact[] = [] const cc: Contact[] = [] if (lastContact) { to.push(lastContact) for (const p of participants) { if (p.email === lastContact.email) continue cc.push(p) } } else if (participants.length > 0) { to.push(participants[0]!) for (let i = 1; i < participants.length; i++) { cc.push(participants[i]!) } } return { to, cc, bcc: [], subject: replySubject(email.subject), bodyHtml: appendDefaultSignature(replyQuotedHtml(email)), placement: "inline", threadEmailId: email.id, threadKind: "replyAll", focusToOnMount: false, focusBodyOnMount: true, threading, showCc: cc.length > 0, } }