"use client" import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, } from "react" import { Star, Reply, ReplyAll, Forward } from "lucide-react" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { avatarColor, cleanSenderName, senderInitial, } from "@/lib/sender-display" import type { Email, EmailAttachment } from "@/lib/email-data" import type { FolderTreeNode, LabelRowItem } from "@/lib/sidebar-nav-data" import { useComposeActions, useComposeDrafts, useComposeWindows, DEFAULT_IDENTITIES, type ThreadComposeKind, type ComposeOpenPreset, savedThreadDraftToComposePreset, } from "@/lib/compose-context" import { buildThreadComposePreset, withTouchFullscreenComposePreset, } from "@/lib/thread-compose-preset" import { openConversationPrint } from "@/lib/print-conversation" import { resolveParsedCalendarInvitation } from "@/lib/resolve-email-calendar-invitation" import { ComposeWindow } from "@/components/gmail/compose-modal" import { CalendarInvitationPreview } from "@/components/gmail/calendar-invitation-preview" import { EmailViewSubjectHeader } from "./email-view/email-view-header" import { MAIL_PREVIEW_SCROLL_CLASS, MAIL_REPLY_BAR_CLASS, MAIL_REPLY_BUTTON_CLASS, } from "@/lib/mail-chrome-classes" import { CollapsedMessage, ExpandedMessage, SpamWhyBanner, } from "@/components/gmail/email-view/email-view-messages" interface EmailViewProps { email: Email onToggleStar: (id: string) => void isStarred: boolean onNavigateToLabel?: (label: string) => void /** Message spam : bannière + pastille sujet ; bouton « non-spam » */ onNotSpam?: () => void /** Si défini, les pastilles libellé dont la fonction retourne false sont masquées (préférences barre latérale). */ showLabelChip?: (label: string) => boolean labelBgByText?: Map emailLabelToSidebarFolderId?: Record getNavItemPrefs?: (id: string) => { messages: string } folderTree?: FolderTreeNode[] labelRows?: readonly LabelRowItem[] /** Id dossier / libellé courant — masque la pastille du dossier actif (comme en liste). */ currentFolderId?: string /** Fil complet (mode message isolé hors conversation). */ threadRoot?: Email | null /** Affiche uniquement le message courant avec option d’ouvrir le fil. */ isSingleMessageView?: boolean } /* ── Main EmailView component ── */ export function EmailView({ email, onToggleStar, isStarred, onNavigateToLabel, onNotSpam, showLabelChip, labelBgByText, emailLabelToSidebarFolderId = {}, getNavItemPrefs = () => ({ messages: "show" }), folderTree, labelRows, currentFolderId, threadRoot = null, isSingleMessageView = false, }: EmailViewProps) { const [showFullThread, setShowFullThread] = useState(false) const threadForReplies = threadRoot ?? email const priorCount = Math.max( 0, (threadForReplies.threadMessageIds?.length ?? 1) - 1 ) const showRepliesCta = isSingleMessageView && !showFullThread && priorCount > 0 const conversation = isSingleMessageView && !showFullThread ? [] : (showFullThread ? threadForReplies.conversation : email.conversation) ?? [] const hasConversation = conversation.length > 0 const isSpamMessage = email.spam === true // Track which conversation messages are expanded (by index). // By default all previous messages are collapsed, only the last (main) is expanded. const [expandedIds, setExpandedIds] = useState>(new Set()) const toggleExpanded = (msgId: string) => { setExpandedIds((prev) => { const next = new Set(prev) if (next.has(msgId)) { next.delete(msgId) } else { next.add(msgId) } return next }) } const mainSenderName = cleanSenderName(email.sender) const mainSenderAddr = email.senderEmail || `${mainSenderName.toLowerCase().replace(/\s+/g, ".")}@example.com` const { composeWindows } = useComposeWindows() const { savedThreadReplyDrafts } = useComposeDrafts() const { openComposeWithInitial } = useComposeActions() const inlineCompose = useMemo( () => composeWindows.find( (c) => c.placement === "inline" && c.threadEmailId === email.id ), [composeWindows, email.id] ) const mainMessageAttachments = useMemo((): EmailAttachment[] => { if (email.attachments && email.attachments.length > 0) return email.attachments if (email.hasAttachment) return [{ name: "Pièce jointe", kind: "other" }] return [] }, [email.attachments, email.hasAttachment]) const savedThreadDraft = savedThreadReplyDrafts[email.id] const hasInlineForThread = Boolean(inlineCompose) const showReplyForwardBar = !inlineCompose const previewScrollRef = useRef(null) const threadComposeAnchorRef = useRef(null) const scrollThreadComposeIntoView = useCallback(() => { requestAnimationFrame(() => { requestAnimationFrame(() => { threadComposeAnchorRef.current?.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest", }) }) }) }, []) const openThreadCompose = useCallback( (preset: ComposeOpenPreset) => { const resolved = withTouchFullscreenComposePreset(preset) openComposeWithInitial(resolved) if (resolved.placement === "inline") { scrollThreadComposeIntoView() } }, [openComposeWithInitial, scrollThreadComposeIntoView] ) useEffect(() => { if (!savedThreadDraft || hasInlineForThread) return openThreadCompose(savedThreadDraftToComposePreset(savedThreadDraft)) }, [ email.id, savedThreadDraft, hasInlineForThread, openThreadCompose, ]) const startThreadCompose = useCallback( (kind: ThreadComposeKind) => { openThreadCompose(buildThreadComposePreset(email, kind)) }, [email, openThreadCompose] ) const selfIdentity = DEFAULT_IDENTITIES[0] const selfName = cleanSenderName(selfIdentity.name) const calendarInvitation = useMemo( () => resolveParsedCalendarInvitation(email), [email] ) return (
{/* Spacer for floating nav buttons on xs */}
{calendarInvitation ? ( ) : null} {isSpamMessage && } {showRepliesCta ? (
) : null} {/* Conversation messages */} {/* Previous messages in conversation */} {hasConversation && conversation.map((msg) => { const isExpanded = expandedIds.has(msg.id) if (isExpanded) { return (
toggleExpanded(msg.id)} onPrintConversation={() => openConversationPrint(email)} />
) } return (
toggleExpanded(msg.id)} />
) })} {/* Last / main message — always expanded */} ${email.preview}

`} isSpam={email.spam === true} isLast={true} starred={isStarred} attachments={mainMessageAttachments} onToggleStar={() => onToggleStar(email.id)} onPrintConversation={() => openConversationPrint(email)} /> {showReplyForwardBar ? ( ) : null} {inlineCompose ? (
{senderInitial(selfName)}
) : null}
) }