"use client"

import {
  startTransition,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  type ComponentType,
  type DragEvent,
  type MouseEvent,
  type ReactNode,
} from "react"
import { Icon, addCollection } from "@iconify/react"
import { icons as mdiIcons } from "@iconify-json/mdi"
import { attachmentsForEmailList } from "@/lib/attachment-display"
import { resolveParsedCalendarInvitation } from "@/lib/resolve-email-calendar-invitation"
import { VIDEO_CONFERENCE_LOGOS } from "@/lib/calendar-invitation"
import { ensureVcLogosCollection } from "@/lib/register-vc-logos"
import { useEmailDrag } from "@/lib/drag-context"
import {
  Star,
  ChevronLeft,
  ChevronRight,
  ChevronUp,
  MoreVertical,
  RefreshCw,
  ChevronDown,
  Tag,
  Reply,
  ReplyAll,
  Forward,
  Paperclip,
  Archive,
  Trash2,
  Mail,
  MailOpen,
  Menu,
  Clock,
  ListTodo,
  FolderInput,
  VolumeX,
  Search,
  SquareArrowOutUpRight,
  File,
  Image as ImageIcon,
  ShieldAlert,
  ArrowLeft,
  Plus,
  Send,
  Pencil,
  CalendarClock,
  CalendarX2,
  X,
  CheckSquare,
  Inbox as InboxIcon,
  User as UserIcon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import {
  Empty,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty"
import { CompactInboxCategoryTabs } from "@/components/gmail/compact-inbox-category-tabs"
import { MailInboxCategoryTabIcons } from "@/components/gmail/mail-inbox-category-tab-icons"
import { cn } from "@/lib/utils"
import { labelPillTextClassForTailwindBgUtility } from "@/lib/label-pill-contrast"
import {
  buildLabelTextToNavColorClass,
  MailLabelPillStrip,
  mailLabelShouldShowInListStrip,
} from "@/components/gmail/mail-label-pills"
import {
  emails,
  type Email,
  type EmailAttachment,
} from "@/lib/email-data"
import {
  getThreadMessageCount,
  isListRowRead,
  isThreadHeadMessage,
  readStateTargets,
} from "@/lib/mail-thread"
import { useScheduledMail } from "@/lib/scheduled-mail-context"
import { useMailStore } from "@/lib/stores/mail-store"
import { useScheduledStore } from "@/lib/stores/scheduled-store"
import { usePersistHydrated } from "@/hooks/use-persist-hydrated"
import { useIsMd } from "@/hooks/use-md-breakpoint"
import { threadStoreId } from "@/lib/mail-settings/list-row-id"
import { resolveOpenEmailView } from "@/lib/mail-settings/resolve-open-email"
import { sortEmailsForInbox } from "@/lib/mail-settings/sort-emails"
import { useMailSettingsStore } from "@/lib/stores/mail-settings-store"
import { useActiveAccount } from "@/lib/stores/account-store"
import { useMailSearchStore } from "@/lib/stores/mail-search-store"
import {
  MAIL_INBOX_CATEGORY_TAB_CONTENT_DARK_CLASS,
  MAIL_MENU_SURFACE_CLASS,
  MAIL_MENU_SURFACE_WIDE_CLASS,
  MAIL_TOOLBAR_ICON_BTN,
} from "@/lib/mail-chrome-classes"
import {
  emailMatchesFolder,
  emailMatchesInboxPrimaryTab,
  type MailNavFolderMaps,
} from "@/lib/mail-folder-filter"
import { cleanSenderName, resolveSenderEmail } from "@/lib/sender-display"
import {
  getMailNavFolderLabel,
  inboxTabDisplayLabel,
  type FolderTreeNode,
  type LabelRowItem,
} from "@/lib/sidebar-nav-data"
import {
  mailNavVisitKey,
  parseMailNavVisitKey,
} from "@/lib/mail-folder-display"
import {
  buildInboxCategoryTabIcons,
  inboxTabActiveAccentColor,
  resolveEmailInboxCategoryTabs,
} from "@/lib/inbox-category-tabs"
import {
  DEFAULT_INBOX_TAB,
  INBOX_ALL_TAB,
  SEARCH_FOLDER_ID,
  inboxTabShowsInactiveMeta,
  normalizeInboxTabSegment,
} from "@/lib/mail-url"
import { useSearchParams, useRouter } from "next/navigation"
import {
  parseSearchParams,
  buildSearchUrl,
  DATE_RANGE_OPTIONS,
  type SearchParams,
} from "@/lib/mail-search/search-params"
import { filterEmailsBySearchParams } from "@/lib/mail-search/search-engine"
import { MailFolderStackIndicator } from "@/components/gmail/mail-folder-stack-indicator"
import { useSidebarNav, registerNavEmailSync } from "@/lib/sidebar-nav-context"
import { ContactHoverCard } from "./contact-hover-card"
import { EmailLabelPickerBlock } from "@/components/gmail/email-label-picker-block"
import type { CatalogLabelPresence } from "@/components/gmail/email-label-picker-block"
import { resolveLabelPickerVisual } from "@/lib/label-picker-visual"
import { MobileXsBulkSheets } from "@/components/gmail/mobile-xs-bulk-sheets"
import { MailListSwipeRow } from "@/components/gmail/mail-list-swipe-row"
import {
  useMoveTargets,
  type MoveTarget,
} from "@/components/gmail/move-to-menu-items"
import { EmailView } from "./email-view"
import { MailSearchBar } from "@/components/gmail/mail-search-bar"
import { MailDateText } from "@/components/gmail/mail-date-text"
import { formatMailDetailDate } from "@/lib/mail-date"
import { buildListMailIndex } from "./email-list-row"
import {
  useComposeActions,
  useComposeDrafts,
  type Contact,
} from "@/lib/compose-context"
import { computeFolderUnreadCounts } from "@/lib/mail-nav-metrics"
import {
  effectiveLabels,
  mergeEmailLabelEdits,
  mergeEmailNotSpam,
} from "@/lib/label-edits"
import type { LabelEditState } from "@/lib/stores/mail-store"
import type { MailRouteState } from "@/lib/mail-url"
import { readXsMatches, useIsXs } from "@/hooks/use-xs"
import { useTouchNav } from "@/hooks/use-touch-nav"
import type { MailXsViewChrome } from "@/lib/mail-xs-view-chrome"
import {
  buildThreadComposePreset,
  withTouchFullscreenComposePreset,
} from "@/lib/thread-compose-preset"

import {
  LABEL_PICKER_EXCLUDE,
  applyNavRenameToEdits,
  applyNavRemoveLabelToEdits,
} from "@/lib/mail-list/label-actions"
import { EmailListAttachmentRow } from "@/components/gmail/email-list/attachments/email-list-attachment-row"
import {
  MoveToDropdownItems,
  MoveToContextMenuItems,
} from "@/components/gmail/email-list/move-to-menu-items"
import { MAIL_LIST_ROW_DIVIDER_CLASS } from "@/lib/mail-chrome-classes"
import {
  LIST_PAGE_SIZE,
  PULL_HOLD_HEIGHT,
  PULL_SNAP_BACK_TRANSITION,
  REFRESH_SPIN_CLASS,
  PULL_ICON_FADE_MS,
  PULL_REFRESH_THRESHOLD,
  computePullOffset,
  computeSpinnerRevealProgress,
  type EmailListProps,
  collectTreeLabels,
  contextMenuTargetIdsForRow,
  escapeHtml,
  importantSignalIcon,
  buildInboxTabBarItems,
  inboxTabBadgeCountClass,
  inboxTabBadgeDotClass,
  CATEGORY_TAB_ICON_CLASS,
  listRowCheckboxClass,
  listRowQuickHoverTrayToneClass,
  formatScheduledDateTimeDisplay,
  scheduledIsoToDatetimeLocalValue,
  parseDatetimeLocalToIso,
} from "@/components/gmail/email-list/email-list-helpers"

export function EmailList({
  selectedFolder,
  inboxTab,
  listPage,
  openMailId,
  splitView = false,
  onToggleSidebar,
  onMailRouteNavigate,
  onSelectFolder,
  onFolderUnreadCountsChange,
  onXsViewChromeChange,
}: EmailListProps) {
  const isViewMode = openMailId !== null && !splitView
  const showSplitReadingPane = splitView && openMailId !== null
  const isSearchMode = selectedFolder === SEARCH_FOLDER_ID
  const searchRouter = useRouter()
  const searchAccount = useActiveAccount()
  const setAdvancedOpen = useMailSearchStore((s) => s.setAdvancedOpen)
  const urlSearchParams = useSearchParams()
  const searchParams = useMemo(
    () => (isSearchMode ? parseSearchParams(urlSearchParams) : null),
    [isSearchMode, urlSearchParams]
  )

  const setSearchFilter = useCallback(
    (patch: Partial<SearchParams>) => {
      if (!searchParams) return
      searchRouter.push(buildSearchUrl({ ...searchParams, ...patch }))
    },
    [searchParams, searchRouter]
  )

  const toggleSearchFilter = useCallback(
    (key: keyof SearchParams, value: string) => {
      if (!searchParams) return
      const next = { ...searchParams }
      if (key === "has") {
        const arr = [...next.has]
        if (arr.includes(value)) next.has = arr.filter((v) => v !== value)
        else next.has = [...arr, value]
      } else if (key === "excludeChats") {
        next.excludeChats = !next.excludeChats
      } else {
        const cur = (next as Record<string, unknown>)[key]
        ;(next as Record<string, unknown>)[key] = cur === value ? "" : value
      }
      searchRouter.push(buildSearchUrl(next))
    },
    [searchParams, searchRouter]
  )

  const { savedThreadReplyDrafts } = useComposeDrafts()
  const {
    openCompose,
    openComposeWithInitial,
    closeAllInlineComposes,
    pruneInlineComposesToOpenThread,
  } = useComposeActions()

  const {
    scheduledEmails,
    snoozedEmails,
    sentPlaceholderEmails,
    requestDeleteScheduled,
    requestArchiveScheduled,
    requestSnoozeScheduled,
    requestToggleReadScheduled,
    requestRescheduleScheduled,
    requestGetScheduledEditPayload,
    requestSendScheduledNow,
    requestSnoozeMailboxEmail,
    requestRestoreSnoozedToInbox,
  } = useScheduledMail()

  const scheduledPersistHydrated = usePersistHydrated(useScheduledStore)

  const allEmails = useMemo(
    () =>
      scheduledPersistHydrated
        ? [...emails, ...scheduledEmails, ...snoozedEmails, ...sentPlaceholderEmails]
        : emails,
    [scheduledPersistHydrated, scheduledEmails, snoozedEmails, sentPlaceholderEmails]
  )

  const emailById = useMemo(
    () => new Map(allEmails.map((e) => [e.id, e])),
    [allEmails]
  )

  const sidebarNav = useSidebarNav()
  const navMaps = useMemo<MailNavFolderMaps>(
    () => ({
      folderIdToLabel: sidebarNav.folderIdToLabel,
      folderTree: sidebarNav.folderTree,
      labelRows: sidebarNav.labelRows,
    }),
    [sidebarNav.folderIdToLabel, sidebarNav.folderTree, sidebarNav.labelRows]
  )

  const inboxCategoryTabIconsCatalog = useMemo(
    () => buildInboxCategoryTabIcons(sidebarNav.labelRows),
    [sidebarNav.labelRows]
  )

  const inboxTabBarItems = useMemo(
    () => buildInboxTabBarItems(sidebarNav.labelRows),
    [sidebarNav.labelRows]
  )

  const listRowLabelBgByTextLower = useMemo(
    () => buildLabelTextToNavColorClass(sidebarNav.folderTree, sidebarNav.labelRows),
    [sidebarNav.folderTree, sidebarNav.labelRows]
  )

  const [rescheduleTarget, setRescheduleTarget] = useState<{
    id: string
    value: string
    /** Faux pendant la fermeture du Popover : la barre d’actions reste visible (évite saut d’ancrage). */
    panelOpen: boolean
  } | null>(null)
  const rescheduleDismissTimeoutsRef = useRef<
    Map<string, ReturnType<typeof setTimeout>>
  >(new Map())

  const scheduleReschedulePopoverDismiss = useCallback((rowId: string) => {
    const existing = rescheduleDismissTimeoutsRef.current.get(rowId)
    if (existing) clearTimeout(existing)
    const t = setTimeout(() => {
      rescheduleDismissTimeoutsRef.current.delete(rowId)
      setRescheduleTarget((p) => (p?.id === rowId ? null : p))
    }, 280)
    rescheduleDismissTimeoutsRef.current.set(rowId, t)
  }, [])

  useEffect(() => {
    const m = rescheduleDismissTimeoutsRef.current
    return () => {
      for (const t of m.values()) clearTimeout(t)
      m.clear()
    }
  }, [])

  useEffect(() => {
    ensureVcLogosCollection()
  }, [])

  const [cmScheduledRescheduleValue, setCmScheduledRescheduleValue] =
    useState("")

  const handleEditScheduledMail = useCallback(
    async (id: string) => {
      const payload = await requestGetScheduledEditPayload(id)
      if (!payload) return
      openComposeWithInitial({
        to: payload.to,
        subject: payload.subject,
        bodyHtml: payload.bodyHtml,
        editingScheduledId: id,
        scheduledSendAtIso: payload.sendAtIso,
        focusToOnMount: false,
        focusBodyOnMount: true,
      })
    },
    [requestGetScheduledEditPayload, openComposeWithInitial]
  )

  useEffect(() => {
    if (!openMailId) {
      closeAllInlineComposes()
    } else {
      const msg = emailById.get(openMailId)
      pruneInlineComposesToOpenThread(msg ? threadStoreId(msg) : openMailId)
    }
  }, [
    openMailId,
    emailById,
    closeAllInlineComposes,
    pruneInlineComposesToOpenThread,
  ])

  const { beginDrag, registerOnDrop } = useEmailDrag()
  const starredEmails = useMailStore((s) => s.starredIds)
  const importantEmails = useMailStore((s) => s.importantIds)
  const [selectedEmails, setSelectedEmails] = useState<string[]>([])
  const readOverrides = useMailStore((s) => s.readOverrides)
  const conversationMode = useMailSettingsStore((s) => s.conversationMode)
  const inboxSort = useMailSettingsStore((s) => s.inboxSort)
  const density = useMailSettingsStore((s) => s.density)
  const isMd = useIsMd()
  const labelEdits = useMailStore((s) => s.labelEdits)
  const mailActions = useRef(useMailStore.getState()).current
  const setReadOverrides = useCallback(
    (updater: (prev: Record<string, boolean>) => Record<string, boolean>) => {
      const current = useMailStore.getState().readOverrides
      const next = updater(current)
      if (next !== current) mailActions.setReadOverrides(next)
    },
    [mailActions]
  )
  const setLabelEdits = useCallback(
    (updater: (prev: LabelEditState) => LabelEditState) => {
      mailActions.setLabelEdits(updater)
    },
    [mailActions]
  )

  useEffect(() => {
    registerNavEmailSync({
      renameLabel: (from, to) => {
        setLabelEdits((prev) => applyNavRenameToEdits(allEmails, prev, from, to))
      },
      removeLabel: (label) => {
        setLabelEdits((prev) => applyNavRemoveLabelToEdits(allEmails, prev, label))
      },
    })
    return () => registerNavEmailSync(null)
  }, [allEmails])
  const [labelPickerQuery, setLabelPickerQuery] = useState("")
  const hiddenEmailIds = useMailStore((s) => s.hiddenEmailIds)
  const notSpamEmailIds = useMailStore((s) => s.notSpamEmailIds)
  const recentMoveTargets = useMailStore((s) => s.recentMoveTargets)
  const rowContextMenuOpenedAtRef = useRef(0)
  const contextMenuTargetIdsRef = useRef<string[]>([])
  const lastSelectionAnchorIdRef = useRef<string | null>(null)
  const [bulkSelectMenuOpen, setBulkSelectMenuOpen] = useState(false)
  const [isRefreshing, setIsRefreshing] = useState(false)
  const [mobileVisibleCount, setMobileVisibleCount] = useState(LIST_PAGE_SIZE)
  const [mobileSelectionMode, setMobileSelectionMode] = useState(false)
  const [mobileXsMoreMenuOpen, setMobileXsMoreMenuOpen] = useState(false)
  const [mobileXsMoveSheetOpen, setMobileXsMoveSheetOpen] = useState(false)
  const [mobileXsLabelSheetOpen, setMobileXsLabelSheetOpen] = useState(false)
  const [swipeLabelEmailId, setSwipeLabelEmailId] = useState<string | null>(null)
  const [openSwipeRowId, setOpenSwipeRowId] = useState<string | null>(null)
  const isXs = useIsXs()
  const touchNav = useTouchNav()

  const openMobileXsMoveSheet = useCallback(() => {
    setMobileXsMoreMenuOpen(false)
    window.setTimeout(() => setMobileXsMoveSheetOpen(true), 0)
  }, [])

  const handleMobileXsMoveSheetOpenChange = useCallback((open: boolean) => {
    setMobileXsMoveSheetOpen(open)
    if (!open) {
      setMobileSelectionMode(false)
      setSelectedEmails([])
    }
  }, [])

  const openMobileXsLabelSheet = useCallback(() => {
    setMobileXsMoreMenuOpen(false)
    setSwipeLabelEmailId(null)
    window.setTimeout(() => setMobileXsLabelSheetOpen(true), 0)
  }, [])

  const handleLabelSheetOpenChange = useCallback((open: boolean) => {
    setMobileXsLabelSheetOpen(open)
    if (!open) setSwipeLabelEmailId(null)
  }, [])

  const touchListSwipeEnabled = touchNav && !mobileSelectionMode && !isViewMode

  useEffect(() => {
    if (!openSwipeRowId) return
    const handler = (e: globalThis.TouchEvent) => {
      const target = e.target as HTMLElement | null
      if (!target) return
      const swipeRow = target.closest(`[data-swipe-row-id="${openSwipeRowId}"]`)
      if (!swipeRow) setOpenSwipeRowId(null)
    }
    document.addEventListener("touchstart", handler, { passive: true })
    return () => document.removeEventListener("touchstart", handler)
  }, [openSwipeRowId])

  const listViewportRef = useRef<HTMLDivElement>(null)
  const pullContentRef = useRef<HTMLDivElement>(null)
  const pullIconRef = useRef<SVGSVGElement>(null)
  const pullTouchStartYRef = useRef(0)
  const pullActiveRef = useRef(false)
  const pullYRef = useRef(0)
  const pullRafRef = useRef<number | null>(null)
  const pendingPullYRef = useRef(0)
  const seenEmailIdsRaw = useMailStore((s) => s.seenEmailIds)
  const seenEmailIds = useMemo(() => new Set(seenEmailIdsRaw), [seenEmailIdsRaw])

  const markEmailSeen = useCallback((id: string) => {
    mailActions.markSeen(id)
  }, [mailActions])

  const folderFilterCtx = useMemo(
    () => ({
      starredEmailIds: starredEmails,
      importantEmailIds: importantEmails,
    }),
    [starredEmails, importantEmails]
  )

  const handleRefreshMessages = useCallback(async () => {
    if (isRefreshing) return
    setIsRefreshing(true)
    try {
      await new Promise((resolve) => setTimeout(resolve, 900))
    } finally {
      setIsRefreshing(false)
    }
  }, [isRefreshing])

  const applyPullVisual = useCallback((y: number, animate: boolean) => {
    const content = pullContentRef.current
    const icon = pullIconRef.current
    const transition = animate ? PULL_SNAP_BACK_TRANSITION : "none"
    if (content) {
      content.style.transition = transition
      content.style.transform = `translate3d(0, ${y}px, 0)`
    }
    if (icon) {
      if (y === 0) {
        icon.style.transition = animate
          ? `opacity ${PULL_ICON_FADE_MS}ms ease-out, transform ${PULL_ICON_FADE_MS}ms ease-out`
          : "none"
        icon.style.opacity = "0"
        icon.style.transform = "rotate(0deg)"
        icon.style.removeProperty("animation")
      } else {
        const progress = computeSpinnerRevealProgress(y)
        icon.style.transition = animate
          ? `opacity ${PULL_ICON_FADE_MS}ms ease-out, transform ${PULL_ICON_FADE_MS}ms ease-out`
          : "none"
        icon.style.opacity = String(progress)
        icon.style.transform = `rotate(${Math.min(320, progress * 320)}deg)`
      }
    }
  }, [])

  const schedulePullVisual = useCallback(
    (y: number) => {
      pendingPullYRef.current = y
      if (pullRafRef.current != null) return
      pullRafRef.current = requestAnimationFrame(() => {
        pullRafRef.current = null
        applyPullVisual(pendingPullYRef.current, false)
      })
    },
    [applyPullVisual]
  )

  const resetPullVisual = useCallback(
    (animate: boolean) => {
      if (pullRafRef.current != null) {
        cancelAnimationFrame(pullRafRef.current)
        pullRafRef.current = null
      }
      pullYRef.current = 0
      pendingPullYRef.current = 0
      applyPullVisual(0, animate)
    },
    [applyPullVisual]
  )

  const armPullRefreshSpinner = useCallback(() => {
    const icon = pullIconRef.current
    if (!icon) return
    icon.style.transition = "none"
    icon.style.opacity = "1"
    icon.style.removeProperty("transform")
    icon.style.animation = "spin 0.55s linear infinite"
  }, [])

  const releasePull = useCallback(async () => {
    if (pullRafRef.current != null) {
      cancelAnimationFrame(pullRafRef.current)
      pullRafRef.current = null
    }
    const offset = pullYRef.current
    if (offset >= PULL_REFRESH_THRESHOLD) {
      pullYRef.current = PULL_HOLD_HEIGHT
      applyPullVisual(PULL_HOLD_HEIGHT, false)
      armPullRefreshSpinner()
      void handleRefreshMessages()
      return
    }
    pullYRef.current = 0
    applyPullVisual(0, true)
  }, [applyPullVisual, armPullRefreshSpinner, handleRefreshMessages])

  useEffect(() => {
    if (isViewMode || !isXs || isRefreshing) return
    pullYRef.current = 0
    applyPullVisual(0, true)
  }, [isRefreshing, isViewMode, isXs, applyPullVisual])

  const filteredEmails = useMemo(() => {
    const hiddenSet = new Set(hiddenEmailIds)
    const subtreeIdsCache = new Map<string, string[] | null>()
    let visible = allEmails.filter((email) => !hiddenSet.has(email.id))
    const hasLabelEdits =
      labelEdits &&
      (Object.keys(labelEdits.additions).length > 0 ||
        Object.keys(labelEdits.removals).length > 0)
    if (hasLabelEdits || notSpamEmailIds.length > 0) {
      visible = visible.map((e) =>
        mergeEmailNotSpam(mergeEmailLabelEdits(e, labelEdits), notSpamEmailIds)
      )
    }

    if (isSearchMode && searchParams) {
      return filterEmailsBySearchParams(visible, searchParams, {
        starredIds: starredEmails,
        importantIds: importantEmails,
      })
    }

    let rows = visible.filter((email) =>
      emailMatchesFolder(
        email,
        selectedFolder,
        folderFilterCtx,
        navMaps,
        subtreeIdsCache
      )
    )
    if (selectedFolder === "inbox") {
      const tab = normalizeInboxTabSegment(inboxTab)
      if (tab === "primary") {
        rows = rows.filter((email) =>
          emailMatchesInboxPrimaryTab(
            email,
            folderFilterCtx,
            navMaps,
            subtreeIdsCache
          )
        )
      } else if (tab !== INBOX_ALL_TAB) {
        rows = rows.filter(
          (email) =>
            emailMatchesFolder(
              email,
              "inbox",
              folderFilterCtx,
              navMaps,
              subtreeIdsCache
            ) &&
            emailMatchesFolder(
              email,
              tab,
              folderFilterCtx,
              navMaps,
              subtreeIdsCache
            )
        )
      }
    }
    return rows
  }, [
    selectedFolder,
    inboxTab,
    hiddenEmailIds,
    folderFilterCtx,
    labelEdits,
    notSpamEmailIds,
    allEmails,
    navMaps,
    isSearchMode,
    searchParams,
    starredEmails,
    importantEmails,
  ])

  const displayListEmails = useMemo(() => {
    let rows = filteredEmails
    if (conversationMode) {
      rows = rows.filter(isThreadHeadMessage)
    }
    return sortEmailsForInbox(
      rows,
      inboxSort,
      {
        readOverrides,
        starredIds: starredEmails,
        importantIds: importantEmails,
      },
      { conversationMode, byId: emailById }
    )
  }, [
    filteredEmails,
    conversationMode,
    inboxSort,
    readOverrides,
    starredEmails,
    importantEmails,
    emailById,
  ])

  const inboxCategoryTabLabel = useMemo(
    () =>
      inboxTabDisplayLabel(
        inboxTab,
        sidebarNav.labelRows,
        sidebarNav.folderIdToLabel
      ),
    [inboxTab, sidebarNav.labelRows, sidebarNav.folderIdToLabel]
  )

  const mobileUnreadCount = useMemo(
    () =>
      displayListEmails.filter(
        (e) => !isListRowRead(e, readOverrides, emailById, conversationMode)
      ).length,
    [displayListEmails, readOverrides, emailById, conversationMode]
  )

  const mobileFolderLabel = useMemo(() => {
    if (isSearchMode) return "Résultats de recherche"
    const inboxTabNorm = normalizeInboxTabSegment(inboxTab)
    return selectedFolder === "inbox" && inboxTabNorm !== "primary"
      ? inboxCategoryTabLabel
      : getMailNavFolderLabel(selectedFolder, sidebarNav.folderIdToLabel)
  }, [
    selectedFolder,
    inboxTab,
    inboxCategoryTabLabel,
    sidebarNav.folderIdToLabel,
    isSearchMode,
  ])

  useEffect(() => {
    setMobileSelectionMode(false)
    setSelectedEmails([])
  }, [selectedFolder, inboxTab])

  const totalPages = useMemo(
    () => Math.max(1, Math.ceil(displayListEmails.length / LIST_PAGE_SIZE)),
    [displayListEmails.length]
  )

  const pagedEmails = useMemo(() => {
    const start = (listPage - 1) * LIST_PAGE_SIZE
    return displayListEmails.slice(start, start + LIST_PAGE_SIZE)
  }, [displayListEmails, listPage])

  const listEmails = useMemo(() => {
    if (isXs && !isViewMode) {
      return displayListEmails.slice(0, mobileVisibleCount)
    }
    return pagedEmails
  }, [isXs, isViewMode, displayListEmails, mobileVisibleCount, pagedEmails])

  const listMailIndex = useMemo(() => buildListMailIndex(allEmails), [allEmails])

  const listRowExtras = useMemo(() => {
    const invitationById = new Map<
      string,
      ReturnType<typeof resolveParsedCalendarInvitation>
    >()
    const attachmentsById = new Map<string, EmailAttachment[]>()
    const categoryTabsById = new Map<
      string,
      ReturnType<typeof resolveEmailInboxCategoryTabs>
    >()
    const subtreeIdsCache = new Map<string, string[] | null>()
    const showCategoryTabIcons =
      selectedFolder === "inbox" &&
      normalizeInboxTabSegment(inboxTab) === INBOX_ALL_TAB

    for (const e of listEmails) {
      invitationById.set(e.id, resolveParsedCalendarInvitation(e))
      attachmentsById.set(e.id, attachmentsForEmailList(e))
      if (showCategoryTabIcons) {
        const tabs = resolveEmailInboxCategoryTabs(
          e,
          folderFilterCtx,
          navMaps,
          inboxCategoryTabIconsCatalog,
          subtreeIdsCache
        )
        if (tabs.length > 0) categoryTabsById.set(e.id, tabs)
      }
    }
    return { invitationById, attachmentsById, categoryTabsById }
  }, [
    listEmails,
    selectedFolder,
    inboxTab,
    folderFilterCtx,
    navMaps,
    inboxCategoryTabIconsCatalog,
  ])

  useEffect(() => {
    if (isXs) return
    if (listPage > totalPages) {
      onMailRouteNavigate({ page: totalPages })
    }
  }, [isXs, listPage, totalPages, onMailRouteNavigate])

  useEffect(() => {
    if (isXs && !isViewMode) return
    listViewportRef.current?.scrollTo(0, 0)
  }, [listPage, selectedFolder, inboxTab, isXs, isViewMode])

  useEffect(() => {
    if (!isXs) return
    setMobileVisibleCount(LIST_PAGE_SIZE)
    listViewportRef.current?.scrollTo(0, 0)
  }, [selectedFolder, inboxTab, isXs])

  useEffect(() => {
    const root = listViewportRef.current
    if (!root || !isXs || isViewMode) return

    const onScroll = () => {
      if (mobileVisibleCount >= displayListEmails.length) return
      const nearBottom =
        root.scrollTop + root.clientHeight >= root.scrollHeight - 120
      if (nearBottom) {
        setMobileVisibleCount((prev) =>
          Math.min(prev + LIST_PAGE_SIZE, displayListEmails.length)
        )
      }
    }

    root.addEventListener("scroll", onScroll, { passive: true })
    return () => root.removeEventListener("scroll", onScroll)
  }, [isXs, isViewMode, mobileVisibleCount, displayListEmails.length])

  useEffect(() => {
    const root = listViewportRef.current
    if (!root || !isXs || isViewMode) return

    const onTouchStart = (e: TouchEvent) => {
      if (root.scrollTop > 0 || isRefreshing) return
      pullActiveRef.current = true
      pullTouchStartYRef.current = e.touches[0]?.clientY ?? 0
    }

    const onTouchMove = (e: TouchEvent) => {
      if (!pullActiveRef.current || isRefreshing) return
      const y = e.touches[0]?.clientY ?? 0
      const delta = y - pullTouchStartYRef.current
      if (root.scrollTop > 0) {
        pullActiveRef.current = false
        resetPullVisual(true)
        return
      }
      if (delta <= 0) {
        resetPullVisual(true)
        return
      }
      e.preventDefault()
      const next = computePullOffset(delta)
      pullYRef.current = next
      schedulePullVisual(next)
    }

    const endPull = () => {
      if (!pullActiveRef.current) return
      pullActiveRef.current = false
      void releasePull()
    }

    root.addEventListener("touchstart", onTouchStart, { passive: true })
    root.addEventListener("touchmove", onTouchMove, { passive: false })
    root.addEventListener("touchend", endPull)
    root.addEventListener("touchcancel", endPull)
    return () => {
      if (pullRafRef.current != null) {
        cancelAnimationFrame(pullRafRef.current)
        pullRafRef.current = null
      }
      root.removeEventListener("touchstart", onTouchStart)
      root.removeEventListener("touchmove", onTouchMove)
      root.removeEventListener("touchend", endPull)
      root.removeEventListener("touchcancel", endPull)
    }
  }, [isXs, isViewMode, isRefreshing, releasePull, resetPullVisual, schedulePullVisual])

  const moveTargets = useMoveTargets({
    folderTree: sidebarNav.folderTree,
    recentMoveTargets,
    currentFolderId: selectedFolder,
  })

  const collectAllFolderLabels = useCallback((): Set<string> => {
    const s = new Set<string>()
    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<string>()
    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]
  )

  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]
  )

  const folderUnreadCounts = useMemo(
    () =>
      computeFolderUnreadCounts(
        allEmails,
        folderFilterCtx,
        hiddenEmailIds,
        readOverrides,
        navMaps,
        labelEdits,
        notSpamEmailIds
      ),
    [
      folderFilterCtx,
      hiddenEmailIds,
      readOverrides,
      allEmails,
      navMaps,
      labelEdits,
      notSpamEmailIds,
    ]
  )

  const pageIds = useMemo(() => listEmails.map((e) => e.id), [listEmails])
  const selectedOnPageCount = useMemo(
    () => pageIds.filter((id) => selectedEmails.includes(id)).length,
    [pageIds, selectedEmails]
  )
  const allPageSelected = pageIds.length > 0 && selectedOnPageCount === pageIds.length
  const somePageSelected = selectedOnPageCount > 0 && !allPageSelected
  const selectAllChecked: boolean | "indeterminate" = allPageSelected
    ? true
    : somePageSelected
      ? "indeterminate"
      : false

  const toggleStar = (id: string) => {
    mailActions.toggleStar(id)
  }

  const toggleImportant = (id: string) => {
    mailActions.toggleImportant(id)
  }

  const toggleSelect = (id: string) => {
    setSelectedEmails(prev =>
      prev.includes(id) ? prev.filter(e => e !== id) : [...prev, id]
    )
  }

  const selectRangeInclusive = (fromId: string, toId: string) => {
    const ids = pageIds
    const i0 = ids.indexOf(fromId)
    const i1 = ids.indexOf(toId)
    if (i0 === -1 || i1 === -1) return
    const lo = Math.min(i0, i1)
    const hi = Math.max(i0, i1)
    const range = ids.slice(lo, hi + 1)
    setSelectedEmails((prev) => [...new Set([...prev, ...range])])
  }

  const handleSelectAllChange = (checked: boolean | "indeterminate") => {
    if (checked === true) {
      setSelectedEmails((prev) => [...new Set([...prev, ...pageIds])])
    } else {
      setSelectedEmails((prev) => prev.filter((id) => !pageIds.includes(id)))
    }
  }

  const mergePageSelection = (subsetOfPageIds: string[]) => {
    setSelectedEmails((prev) => {
      const outsidePage = prev.filter((id) => !pageIds.includes(id))
      return [...new Set([...outsidePage, ...subsetOfPageIds])]
    })
  }

  const effectiveRead = (email: Email) =>
    readOverrides[email.id] !== undefined ? readOverrides[email.id]! : email.read

  const seenSerialized = useMemo(
    () => [...seenEmailIds].sort().join(","),
    [seenEmailIds]
  )

  /** Onglets catégories : « nouveaux » + ligne d’expéditeurs = non vus (pas encore aperçus dans la liste), pas non lus. */
  const { unseenInTabById, tabUnseenSenderLineById } = useMemo(() => {
    const seen = new Set(
      seenSerialized.length > 0 ? seenSerialized.split(",") : []
    )
    const hidden = new Set(hiddenEmailIds)
    const visible = allEmails
      .filter((email) => !hidden.has(email.id))
      .map((e) =>
        mergeEmailNotSpam(mergeEmailLabelEdits(e, labelEdits), notSpamEmailIds)
      )
    const inboxPool = visible.filter((e) =>
      emailMatchesFolder(e, "inbox", folderFilterCtx, navMaps)
    )
    const counts: Record<string, number> = {}
    const preview: Record<string, string> = {}
    const tabCache = new Map<string, string[] | null>()
    for (const tab of inboxTabBarItems) {
      const rows = inboxPool.filter((e) => {
        if (tab.id === "primary") {
          return (
            emailMatchesInboxPrimaryTab(e, folderFilterCtx, navMaps, tabCache) &&
            !seen.has(e.id)
          )
        }
        if (tab.id === INBOX_ALL_TAB) {
          return !seen.has(e.id)
        }
        return (
          emailMatchesFolder(e, "inbox", folderFilterCtx, navMaps, tabCache) &&
          emailMatchesFolder(e, tab.id, folderFilterCtx, navMaps, tabCache) &&
          !seen.has(e.id)
        )
      })
      counts[tab.id] = rows.length
      if (inboxTabShowsInactiveMeta(tab.id)) {
        const chain: string[] = []
        const used = new Set<string>()
        for (const e of rows) {
          const n = cleanSenderName(e.sender).trim()
          if (!n || used.has(n)) continue
          used.add(n)
          chain.push(n)
          if (chain.length >= 6) break
        }
        preview[tab.id] = chain.join(", ")
      }
    }
    return { unseenInTabById: counts, tabUnseenSenderLineById: preview }
  }, [folderFilterCtx, hiddenEmailIds, labelEdits, seenSerialized, allEmails, navMaps, notSpamEmailIds, inboxTabBarItems])

  const effectiveStarred = (email: Email) =>
    starredEmails.includes(email.id) || email.starred

  const selectMenuAll = () => mergePageSelection(pageIds)
  const selectMenuNone = () =>
    setSelectedEmails((prev) => prev.filter((id) => !pageIds.includes(id)))
  const selectMenuRead = () =>
    mergePageSelection(
      listEmails.filter((e) => effectiveRead(e)).map((e) => e.id)
    )
  const selectMenuUnread = () =>
    mergePageSelection(
      listEmails.filter((e) => !effectiveRead(e)).map((e) => e.id)
    )
  const selectMenuStarred = () =>
    mergePageSelection(
      listEmails.filter((e) => effectiveStarred(e)).map((e) => e.id)
    )
  const selectMenuUnstarred = () =>
    mergePageSelection(
      listEmails.filter((e) => !effectiveStarred(e)).map((e) => e.id)
    )

  const handleRowCheckboxClickCapture = (id: string, e: MouseEvent) => {
    if (e.shiftKey && lastSelectionAnchorIdRef.current != null) {
      e.preventDefault()
      e.stopPropagation()
      selectRangeInclusive(lastSelectionAnchorIdRef.current, id)
      lastSelectionAnchorIdRef.current = id
    }
  }

  const bulkTargetIds = useMemo(
    () => pageIds.filter((id) => selectedEmails.includes(id)),
    [pageIds, selectedEmails]
  )
  const hasUnreadInSelection = useMemo(() => {
    for (const id of bulkTargetIds) {
      const email = allEmails.find((e) => e.id === id)
      if (!email) continue
      const isRead =
        readOverrides[id] !== undefined ? readOverrides[id]! : email.read
      if (!isRead) return true
    }
    return false
  }, [bulkTargetIds, readOverrides, allEmails])
  const showBulkToolbar = bulkTargetIds.length > 0

  const labelSheetTargetIds = useMemo(
    () => (swipeLabelEmailId ? [swipeLabelEmailId] : bulkTargetIds),
    [swipeLabelEmailId, bulkTargetIds]
  )

  const clearBulkSelection = (ids: string[]) => {
    setSelectedEmails((prev) => prev.filter((id) => !ids.includes(id)))
  }

  const bulkHideFromList = (ids: string[]) => {
    if (ids.length === 0) return
    mailActions.hideEmails(ids)
    clearBulkSelection(ids)
  }

  const bulkArchive = () => bulkHideFromList(bulkTargetIds)
  const bulkDelete = () => bulkHideFromList(bulkTargetIds)
  const bulkSpam = () => bulkHideFromList(bulkTargetIds)

  const handleEmailsDroppedOnTarget = useCallback(
    (targetId: string, _targetLabel: string, ids: string[]) => {
      if (ids.length === 0) return
      moveEmailsToTarget(ids, targetId)
      setSelectedEmails((prev) => prev.filter((id) => !ids.includes(id)))
    },
    [moveEmailsToTarget]
  )

  useEffect(() => {
    return registerOnDrop(handleEmailsDroppedOnTarget)
  }, [registerOnDrop, handleEmailsDroppedOnTarget])

  const startRowDrag = useCallback(
    (rowId: string, e: DragEvent<HTMLDivElement>) => {
      if (isXs) return
      const inSelection = selectedEmails.includes(rowId)
      const ids =
        inSelection && bulkTargetIds.length > 0 ? bulkTargetIds : [rowId]
      if (e.dataTransfer) {
        e.dataTransfer.effectAllowed = "move"
        try {
          e.dataTransfer.setData("text/plain", ids.join(","))
        } catch {
          /* some browsers throw if called outside dragstart context */
        }
        const ghost = document.createElement("div")
        ghost.style.position = "fixed"
        ghost.style.top = "-1000px"
        ghost.style.left = "-1000px"
        ghost.style.width = "1px"
        ghost.style.height = "1px"
        ghost.style.opacity = "0"
        document.body.appendChild(ghost)
        e.dataTransfer.setDragImage(ghost, 0, 0)
        window.setTimeout(() => {
          if (ghost.parentNode) ghost.parentNode.removeChild(ghost)
        }, 0)
      }
      beginDrag(ids, selectedFolder, e.clientX, e.clientY)
    },
    [beginDrag, isXs, selectedEmails, bulkTargetIds, selectedFolder]
  )

  const bulkMarkRead = () => {
    if (bulkTargetIds.length === 0) return
    setReadOverrides((prev) => {
      const next = { ...prev }
      for (const id of bulkTargetIds) next[id] = true
      return next
    })
  }

  const bulkMarkUnread = () => {
    if (bulkTargetIds.length === 0) return
    setReadOverrides((prev) => {
      const next = { ...prev }
      for (const id of bulkTargetIds) next[id] = false
      return next
    })
  }

  const markAllInViewAsRead = useCallback(() => {
    setReadOverrides((prev) => {
      const next = { ...prev }
      for (const e of displayListEmails) {
        for (const id of readStateTargets(e, conversationMode)) {
          next[id] = true
        }
      }
      return next
    })
  }, [displayListEmails, conversationMode])

  const bulkMoveTo = useCallback(
    (targetId: string) => {
      if (bulkTargetIds.length === 0) return
      moveEmailsToTarget(bulkTargetIds, targetId)
      if (targetId !== "inbox") {
        setSelectedEmails((prev) => prev.filter((id) => !bulkTargetIds.includes(id)))
      }
    },
    [bulkTargetIds, moveEmailsToTarget]
  )

  // --- View mode helpers ---
  const openEmailView = useMemo(() => {
    if (!openMailId) return null
    const resolved = resolveOpenEmailView(
      openMailId,
      allEmails,
      conversationMode
    )
    if (!resolved) return null
    if (resolved.email.labels?.includes("scheduled")) return null
    const email = mergeEmailNotSpam(
      mergeEmailLabelEdits(resolved.email, labelEdits),
      notSpamEmailIds
    )
    const threadRoot = mergeEmailNotSpam(
      mergeEmailLabelEdits(resolved.threadRoot, labelEdits),
      notSpamEmailIds
    )
    return {
      email,
      threadRoot,
      isSingleMessageView: resolved.isSingleMessageView,
    }
  }, [openMailId, labelEdits, allEmails, notSpamEmailIds, conversationMode])

  const openEmail = openEmailView?.email ?? null
  const openEmailThreadRoot = openEmailView?.threadRoot ?? null
  const isSingleMessageView = openEmailView?.isSingleMessageView ?? false

  const openMailIndex = useMemo(
    () =>
      openMailId ? displayListEmails.findIndex((e) => e.id === openMailId) : -1,
    [openMailId, displayListEmails]
  )

  useEffect(() => {
    if (!openMailId) return
    const message = emailById.get(openMailId)
    if (!message) return
    const targets = readStateTargets(message, conversationMode)
    for (const id of targets) {
      markEmailSeen(id)
    }
    setReadOverrides((prev) => {
      let changed = false
      const next = { ...prev }
      for (const id of targets) {
        if (next[id] === undefined) {
          next[id] = true
          changed = true
        }
      }
      return changed ? next : prev
    })
  }, [openMailId, markEmailSeen, emailById, conversationMode])

  const navigateToMail = useCallback(
    (id: string | null) => {
      if (id && splitView) {
        const idx = displayListEmails.findIndex((e) => e.id === id)
        if (idx >= 0) {
          const page = Math.floor(idx / LIST_PAGE_SIZE) + 1
          onMailRouteNavigate({ mailId: id, page })
          return
        }
      }
      onMailRouteNavigate({ mailId: id })
    },
    [splitView, displayListEmails, onMailRouteNavigate]
  )

  useEffect(() => {
    if (!openMailId) return
    const raw = allEmails.find((e) => e.id === openMailId)
    if (raw?.labels?.includes("scheduled")) {
      navigateToMail(null)
    }
  }, [openMailId, allEmails, navigateToMail])

  const pickAdjacentMailId = useCallback(
    (currentId: string) => {
      const idx = displayListEmails.findIndex((e) => e.id === currentId)
      if (idx < 0) return displayListEmails[0]?.id ?? null
      if (idx < displayListEmails.length - 1) return displayListEmails[idx + 1]!.id
      if (idx > 0) return displayListEmails[idx - 1]!.id
      return null
    },
    [displayListEmails]
  )

  const leaveReadingPane = useCallback(() => {
    if (!splitView) {
      navigateToMail(null)
      return
    }
    if (!openMailId) return
    navigateToMail(pickAdjacentMailId(openMailId))
  }, [splitView, openMailId, navigateToMail, pickAdjacentMailId])

  const goBack = useCallback(() => {
    if (splitView) leaveReadingPane()
    else navigateToMail(null)
  }, [splitView, leaveReadingPane, navigateToMail])

  const closeViewIfShowingEmail = useCallback(
    (emailId: string) => {
      if (openMailId === emailId) goBack()
    },
    [openMailId, goBack]
  )

  const archiveListRow = useCallback(
    (email: Email) => {
      if (email.labels?.includes("scheduled")) {
        void requestArchiveScheduled(email.id)
      } else {
        mailActions.hideEmail(email.id)
        closeViewIfShowingEmail(email.id)
      }
    },
    [closeViewIfShowingEmail, mailActions, requestArchiveScheduled]
  )

  const deleteListRow = useCallback(
    (email: Email) => {
      if (email.labels?.includes("scheduled")) {
        void requestDeleteScheduled(email.id)
      } else {
        mailActions.hideEmail(email.id)
        closeViewIfShowingEmail(email.id)
      }
    },
    [closeViewIfShowingEmail, mailActions, requestDeleteScheduled]
  )

  const openSwipeRowLabelSheet = useCallback((emailId: string) => {
    setSwipeLabelEmailId(emailId)
    setMobileXsLabelSheetOpen(true)
  }, [])

  const restoreSnoozedRowToMailbox = useCallback(
    (emailRow: Email) => {
      void requestRestoreSnoozedToInbox(emailRow)
      if (emailRow.id.startsWith("snz-")) {
        const baseId = emailRow.id.slice(4)
        if (baseId.length > 0) mailActions.unhideEmail(baseId)
        onSelectFolder?.("inbox")
      } else {
        onSelectFolder?.("scheduled")
      }
      closeViewIfShowingEmail(emailRow.id)
    },
    [
      requestRestoreSnoozedToInbox,
      mailActions,
      closeViewIfShowingEmail,
      onSelectFolder,
    ]
  )

  const handleCategoryInboxTabClick = useCallback(
    (tabId: string) => {
      startTransition(() => {
        onMailRouteNavigate({
          inboxTab: tabId,
          page: 1,
          mailId: null,
        })
      })
    },
    [onMailRouteNavigate]
  )

  const handleBreadcrumbNavigate = useCallback(
    (visitKey: string) => {
      if (visitKey === mailNavVisitKey(selectedFolder, inboxTab)) return
      const { folderId, inboxTab: tab } = parseMailNavVisitKey(visitKey)
      startTransition(() => {
        if (folderId === "inbox" && tab && tab !== DEFAULT_INBOX_TAB) {
          onMailRouteNavigate({
            folderId: "inbox",
            inboxTab: tab,
            page: 1,
            mailId: null,
          })
          return
        }
        if (onSelectFolder) {
          onSelectFolder(folderId)
          return
        }
        onMailRouteNavigate({
          folderId,
          inboxTab: DEFAULT_INBOX_TAB,
          page: 1,
          mailId: null,
        })
      })
    },
    [
      selectedFolder,
      inboxTab,
      onMailRouteNavigate,
      onSelectFolder,
    ]
  )

  const goListPrevPage = useCallback(() => {
    if (listPage <= 1) return
    onMailRouteNavigate({ page: listPage - 1 })
  }, [listPage, onMailRouteNavigate])

  const goListNextPage = useCallback(() => {
    if (listPage >= totalPages) return
    onMailRouteNavigate({ page: listPage + 1 })
  }, [listPage, totalPages, onMailRouteNavigate])

  const goToPrev = useCallback(() => {
    if (openMailIndex > 0) {
      const id = displayListEmails[openMailIndex - 1]!.id
      markEmailSeen(id)
      setReadOverrides((prev) => ({ ...prev, [id]: true }))
      navigateToMail(id)
    }
  }, [openMailIndex, displayListEmails, navigateToMail, markEmailSeen])

  const goToNext = useCallback(() => {
    if (openMailIndex >= 0 && openMailIndex < displayListEmails.length - 1) {
      const id = displayListEmails[openMailIndex + 1]!.id
      markEmailSeen(id)
      setReadOverrides((prev) => ({ ...prev, [id]: true }))
      navigateToMail(id)
    }
  }, [openMailIndex, displayListEmails, navigateToMail, markEmailSeen])

  const handleOpenEmail = useCallback(
    (id: string) => {
      const em = allEmails.find((e) => e.id === id)
      if (em?.labels?.includes("scheduled")) return
      markEmailSeen(id)
      setReadOverrides((prev) => ({ ...prev, [id]: true }))
      navigateToMail(id)
    },
    [navigateToMail, markEmailSeen, allEmails]
  )

  const openDraftInCompose = useCallback(
    (email: Email) => {
      markEmailSeen(email.id)
      setReadOverrides((prev) => ({ ...prev, [email.id]: true }))
      const to: Contact[] = email.senderEmail
        ? [{ name: email.sender.trim(), email: email.senderEmail }]
        : []
      const body =
        email.body ??
        (email.preview
          ? `<p style="color:#5f6368">${escapeHtml(email.preview)}</p>`
          : "<p></p>")
      openComposeWithInitial({
        to,
        subject: email.subject,
        bodyHtml: body,
        focusToOnMount: false,
        focusBodyOnMount: true,
      })
    },
    [markEmailSeen, openComposeWithInitial]
  )

  const handleRowActivate = useCallback(
    (email: Email) => {
      if (email.labels?.includes("scheduled")) return
      if (email.labels?.includes("drafts")) {
        openDraftInCompose(email)
        return
      }
      handleOpenEmail(email.id)
    },
    [handleOpenEmail, openDraftInCompose]
  )

  const viewModeIsRead = useMemo(() => {
    if (!openEmail) return true
    return readOverrides[openEmail.id] !== undefined
      ? readOverrides[openEmail.id]!
      : openEmail.read
  }, [openEmail, readOverrides])

  const afterSingleMessageRemoved = useCallback(
    (removedId: string) => {
      if (splitView) navigateToMail(pickAdjacentMailId(removedId))
      else navigateToMail(null)
    },
    [splitView, navigateToMail, pickAdjacentMailId]
  )

  const singleArchive = useCallback(() => {
    if (!openMailId) return
    const id = openMailId
    mailActions.hideEmail(id)
    afterSingleMessageRemoved(id)
  }, [openMailId, afterSingleMessageRemoved, mailActions])

  const singleDelete = useCallback(() => {
    if (!openMailId) return
    const id = openMailId
    mailActions.hideEmail(id)
    afterSingleMessageRemoved(id)
  }, [openMailId, afterSingleMessageRemoved, mailActions])

  const singleSpam = useCallback(() => {
    if (!openMailId) return
    const id = openMailId
    mailActions.hideEmail(id)
    afterSingleMessageRemoved(id)
  }, [openMailId, afterSingleMessageRemoved, mailActions])

  const singleNotSpam = useCallback(() => {
    if (!openMailId) return
    const id = openMailId
    mailActions.markNotSpam(id)
    onSelectFolder?.("inbox")
    afterSingleMessageRemoved(id)
  }, [openMailId, afterSingleMessageRemoved, onSelectFolder, mailActions])

  const singleToggleRead = useCallback(() => {
    if (!openMailId) return
    setReadOverrides((prev) => ({ ...prev, [openMailId]: !viewModeIsRead }))
  }, [openMailId, viewModeIsRead])

  const singleMoveTo = useCallback(
    (targetId: string) => {
      if (!openMailId) return
      moveEmailsToTarget([openMailId], targetId)
      const isSystemHide = ["sent", "drafts", "spam", "trash"].includes(targetId)
      if (isSystemHide || targetId !== "inbox") {
        afterSingleMessageRemoved(openMailId)
      }
    },
    [openMailId, afterSingleMessageRemoved, moveEmailsToTarget]
  )

  const singleReply = useCallback(() => {
    if (!openEmail) return
    openComposeWithInitial(
      withTouchFullscreenComposePreset(buildThreadComposePreset(openEmail, "reply"))
    )
  }, [openEmail, openComposeWithInitial])

  useEffect(() => {
    if (!onXsViewChromeChange) return
    if (!isXs || !isViewMode || !openEmail) {
      onXsViewChromeChange(null)
      return
    }
    onXsViewChromeChange({
      onArchive: singleArchive,
      onReply: singleReply,
      moveTargets,
      onMoveTo: singleMoveTo,
    })
    return () => onXsViewChromeChange(null)
  }, [
    onXsViewChromeChange,
    isXs,
    isViewMode,
    openEmail,
    singleArchive,
    singleReply,
    singleMoveTo,
    moveTargets,
  ])

  useEffect(() => {
    if (!splitView) return
    const firstId = displayListEmails[0]?.id ?? null
    if (!openMailId) {
      if (firstId) navigateToMail(firstId)
      return
    }
    const raw = allEmails.find((e) => e.id === openMailId)
    if (raw?.labels?.includes("scheduled")) {
      navigateToMail(firstId)
      return
    }
    if (!displayListEmails.some((e) => e.id === openMailId)) {
      navigateToMail(firstId)
    }
  }, [
    splitView,
    selectedFolder,
    inboxTab,
    listPage,
    displayListEmails,
    openMailId,
    navigateToMail,
    allEmails,
  ])

  const handleNavigateToLabel = useCallback(
    (label: string) => {
      const folderId =
        sidebarNav.emailLabelToSidebarFolderId[label] ?? label
      onSelectFolder?.(folderId)
    },
    [onSelectFolder, sidebarNav.emailLabelToSidebarFolderId]
  )

  useEffect(() => {
    onFolderUnreadCountsChange?.(folderUnreadCounts)
  }, [folderUnreadCounts, onFolderUnreadCountsChange])

  const listRowsDep = listEmails.map((e) => e.id).join(",")
  useLayoutEffect(() => {
    if (!splitView || !openMailId) return
    const scrollActiveRowIntoView = () => {
      const root = listViewportRef.current
      if (!root) return
      const row = root.querySelector<HTMLElement>(
        `[data-email-row-id="${openMailId}"]`
      )
      if (!row) return
      row.scrollIntoView({ block: "nearest", behavior: "smooth" })
    }
    scrollActiveRowIntoView()
    const frame = requestAnimationFrame(scrollActiveRowIntoView)
    return () => cancelAnimationFrame(frame)
  }, [splitView, openMailId, listPage, listRowsDep])

  useEffect(() => {
    const root = listViewportRef.current
    if (!root) return
    const obs = new IntersectionObserver(
      (entries) => {
        for (const en of entries) {
          if (!en.isIntersecting) continue
          const id = (en.target as HTMLElement).dataset.emailRowId
          if (id) markEmailSeen(id)
        }
      },
      { root, threshold: 0.12, rootMargin: "0px" }
    )
    root.querySelectorAll<HTMLElement>("[data-email-row-id]").forEach((el) => {
      obs.observe(el)
    })
    return () => obs.disconnect()
  }, [listRowsDep, markEmailSeen])

  // --- keyboard shortcuts for view / split reading pane ---
  useEffect(() => {
    if (!isViewMode && !showSplitReadingPane) return
    const handler = (e: KeyboardEvent) => {
      if (e.key === "Escape") {
        if (!splitView) goBack()
        return
      }
      if (e.key === "ArrowLeft" || e.key === "k") {
        goToPrev()
        return
      }
      if (e.key === "ArrowRight" || e.key === "j") {
        goToNext()
        return
      }
    }
    window.addEventListener("keydown", handler)
    return () => window.removeEventListener("keydown", handler)
  }, [isViewMode, showSplitReadingPane, splitView, goBack, goToPrev, goToNext])

  const dropdownSurfaceClass = MAIL_MENU_SURFACE_CLASS

  const listToolbarMode = splitView || !isViewMode
  /** xs + split : icône (+ point si non lus) ; libellé uniquement sur l’onglet actif. */
  const compactInboxTabs = isXs || splitView
  const activeInboxTabId = useMemo(
    () => normalizeInboxTabSegment(inboxTab),
    [inboxTab]
  )

  const openMailToolbar = (showBack: boolean) => (
    <TooltipProvider delayDuration={400}>
      {showBack ? (
        <Tooltip>
          <TooltipTrigger asChild>
            <Button
              type="button"
              variant="ghost"
              size="icon"
              className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
              aria-label="Retour à la boîte de réception"
              onClick={goBack}
            >
              <ArrowLeft className="h-[18px] w-[18px]" strokeWidth={1.5} />
            </Button>
          </TooltipTrigger>
          <TooltipContent side="bottom" className="text-xs">
            Retour à la boîte de réception
          </TooltipContent>
        </Tooltip>
      ) : null}

      <div className="flex min-w-0 flex-wrap items-center gap-0.5 pl-1">
        {openEmail?.spam === true ? (
          <>
            <div className="flex min-w-0 shrink-0 flex-wrap items-center gap-0.5">
              <Button
                type="button"
                variant="ghost"
                size="sm"
                className="h-9 shrink-0 px-2.5 text-sm font-medium text-[#444746] hover:bg-[#f1f3f4]"
                onClick={singleDelete}
              >
                Supprimer définitivement
              </Button>
              <Button
                type="button"
                variant="ghost"
                size="sm"
                className="h-9 shrink-0 px-2.5 text-sm font-medium text-[#444746] hover:bg-[#f1f3f4]"
                onClick={singleNotSpam}
              >
                Non-spam
              </Button>
            </div>

            <span className="mx-1 h-6 w-px shrink-0 bg-[#dadce0]" aria-hidden />

            <div className="flex shrink-0 items-center gap-0.5">
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                    aria-label="Archiver"
                    onClick={singleArchive}
                  >
                    <Archive className="h-[18px] w-[18px]" strokeWidth={1.5} />
                  </Button>
                </TooltipTrigger>
                <TooltipContent side="bottom" className="text-xs">
                  Archiver
                </TooltipContent>
              </Tooltip>
            </div>

            <span className="mx-1 h-6 w-px shrink-0 bg-[#dadce0]" aria-hidden />

            <div className="flex min-w-0 shrink-0 items-center gap-0.5">
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                    aria-label={
                      viewModeIsRead ? "Marquer comme non lu" : "Marquer comme lu"
                    }
                    onClick={singleToggleRead}
                  >
                    {viewModeIsRead ? (
                      <Mail className="h-[18px] w-[18px]" strokeWidth={1.5} />
                    ) : (
                      <MailOpen className="h-[18px] w-[18px]" strokeWidth={1.5} />
                    )}
                  </Button>
                </TooltipTrigger>
                <TooltipContent side="bottom" className="text-xs">
                  {viewModeIsRead ? "Marquer comme non lu" : "Marquer comme lu"}
                </TooltipContent>
              </Tooltip>

              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    className="h-9 w-9 shrink-0 gap-1 px-0 text-[#444746] hover:bg-[#f1f3f4] lg:h-9 lg:w-auto lg:px-2"
                    aria-label="Déplacer vers"
                  >
                    <FolderInput
                      className="h-[18px] w-[18px] shrink-0"
                      strokeWidth={1.5}
                    />
                    <span className="hidden max-w-32 truncate lg:inline">
                      Déplacer vers
                    </span>
                    <ChevronDown className="hidden h-3.5 w-3.5 shrink-0 opacity-70 lg:block" />
                  </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent
                  align="start"
                  className={cn(dropdownSurfaceClass, "max-h-80 overflow-y-auto")}
                >
                  <MoveToDropdownItems targets={moveTargets} onMoveTo={singleMoveTo} />
                </DropdownMenuContent>
              </DropdownMenu>
            </div>
          </>
        ) : (
          <>
            <div className="flex shrink-0 items-center gap-0.5">
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                    aria-label="Archiver"
                    onClick={singleArchive}
                  >
                    <Archive className="h-[18px] w-[18px]" strokeWidth={1.5} />
                  </Button>
                </TooltipTrigger>
                <TooltipContent side="bottom" className="text-xs">
                  Archiver
                </TooltipContent>
              </Tooltip>
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                    aria-label="Signaler comme spam"
                    onClick={singleSpam}
                  >
                    <ShieldAlert className="h-[18px] w-[18px]" strokeWidth={1.5} />
                  </Button>
                </TooltipTrigger>
                <TooltipContent side="bottom" className="text-xs">
                  Signaler comme spam
                </TooltipContent>
              </Tooltip>
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                    aria-label="Supprimer"
                    onClick={singleDelete}
                  >
                    <Trash2 className="h-[18px] w-[18px]" strokeWidth={1.5} />
                  </Button>
                </TooltipTrigger>
                <TooltipContent side="bottom" className="text-xs">
                  Supprimer
                </TooltipContent>
              </Tooltip>
            </div>

            <span className="mx-1 h-6 w-px shrink-0 bg-[#dadce0]" aria-hidden />

            <div className="flex min-w-0 shrink-0 items-center gap-0.5">
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                    aria-label={
                      viewModeIsRead ? "Marquer comme non lu" : "Marquer comme lu"
                    }
                    onClick={singleToggleRead}
                  >
                    {viewModeIsRead ? (
                      <Mail className="h-[18px] w-[18px]" strokeWidth={1.5} />
                    ) : (
                      <MailOpen className="h-[18px] w-[18px]" strokeWidth={1.5} />
                    )}
                  </Button>
                </TooltipTrigger>
                <TooltipContent side="bottom" className="text-xs">
                  {viewModeIsRead ? "Marquer comme non lu" : "Marquer comme lu"}
                </TooltipContent>
              </Tooltip>

              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="sm"
                    className="h-9 w-9 shrink-0 gap-1 px-0 text-[#444746] hover:bg-[#f1f3f4] lg:h-9 lg:w-auto lg:px-2"
                    aria-label="Déplacer vers"
                  >
                    <FolderInput
                      className="h-[18px] w-[18px] shrink-0"
                      strokeWidth={1.5}
                    />
                    <span className="hidden max-w-32 truncate lg:inline">
                      Déplacer vers
                    </span>
                    <ChevronDown className="hidden h-3.5 w-3.5 shrink-0 opacity-70 lg:block" />
                  </Button>
                </DropdownMenuTrigger>
                <DropdownMenuContent
                  align="start"
                  className={cn(dropdownSurfaceClass, "max-h-80 overflow-y-auto")}
                >
                  <MoveToDropdownItems targets={moveTargets} onMoveTo={singleMoveTo} />
                </DropdownMenuContent>
              </DropdownMenu>
            </div>
          </>
        )}
      </div>
    </TooltipProvider>
  )

  const mailPaginationControls = (mode: "list" | "view") => (
    <div
      className={cn(
        "flex shrink-0 items-center gap-2 whitespace-nowrap text-sm text-gray-600",
        mode === "list" && "max-sm:hidden sm:flex"
      )}
    >
      {displayListEmails.length === 0 ? (
        <span>Aucun résultat</span>
      ) : mode === "view" ? (
        <span className="hidden sm:inline">
          {openMailIndex >= 0 ? openMailIndex + 1 : "–"} sur {displayListEmails.length}
        </span>
      ) : (
        <span>
          {(listPage - 1) * LIST_PAGE_SIZE + 1}–
          {Math.min(listPage * LIST_PAGE_SIZE, displayListEmails.length)} sur{" "}
          {displayListEmails.length}
          {totalPages > 1 ? ` · p. ${listPage}/${totalPages}` : null}
        </span>
      )}
      <Tooltip>
        <TooltipTrigger asChild>
          <Button
            variant="ghost"
            size="icon"
            className={cn(
              "h-9 w-9",
              mode === "view" && openMailIndex > 0
                ? "text-gray-600"
                : mode === "list" && listPage > 1
                  ? "text-gray-600"
                  : "text-gray-400"
            )}
            disabled={mode === "view" ? openMailIndex <= 0 : listPage <= 1}
            onClick={mode === "view" ? goToPrev : goListPrevPage}
            aria-label={mode === "view" ? "Plus récent" : "Page précédente"}
          >
            <ChevronLeft className="h-4 w-4" />
          </Button>
        </TooltipTrigger>
        <TooltipContent side="bottom" className="text-xs">
          {mode === "view" ? "Plus récent" : "Page précédente"}
        </TooltipContent>
      </Tooltip>
      <Tooltip>
        <TooltipTrigger asChild>
          <Button
            variant="ghost"
            size="icon"
            className={cn(
              "h-9 w-9",
              mode === "view" && openMailIndex < displayListEmails.length - 1
                ? "text-gray-600"
                : mode === "list" && listPage < totalPages
                  ? "text-gray-600"
                  : "text-gray-400"
            )}
            disabled={
              mode === "view"
                ? openMailIndex >= displayListEmails.length - 1
                : listPage >= totalPages
            }
            onClick={mode === "view" ? goToNext : goListNextPage}
            aria-label={mode === "view" ? "Plus ancien" : "Page suivante"}
          >
            <ChevronRight className="h-4 w-4" />
          </Button>
        </TooltipTrigger>
        <TooltipContent side="bottom" className="text-xs">
          {mode === "view" ? "Plus ancien" : "Page suivante"}
        </TooltipContent>
      </Tooltip>
    </div>
  )

  const mainScrollClass =
    "min-h-0 flex-1 overflow-y-auto overflow-x-hidden border-0 bg-mail-surface shadow-none outline-none sm:rounded-b-2xl " +
    "[scrollbar-color:#9aa0a6_#ffffff] [scrollbar-width:auto] " +
    "[&::-webkit-scrollbar]:w-2.5 [&::-webkit-scrollbar]:border-0 [&::-webkit-scrollbar]:bg-white " +
    "[&::-webkit-scrollbar-track]:border-0 [&::-webkit-scrollbar-track]:bg-white [&::-webkit-scrollbar-track]:shadow-none " +
    "[&::-webkit-scrollbar-thumb]:rounded-none [&::-webkit-scrollbar-thumb]:border-0 [&::-webkit-scrollbar-thumb]:shadow-none " +
    "[&::-webkit-scrollbar-thumb]:bg-[#9aa0a6] hover:[&::-webkit-scrollbar-thumb]:bg-[#5f6368] " +
    "[&::-webkit-scrollbar-corner]:border-0 [&::-webkit-scrollbar-corner]:bg-white"

  return (
    <div className="flex h-full min-h-0 flex-1 flex-col">
      {/* Mobile xs top bar */}
      {!isViewMode && (
        <div className="relative z-20 flex shrink-0 items-center gap-2 border-b border-border bg-mail-surface dark:bg-zinc-800 px-4 py-2.5 sm:hidden">
          <div className="min-w-0 flex-1">
            <h1 className="truncate text-base font-semibold text-[#1f1f1f] leading-tight">
              {mobileFolderLabel}
            </h1>
            <p className="text-xs text-[#5f6368] leading-snug">
              {displayListEmails.length} message{displayListEmails.length !== 1 ? "s" : ""}
              {mobileUnreadCount > 0 && ` · ${mobileUnreadCount} non lu${mobileUnreadCount !== 1 ? "s" : ""}`}
            </p>
          </div>
          <Button
            type="button"
            variant="ghost"
            size={mobileSelectionMode ? "icon" : "sm"}
            className={cn(
              "shrink-0 text-[#444746]",
              mobileSelectionMode
                ? "size-9 rounded-full border border-gray-200 bg-white/80 shadow-md backdrop-blur hover:bg-white"
                : "h-9 min-h-9 gap-1.5 rounded-full border border-gray-200 bg-white/80 px-3 text-xs font-medium shadow-md backdrop-blur hover:bg-white"
            )}
            onClick={() => {
              setMobileSelectionMode((p) => !p)
              if (mobileSelectionMode) setSelectedEmails([])
            }}
            aria-label={mobileSelectionMode ? "Annuler la sélection" : "Sélection"}
          >
            {mobileSelectionMode ? (
              <X className="size-[18px]" strokeWidth={1.5} />
            ) : (
              <>
                <CheckSquare className="size-4" strokeWidth={1.5} />
                <span>Sélection</span>
              </>
            )}
          </Button>
          <DropdownMenu
            open={mobileXsMoreMenuOpen}
            onOpenChange={setMobileXsMoreMenuOpen}
          >
            <DropdownMenuTrigger asChild>
              <Button
                type="button"
                variant="ghost"
                size="icon"
                className="size-9 shrink-0 rounded-full border border-gray-200 bg-white/80 text-[#444746] shadow-md backdrop-blur hover:bg-white"
                aria-label="Plus d'actions"
              >
                <MoreVertical className="size-[18px]" strokeWidth={1.5} />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent
              align="end"
              sideOffset={4}
              className={cn(dropdownSurfaceClass, "min-w-[260px]")}
            >
              {showBulkToolbar ? (
                <>
                  <DropdownMenuItem onSelect={bulkArchive}>
                    <Archive className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                    Archiver
                  </DropdownMenuItem>
                  <DropdownMenuItem onSelect={bulkDelete}>
                    <Trash2 className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                    Supprimer
                  </DropdownMenuItem>
                  <DropdownMenuItem onSelect={bulkSpam}>
                    <ShieldAlert className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                    Signaler comme spam
                  </DropdownMenuItem>
                  <DropdownMenuItem
                    onSelect={hasUnreadInSelection ? bulkMarkRead : bulkMarkUnread}
                  >
                    {hasUnreadInSelection ? (
                      <>
                        <MailOpen className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                        Marquer comme lu
                      </>
                    ) : (
                      <>
                        <Mail className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                        Marquer comme non lu
                      </>
                    )}
                  </DropdownMenuItem>
                  <DropdownMenuSeparator />
                  <DropdownMenuItem
                    onSelect={(e) => {
                      e.preventDefault()
                      openMobileXsMoveSheet()
                    }}
                  >
                    <FolderInput className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                    <span className="flex-1">Déplacer vers</span>
                    <ChevronRight className="ml-auto size-4 text-[#5f6368]" strokeWidth={1.5} />
                  </DropdownMenuItem>
                  <DropdownMenuItem
                    onSelect={(e) => {
                      e.preventDefault()
                      openMobileXsLabelSheet()
                    }}
                  >
                    <Tag className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                    <span className="flex-1">Ajouter le libellé</span>
                    <ChevronRight className="ml-auto size-4 text-[#5f6368]" strokeWidth={1.5} />
                  </DropdownMenuItem>
                  <DropdownMenuItem>
                    <VolumeX className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                    Ignorer la conversation
                  </DropdownMenuItem>
                </>
              ) : (
                <>
                  <DropdownMenuItem onSelect={markAllInViewAsRead}>
                    <MailOpen className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                    Tout marquer comme lu
                  </DropdownMenuItem>
                  <DropdownMenuSeparator />
                  <div
                    className="px-3 py-2 text-sm leading-snug text-[#5f6368] select-none"
                    role="note"
                  >
                    Sélectionnez des messages pour plus d&apos;actions
                  </div>
                </>
              )}
            </DropdownMenuContent>
          </DropdownMenu>
        </div>
      )}
      {/* View-mode xs nav buttons are rendered inside the scroll area below */}
      {!isViewMode && touchNav && (
        <MobileXsBulkSheets
          moveSheetOpen={isXs && mobileXsMoveSheetOpen}
          onMoveSheetOpenChange={handleMobileXsMoveSheetOpenChange}
          labelSheetOpen={mobileXsLabelSheetOpen}
          onLabelSheetOpenChange={handleLabelSheetOpenChange}
          labelPickerQuery={labelPickerQuery}
          onLabelPickerQueryChange={setLabelPickerQuery}
          catalogLabels={catalogLabels}
          resolveLabelVisual={resolveLabelVisual}
          moveTargets={moveTargets}
          onMoveTo={bulkMoveTo}
          getLabelPresence={(lab) => getCatalogLabelPresence(labelSheetTargetIds, lab)}
          onToggleCatalogLabel={(lab) => toggleLabelOnEmails(labelSheetTargetIds, lab)}
          onCreateLabel={(lab) => {
            addLabelToEmails(labelSheetTargetIds, lab)
            setLabelPickerQuery("")
          }}
        />
      )}

      <div className={cn("flex min-h-0 flex-1 flex-col", splitView && "min-h-0 flex-row overflow-hidden")}>
        <div
          className={cn(
            "flex min-h-0 min-w-0 flex-col",
            splitView
              ? "relative w-[min(42%,480px)] min-w-[280px] max-w-[480px] shrink-0 border-r border-gray-200"
              : "min-h-0 flex-1"
          )}
        >
          {splitView ? (
            <div className="flex max-sm:hidden shrink-0 items-center gap-2 border-b border-border bg-mail-surface px-2 py-2">
              {onToggleSidebar ? (
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className="size-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                  aria-label="Ouvrir le menu"
                  onClick={onToggleSidebar}
                >
                  <Menu className="size-5" strokeWidth={1.5} />
                </Button>
              ) : null}
              <MailSearchBar compact className="min-w-0 flex-1" />
            </div>
          ) : null}

      {/* Toolbar — relative: scroll lives in sibling below */}
      <div
        className={cn(
          "relative z-20 flex shrink-0 min-h-12 gap-2 border-b border-border bg-mail-surface py-1.5 pl-2 pr-4",
          splitView ? "rounded-none" : "sm:rounded-t-2xl",
          isViewMode ? "items-start" : "items-center",
          (isViewMode ? !listToolbarMode : true) && "max-sm:hidden"
        )}
      >

        {!splitView && isViewMode ? (
          openMailToolbar(true)
        ) : (
          /* ── LIST MODE TOOLBAR (original) ── */
          <>
            <DropdownMenu
              open={bulkSelectMenuOpen}
              onOpenChange={setBulkSelectMenuOpen}
            >
              <div
                className={cn(
                  "flex items-center overflow-hidden rounded-md border pr-0 transition-[background-color,box-shadow,border-color]",
                  bulkSelectMenuOpen
                    ? "border-[#dadce0] bg-[#f1f3f4] shadow-sm"
                    : "border-transparent"
                )}
              >
                <div className="flex h-9 shrink-0 items-center pl-1 pr-0.5 md:pl-0">
                  <Checkbox
                    checked={selectAllChecked}
                    onCheckedChange={handleSelectAllChange}
                    className="size-4 min-h-4 min-w-4 shrink-0 rounded-[2.5px] border-[1.5px] border-[#c2c2c2] bg-transparent shadow-none dark:bg-transparent focus-visible:ring-[#c2c2c2]/30 data-[state=checked]:border-[#1a73e8] data-[state=checked]:bg-[#1a73e8] data-[state=checked]:text-white data-[state=indeterminate]:border-[#1a73e8] data-[state=indeterminate]:bg-[#1a73e8] data-[state=indeterminate]:text-white"
                  />
                </div>
                <DropdownMenuTrigger asChild>
                  <Button
                    type="button"
                    variant="ghost"
                    size="icon"
                    className={cn(
                      "h-9 w-7 shrink-0 rounded-none p-0 text-[#5f6368]",
                      bulkSelectMenuOpen
                        ? "border-l border-[#dadce0] hover:bg-[#e8eaed]"
                        : "hover:bg-[#f1f3f4]"
                    )}
                    aria-label="Options de sélection"
                  >
                    <ChevronDown className="h-4 w-4" />
                  </Button>
                </DropdownMenuTrigger>
              </div>
              <DropdownMenuContent
                align="start"
                sideOffset={4}
                className={cn(dropdownSurfaceClass, "min-w-[180px]")}
              >
                <DropdownMenuItem onSelect={selectMenuAll}>Tous</DropdownMenuItem>
                <DropdownMenuItem onSelect={selectMenuNone}>Aucun</DropdownMenuItem>
                <DropdownMenuItem onSelect={selectMenuRead}>Lus</DropdownMenuItem>
                <DropdownMenuItem onSelect={selectMenuUnread}>Non lus</DropdownMenuItem>
                <DropdownMenuItem onSelect={selectMenuStarred}>Suivis</DropdownMenuItem>
                <DropdownMenuItem onSelect={selectMenuUnstarred}>
                  Non suivis
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>

            {showBulkToolbar ? (
              <TooltipProvider delayDuration={400}>
                <div className="flex min-w-0 items-center gap-0.5 pl-1">
                  <div className="flex shrink-0 items-center gap-0.5">
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <Button
                          type="button"
                          variant="ghost"
                          size="icon"
                          className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                          aria-label="Archiver"
                          onClick={bulkArchive}
                        >
                          <Archive className="h-[18px] w-[18px]" strokeWidth={1.5} />
                        </Button>
                      </TooltipTrigger>
                      <TooltipContent side="bottom" className="text-xs">
                        Archiver
                      </TooltipContent>
                    </Tooltip>
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <Button
                          type="button"
                          variant="ghost"
                          size="icon"
                          className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                          aria-label="Signaler comme spam"
                          onClick={bulkSpam}
                        >
                          <ShieldAlert className="h-[18px] w-[18px]" strokeWidth={1.5} />
                        </Button>
                      </TooltipTrigger>
                      <TooltipContent side="bottom" className="text-xs">
                        Signaler comme spam
                      </TooltipContent>
                    </Tooltip>
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <Button
                          type="button"
                          variant="ghost"
                          size="icon"
                          className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                          aria-label="Supprimer"
                          onClick={bulkDelete}
                        >
                          <Trash2 className="h-[18px] w-[18px]" strokeWidth={1.5} />
                        </Button>
                      </TooltipTrigger>
                      <TooltipContent side="bottom" className="text-xs">
                        Supprimer
                      </TooltipContent>
                    </Tooltip>
                  </div>

                  <span
                    className="mx-1 h-6 w-px shrink-0 bg-[#dadce0]"
                    aria-hidden
                  />

                  <div className="flex min-w-0 shrink-0 items-center gap-0.5">
                    <Tooltip>
                      <TooltipTrigger asChild>
                        <Button
                          type="button"
                          variant="ghost"
                          size="icon"
                          className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                          aria-label={
                            hasUnreadInSelection
                              ? "Marquer comme lu"
                              : "Marquer comme non lu"
                          }
                          onClick={() =>
                            hasUnreadInSelection ? bulkMarkRead() : bulkMarkUnread()
                          }
                        >
                          {hasUnreadInSelection ? (
                            <MailOpen className="h-[18px] w-[18px]" strokeWidth={1.5} />
                          ) : (
                            <Mail className="h-[18px] w-[18px]" strokeWidth={1.5} />
                          )}
                        </Button>
                      </TooltipTrigger>
                      <TooltipContent side="bottom" className="text-xs">
                        {hasUnreadInSelection
                          ? "Marquer comme lu"
                          : "Marquer comme non lu"}
                      </TooltipContent>
                    </Tooltip>

                    <DropdownMenu>
                      <DropdownMenuTrigger asChild>
                        <Button
                          type="button"
                          variant="ghost"
                          size="sm"
                          className="h-9 w-9 shrink-0 gap-1 px-0 text-[#444746] hover:bg-[#f1f3f4] lg:h-9 lg:w-auto lg:px-2"
                          aria-label="Déplacer vers"
                        >
                          <FolderInput className="h-[18px] w-[18px] shrink-0" strokeWidth={1.5} />
                          <span className="hidden max-w-32 truncate lg:inline">
                            Déplacer vers
                          </span>
                          <ChevronDown className="hidden h-3.5 w-3.5 shrink-0 opacity-70 lg:block" />
                        </Button>
                      </DropdownMenuTrigger>
                      <DropdownMenuContent
                        align="start"
                        className={cn(dropdownSurfaceClass, "max-h-80 overflow-y-auto")}
                      >
                        <MoveToDropdownItems targets={moveTargets} onMoveTo={bulkMoveTo} />
                      </DropdownMenuContent>
                    </DropdownMenu>
                  </div>

                  <span
                    className="mx-1 h-6 w-px shrink-0 bg-[#dadce0]"
                    aria-hidden
                  />

                  <DropdownMenu
                    onOpenChange={(open) => {
                      if (!open) setLabelPickerQuery("")
                    }}
                  >
                    <DropdownMenuTrigger asChild>
                      <Button
                        type="button"
                        variant="ghost"
                        size="icon"
                        className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                        aria-label="Plus d'actions"
                      >
                        <MoreVertical className="h-[18px] w-[18px]" strokeWidth={1.5} />
                      </Button>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent
                      align="start"
                      className={cn(
                        dropdownSurfaceClass,
                        /* Sous-menus Radix restent dans ce nœud : overflow-auto les clippe */
                        "overflow-visible"
                      )}
                    >
                      <DropdownMenuItem>
                        <Clock className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                        Mettre en attente
                      </DropdownMenuItem>
                      <DropdownMenuItem>
                        <ListTodo className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                        Ajouter à Tasks
                      </DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuSub>
                        <DropdownMenuSubTrigger className="[&>svg:last-child]:text-[#5f6368]">
                          <Tag className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                          Ajouter le libellé
                        </DropdownMenuSubTrigger>
                        <DropdownMenuSubContent
                          className={cn(
                            dropdownSurfaceClass,
                            "z-[100] flex max-h-72 min-w-[260px] flex-col overflow-hidden p-0 py-0"
                          )}
                        >
                          <EmailLabelPickerBlock
                            query={labelPickerQuery}
                            onQueryChange={setLabelPickerQuery}
                            catalogLabels={catalogLabels}
                            resolveLabelVisual={resolveLabelVisual}
                            Item={DropdownMenuItem}
                            getLabelPresence={(lab) =>
                              getCatalogLabelPresence(bulkTargetIds, lab)
                            }
                            onToggleCatalogLabel={(lab) =>
                              toggleLabelOnEmails(bulkTargetIds, lab)
                            }
                            onCreateLabel={(lab) => {
                              addLabelToEmails(bulkTargetIds, lab)
                              setLabelPickerQuery("")
                            }}
                          />
                        </DropdownMenuSubContent>
                      </DropdownMenuSub>
                      <DropdownMenuItem>
                        <VolumeX className="size-[18px] text-[#5f6368]" strokeWidth={1.5} />
                        Ignorer la conversation
                      </DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuItem>
                        <SquareArrowOutUpRight
                          className="size-[18px] text-[#5f6368]"
                          strokeWidth={1.5}
                        />
                        Ouvrir dans une nouvelle fenêtre
                      </DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </div>
              </TooltipProvider>
            ) : (
              <>
                <Button
                  type="button"
                  variant="ghost"
                  size="icon"
                  className="hidden h-9 w-9 text-gray-600 sm:inline-flex"
                  aria-label="Rafraîchir"
                  aria-busy={isRefreshing}
                  disabled={isRefreshing}
                  onClick={() => void handleRefreshMessages()}
                >
                  <RefreshCw
                    className={cn(
                      "h-4 w-4",
                      isRefreshing && REFRESH_SPIN_CLASS
                    )}
                  />
                </Button>
                <DropdownMenu>
                  <DropdownMenuTrigger asChild>
                    <Button
                      type="button"
                      variant="ghost"
                      size="icon"
                      className="h-9 w-9 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                      aria-label="Plus d'actions"
                    >
                      <MoreVertical
                        className="h-[18px] w-[18px]"
                        strokeWidth={1.5}
                      />
                    </Button>
                  </DropdownMenuTrigger>
                  <DropdownMenuContent
                    align="start"
                    sideOffset={4}
                    className={cn(dropdownSurfaceClass, "min-w-[260px]")}
                  >
                    <DropdownMenuItem onSelect={markAllInViewAsRead}>
                      <MailOpen
                        className="size-[18px] text-[#5f6368]"
                        strokeWidth={1.5}
                      />
                      Tout marquer comme lu
                    </DropdownMenuItem>
                    <DropdownMenuSeparator />
                    <div
                      className="px-3 py-2 text-sm leading-snug text-[#5f6368] select-none"
                      role="note"
                    >
                      Sélectionnez des messages pour afficher plus d&apos;actions
                    </div>
                  </DropdownMenuContent>
                </DropdownMenu>
              </>
            )}
          </>
        )}

        <div className="flex-1" />

        {listToolbarMode ? mailPaginationControls("list") : null}
        {!splitView && !listToolbarMode ? mailPaginationControls("view") : null}
      </div>

      {selectedFolder === "inbox" && (
        <div className="relative z-10 w-full shrink-0 bg-mail-surface dark:bg-zinc-800 sm:dark:bg-mail-surface after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:z-0 after:h-px after:bg-border">
          {listToolbarMode &&
            (compactInboxTabs ? (
              <CompactInboxCategoryTabs
                tabs={inboxTabBarItems}
                activeTabId={activeInboxTabId}
                unseenInTabById={unseenInTabById}
                onTabClick={handleCategoryInboxTabClick}
              />
            ) : (
            <div
              className="grid w-full min-w-0 max-w-[1260px]"
              style={{
                gridTemplateColumns: `repeat(${inboxTabBarItems.length}, minmax(0, 1fr))`,
              }}
            >
              {inboxTabBarItems.map((tab) => {
                const isActive = activeInboxTabId === tab.id
                const accentColor = isActive
                  ? inboxTabActiveAccentColor(tab.id, tab.badgeColor)
                  : undefined
                const unseen = unseenInTabById[tab.id] ?? 0
                const senderLine = tabUnseenSenderLineById[tab.id] ?? ""
                const showMeta =
                  inboxTabShowsInactiveMeta(tab.id) && !isActive && unseen > 0
                const showSenderLine = showMeta && Boolean(senderLine)
                const isExpandedTabMeta = showSenderLine
                return (
                  <button
                    key={tab.id}
                    type="button"
                    aria-label={tab.label}
                    aria-current={isActive ? "true" : undefined}
                    onClick={() => handleCategoryInboxTabClick(tab.id)}
                    style={
                      accentColor
                        ? { boxShadow: `inset 0 -3px 0 0 ${accentColor}` }
                        : undefined
                    }
                    className={cn(
                      "relative z-[1] flex cursor-pointer transition-colors",
                      "min-w-0 w-full overflow-hidden max-sm:min-h-10 max-sm:items-center max-sm:justify-center",
                      "sm:min-h-14 sm:items-center sm:py-2 sm:text-left",
                      !isActive && "hover:bg-[#f1f3f4]"
                    )}
                  >
                    <>
                    <div className="flex h-10 w-full items-center justify-center sm:hidden">
                      <div className="relative inline-flex shrink-0">
                        <Icon
                          icon={tab.icon}
                          className={cn(
                            CATEGORY_TAB_ICON_CLASS,
                            MAIL_INBOX_CATEGORY_TAB_CONTENT_DARK_CLASS,
                            !isActive && "text-[#5f6368]"
                          )}
                          style={accentColor ? { color: accentColor } : undefined}
                          aria-hidden
                        />
                        {showMeta && unseen > 0 ? (
                          <span
                            className={inboxTabBadgeDotClass(tab.badgeColor)}
                            aria-hidden
                          />
                        ) : null}
                      </div>
                    </div>

                    <div className="hidden min-w-0 flex-1 items-center gap-2 mx-2 sm:mx-3 sm:flex">
                      <Icon
                        icon={tab.icon}
                        className={cn(
                          CATEGORY_TAB_ICON_CLASS,
                          "self-center",
                          MAIL_INBOX_CATEGORY_TAB_CONTENT_DARK_CLASS,
                          !isActive && "text-[#5f6368]"
                        )}
                        style={accentColor ? { color: accentColor } : undefined}
                        aria-hidden
                      />
                      <div className="flex min-w-0 w-0 flex-1 flex-col gap-px">
                        <div
                          className={cn(
                            "flex min-w-0 items-center gap-1.5",
                            isExpandedTabMeta && "min-h-5"
                          )}
                        >
                          <span
                            className={cn(
                              "min-w-0 flex-1 truncate text-[13px] font-semibold leading-tight",
                              MAIL_INBOX_CATEGORY_TAB_CONTENT_DARK_CLASS,
                              !isActive && "text-[#3c4043]"
                            )}
                            style={accentColor ? { color: accentColor } : undefined}
                          >
                            {tab.label}
                          </span>
                          {showMeta && unseen > 0 ? (
                            <span className={inboxTabBadgeCountClass(tab.badgeColor)}>
                              {unseen}
                              <span className="hidden md:inline">
                                {" "}
                                {unseen === 1 ? "nouveau" : "nouveaux"}
                              </span>
                            </span>
                          ) : null}
                        </div>
                        {isExpandedTabMeta ? (
                          <span
                            className={cn(
                              "block min-h-4 min-w-0 truncate text-[11px] leading-snug text-[#5f6368]",
                              MAIL_INBOX_CATEGORY_TAB_CONTENT_DARK_CLASS
                            )}
                          >
                            {senderLine}
                          </span>
                        ) : null}
                      </div>
                    </div>
                    </>
                  </button>
                )
              })}
            </div>
            ))}
        </div>
      )}

      {isSearchMode && searchParams && listToolbarMode && (
        <div className="flex w-full shrink-0 items-center gap-1.5 overflow-x-auto border-b border-[#dadce0] bg-mail-surface px-3 py-1.5 text-xs dark:border-gray-700">
          {/* De dropdown */}
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <button
                type="button"
                className={cn(
                  "flex shrink-0 items-center gap-1 rounded-full border px-2.5 py-1 transition-colors",
                  searchParams.from
                    ? "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
                    : "border-[#dadce0] text-[#5f6368] hover:bg-[#f1f3f4] dark:border-gray-600 dark:text-gray-400"
                )}
              >
                <UserIcon className="size-3" strokeWidth={2} />
                De{searchParams.from ? ` : ${searchParams.from}` : ""}
                <ChevronDown className="size-3" />
              </button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" className={MAIL_MENU_SURFACE_CLASS}>
              <DropdownMenuItem onSelect={() => setSearchFilter({ from: "" })}>
                N&apos;importe qui
              </DropdownMenuItem>
              <DropdownMenuItem onSelect={() => setSearchFilter({ from: searchAccount.email })}>
                De moi ({searchAccount.email})
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              {Array.from(new Set(allEmails.map((e) => e.senderEmail).filter(Boolean))).slice(0, 8).map((addr) => (
                <DropdownMenuItem key={addr} onSelect={() => setSearchFilter({ from: addr! })}>
                  {addr}
                </DropdownMenuItem>
              ))}
            </DropdownMenuContent>
          </DropdownMenu>

          {/* Date dropdown */}
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <button
                type="button"
                className={cn(
                  "flex shrink-0 items-center gap-1 rounded-full border px-2.5 py-1 transition-colors",
                  searchParams.within
                    ? "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
                    : "border-[#dadce0] text-[#5f6368] hover:bg-[#f1f3f4] dark:border-gray-600 dark:text-gray-400"
                )}
              >
                <Clock className="size-3" strokeWidth={2} />
                {searchParams.within
                  ? DATE_RANGE_OPTIONS.find((o) => o.value === searchParams.within)?.label ?? searchParams.within
                  : "Indifférente"}
                <ChevronDown className="size-3" />
              </button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" className={MAIL_MENU_SURFACE_CLASS}>
              <DropdownMenuItem onSelect={() => setSearchFilter({ within: "" })}>
                Indifférente
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              {DATE_RANGE_OPTIONS.map((opt) => (
                <DropdownMenuItem key={opt.value} onSelect={() => setSearchFilter({ within: opt.value })}>
                  {opt.label}
                </DropdownMenuItem>
              ))}
            </DropdownMenuContent>
          </DropdownMenu>

          {/* Contient une pièce jointe */}
          <button
            type="button"
            onClick={() => toggleSearchFilter("has", "attachment")}
            className={cn(
              "flex shrink-0 items-center gap-1 rounded-full border px-2.5 py-1 transition-colors",
              searchParams.has.includes("attachment")
                ? "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
                : "border-[#dadce0] text-[#5f6368] hover:bg-[#f1f3f4] dark:border-gray-600 dark:text-gray-400"
            )}
          >
            <Paperclip className="size-3" strokeWidth={2} />
            Pièces jointes
          </button>

          {/* Exclure les mises à jour d'agenda */}
          <button
            type="button"
            onClick={() => toggleSearchFilter("excludeChats", "true")}
            className={cn(
              "flex shrink-0 items-center gap-1 rounded-full border px-2.5 py-1 transition-colors",
              searchParams.excludeChats
                ? "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
                : "border-[#dadce0] text-[#5f6368] hover:bg-[#f1f3f4] dark:border-gray-600 dark:text-gray-400"
            )}
          >
            <CalendarX2 className="size-3" strokeWidth={2} />
            Exclure les mises à jour d&apos;agenda
          </button>

          {/* À dropdown */}
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <button
                type="button"
                className={cn(
                  "flex shrink-0 items-center gap-1 rounded-full border px-2.5 py-1 transition-colors",
                  searchParams.to
                    ? "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
                    : "border-[#dadce0] text-[#5f6368] hover:bg-[#f1f3f4] dark:border-gray-600 dark:text-gray-400"
                )}
              >
                <Send className="size-3" strokeWidth={2} />
                À{searchParams.to ? ` : ${searchParams.to}` : ""}
                <ChevronDown className="size-3" />
              </button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="start" className={MAIL_MENU_SURFACE_CLASS}>
              <DropdownMenuItem onSelect={() => setSearchFilter({ to: "" })}>
                N&apos;importe qui
              </DropdownMenuItem>
              <DropdownMenuItem onSelect={() => setSearchFilter({ to: searchAccount.email })}>
                À moi ({searchAccount.email})
              </DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>

          {/* Non lu */}
          <button
            type="button"
            onClick={() => {
              if (!searchParams) return
              const next = { ...searchParams }
              if (next.q.includes("is:unread")) {
                next.q = next.q.replace(/\s*is:unread\s*/g, "").trim()
              } else {
                next.q = (next.q + " is:unread").trim()
              }
              searchRouter.push(buildSearchUrl(next))
            }}
            className={cn(
              "flex shrink-0 items-center gap-1 rounded-full border px-2.5 py-1 transition-colors",
              searchParams.q.includes("is:unread")
                ? "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-700 dark:bg-blue-900/30 dark:text-blue-300"
                : "border-[#dadce0] text-[#5f6368] hover:bg-[#f1f3f4] dark:border-gray-600 dark:text-gray-400"
            )}
          >
            <MailOpen className="size-3" strokeWidth={2} />
            Non lu
          </button>

          {/* Recherche avancée */}
          <button
            type="button"
            onClick={() => setAdvancedOpen(true)}
            className="ml-auto shrink-0 px-2 py-1 text-xs font-medium text-[#1a73e8] hover:text-[#1765cc] dark:text-blue-400"
          >
            Recherche avancée
          </button>
        </div>
      )}

      <div className={cn("relative flex min-h-0 flex-1 flex-col")}>
      <div
        ref={listViewportRef}
        className={cn("max-sm:pb-16",
          !splitView && isViewMode && openEmail
            ? "relative flex min-h-0 flex-1 flex-col overflow-hidden"
            : mainScrollClass,
          "relative min-h-0 flex-1 overscroll-y-none"
        )}
      >
      {listToolbarMode && (
        <div className="pointer-events-none absolute inset-x-0 top-0 z-10 flex items-center justify-center pt-2 sm:hidden"
          style={{ height: PULL_HOLD_HEIGHT }}
          aria-hidden
        >
          <RefreshCw
            ref={pullIconRef}
            className={cn(
              "h-5 w-5 text-[#5f6368]",
              isRefreshing && REFRESH_SPIN_CLASS
            )}
            style={{ opacity: 0 }}
          />
        </div>
      )}
      <div
        ref={pullContentRef}
        className={cn(
          !splitView && isViewMode && openEmail && "relative flex min-h-0 flex-1 flex-col ",
          listToolbarMode && "max-sm:[transform:translateZ(0)]"
        )}
      >
      {!splitView && isViewMode && openEmail ? (
        /* ── EMAIL VIEW ── */
        <>
          <div className="pointer-events-none absolute inset-x-0 top-0 z-30 flex items-center justify-between gap-2 px-3 py-2 sm:hidden">
            <Button
              type="button"
              variant="ghost"
              size="icon"
              className="pointer-events-auto size-9 shrink-0 rounded-full border border-gray-200 bg-white/80 text-[#444746] shadow-md backdrop-blur hover:bg-white"
              aria-label="Retour à la boîte de réception"
              onClick={goBack}
            >
              <ChevronLeft className="size-5" strokeWidth={1.5} />
            </Button>
            <div className="pointer-events-auto flex shrink-0 overflow-hidden rounded-full border border-gray-200 bg-white/80 shadow-md backdrop-blur">
              <Button
                type="button"
                variant="ghost"
                size="icon"
                className="size-9 rounded-none text-[#444746] hover:bg-[#f1f3f4] disabled:opacity-40"
                disabled={openMailIndex <= 0}
                onClick={goToPrev}
                aria-label="Message plus récent"
              >
                <ChevronUp className="size-5" strokeWidth={1.5} />
              </Button>
              <span className="w-px shrink-0 self-stretch bg-border" aria-hidden />
              <Button
                type="button"
                variant="ghost"
                size="icon"
                className="size-9 rounded-none text-[#444746] hover:bg-[#f1f3f4] disabled:opacity-40"
                disabled={openMailIndex >= displayListEmails.length - 1}
                onClick={goToNext}
                aria-label="Message plus ancien"
              >
                <ChevronDown className="size-5" strokeWidth={1.5} />
              </Button>
            </div>
          </div>
          <EmailView
            email={openEmail}
            threadRoot={openEmailThreadRoot}
            isSingleMessageView={isSingleMessageView}
            onToggleStar={toggleStar}
            isStarred={
              starredEmails.includes(threadStoreId(openEmail)) ||
              openEmail.starred
            }
            onNavigateToLabel={handleNavigateToLabel}
            onNotSpam={openEmail.spam === true ? singleNotSpam : undefined}
            labelBgByText={listRowLabelBgByTextLower}
            emailLabelToSidebarFolderId={sidebarNav.emailLabelToSidebarFolderId}
            getNavItemPrefs={sidebarNav.getNavItemPrefs}
            folderTree={sidebarNav.folderTree}
            labelRows={sidebarNav.labelRows}
            currentFolderId={selectedFolder}
            showLabelChip={(lab) => {
              if (LABEL_PICKER_EXCLUDE.has(lab)) return true
              return mailLabelShouldShowInListStrip(
                lab,
                sidebarNav.emailLabelToSidebarFolderId,
                sidebarNav.getNavItemPrefs,
                sidebarNav.labelRows
              )
            }}
          />
        </>
      ) : (
        <TooltipProvider delayDuration={400}>
        <>
          {selectedFolder === "scheduled" && (
            <div className="flex shrink-0 items-start gap-3 border-b border-[#eceff1] bg-[#f8f9fa] px-4 py-3">
              <Clock
                className="h-5 w-5 shrink-0 text-[#5f6368]"
                strokeWidth={1.5}
                aria-hidden
              />
              <p className="text-sm leading-snug text-[#3c4043]">
                Les messages de la liste «&nbsp;Envois programmés&nbsp;» seront envoyés à l&apos;heure prévue pour chacun d&apos;eux.
              </p>
            </div>
          )}
          {displayListEmails.length === 0 ? (
            selectedFolder === "scheduled" ? (
              <div className="flex min-h-[220px] flex-col items-center justify-center px-4 py-12 text-center">
                <p className="text-sm text-[#5f6368]">Aucun message planifié.</p>
              </div>
            ) : isSearchMode && searchParams ? (
              <Empty className="min-h-[240px] flex-1 border-0 bg-mail-surface py-10 shadow-none">
                <EmptyHeader className="max-w-md">
                  <EmptyMedia
                    variant="icon"
                    className="mb-1 border-0 bg-[#f1f3f4] text-[#5f6368] [&_svg]:size-6"
                  >
                    <Search className="size-6" strokeWidth={1.5} aria-hidden />
                  </EmptyMedia>
                  <EmptyTitle className="text-[15px] font-medium text-[#3c4043]">
                    Aucun résultat
                  </EmptyTitle>
                  <EmptyDescription className="text-[13px] text-[#5f6368]">
                    Pas de résultats pour{" "}
                    <span className="font-medium text-[#3c4043]">
                      {searchParams.q || searchParams.hasWords || searchParams.from || searchParams.subject || "votre recherche"}
                    </span>
                    {(searchParams.has.length > 0 || searchParams.within || searchParams.from || searchParams.to || searchParams.subject) ? (
                      <> avec les filtres choisis</>
                    ) : null}
                    .
                  </EmptyDescription>
                </EmptyHeader>
              </Empty>
            ) : (
              <Empty className="min-h-[240px] flex-1 border-0 bg-mail-surface py-10 shadow-none">
                <EmptyHeader className="max-w-md">
                  <EmptyMedia
                    variant="icon"
                    className="mb-1 border-0 bg-[#f1f3f4] text-[#5f6368] [&_svg]:size-6"
                  >
                    <Mail className="size-6" strokeWidth={1.5} aria-hidden />
                  </EmptyMedia>
                  <EmptyTitle className="text-[15px] font-medium text-[#3c4043]">
                    Aucun message
                  </EmptyTitle>
                  <EmptyDescription className="text-[13px] text-[#5f6368]">
                    {selectedFolder === "inbox" ? (
                      <>
                        Aucun message dans l&apos;onglet{" "}
                        <span className="font-medium text-[#3c4043]">
                          {inboxCategoryTabLabel}
                        </span>{" "}
                        de la boîte de réception.
                      </>
                    ) : (
                      <>
                        Aucun message dans{" "}
                        <span className="font-medium text-[#3c4043]">
                          {getMailNavFolderLabel(selectedFolder, sidebarNav.folderIdToLabel)}
                        </span>
                        .
                      </>
                    )}
                  </EmptyDescription>
                </EmptyHeader>
              </Empty>
            )
          ) : (
          <div
            className={cn(
              MAIL_LIST_ROW_DIVIDER_CLASS,
              listToolbarMode && "sm:pb-14"
            )}
          >
            {listEmails.map((email) => {
              const rowThreadId = threadStoreId(email)
              const isStarred =
                starredEmails.includes(rowThreadId) || email.starred
              const isImportant =
                importantEmails.includes(rowThreadId) || email.important
              const isSpam = email.spam === true
              const isDraft = email.labels?.includes("drafts") === true
              const hasThreadReplyDraft =
                savedThreadReplyDrafts[rowThreadId] !== undefined
              const showDraftBadge = isDraft || hasThreadReplyDraft
              const isRead = isListRowRead(
                email,
                readOverrides,
                emailById,
                conversationMode
              )
              const senderHoverEmail = resolveSenderEmail(email.sender, email.senderEmail)
              const threadMessageCount = conversationMode
                ? getThreadMessageCount(email)
                : 0
              const senderForSearch = email.sender.replace(/\s+/g, " ").trim()
              const isSelected = selectedEmails.includes(email.id)
              const isSplitActiveRow =
                splitView && openMailId === email.id
              const hasInvitation = email.hasInvitation === true
              const parsedInvitation =
                listRowExtras.invitationById.get(email.id) ?? null
              const attachmentList =
                listRowExtras.attachmentsById.get(email.id) ?? []
              const showAttachmentPills =
                attachmentList.length > 0 && (!isMd || density === "default")
              const showListPaperclip =
                attachmentList.length > 0 && isMd && density !== "default"
              const isCompactListRow = isMd && density === "compact"
              const listRowPadTop = !showAttachmentPills
                ? isCompactListRow
                  ? "pt-0"
                  : "pt-1"
                : isCompactListRow
                  ? "pt-0"
                  : "pt-0.5"
              const isScheduled = email.labels?.includes("scheduled") === true
              const contextTargetIds = contextMenuTargetIdsForRow(
                email.id,
                selectedEmails,
                selectedFolder,
                allEmails
              )
              const allContextTargetsScheduled =
                contextTargetIds.length > 0 &&
                contextTargetIds.every((id) =>
                  listMailIndex.scheduledIds.has(id)
                )
              const scheduledCtxAnyUnread =
                allContextTargetsScheduled &&
                contextTargetIds.some((id) => {
                  const em = listMailIndex.emailById.get(id)
                  if (!em) return false
                  return !(readOverrides[id] ?? em.read)
                })
              const isRescheduleOpenThisRow =
                rescheduleTarget?.id === email.id
              const spamRowHoverNoArchive = selectedFolder === "spam"
              const snoozedFolderRow = selectedFolder === "snoozed"

              return (
              <ContextMenu
                key={email.id}
                modal={false}
                onOpenChange={(open) => {
                  if (open) {
                    rowContextMenuOpenedAtRef.current = Date.now()
                    setSelectedEmails((prev) => {
                      const next = contextMenuTargetIdsForRow(
                        email.id,
                        prev,
                        selectedFolder,
                        allEmails
                      )
                      contextMenuTargetIdsRef.current = [...next]
                      return next
                    })
                  } else {
                    setLabelPickerQuery("")
                  }
                }}
              >
                <ContextMenuTrigger asChild>
              <MailListSwipeRow
                enabled={touchListSwipeEnabled}
                emailId={email.id}
                isOpen={openSwipeRowId === email.id}
                onOpenChange={(open) => {
                  if (open) setOpenSwipeRowId(email.id)
                  else if (openSwipeRowId === email.id) setOpenSwipeRowId(null)
                }}
                onArchive={() => archiveListRow(email)}
                onDelete={() => deleteListRow(email)}
                onStar={() => toggleStar(email.id)}
                onLabel={() => openSwipeRowLabelSheet(email.id)}
              >
              <div
                data-email-row-id={email.id}
                data-split-active={isSplitActiveRow ? "" : undefined}
                aria-current={isSplitActiveRow ? "true" : undefined}
                draggable={!isXs}
                onDragStart={isXs ? undefined : (e) => startRowDrag(email.id, e)}
                onClick={() => {
                  if (readXsMatches() && mobileSelectionMode) {
                    toggleSelect(email.id)
                    lastSelectionAnchorIdRef.current = email.id
                    return
                  }
                  handleRowActivate(email)
                }}
                className={cn(
                  "group relative z-0 w-full cursor-pointer pl-3 pr-2 py-2 transition-[background-color,box-shadow] duration-[50ms] ease-out",
                  !splitView &&
                    "md:flex md:gap-2 md:px-2 md:py-1.5",
                  !splitView &&
                    (isCompactListRow && !showAttachmentPills
                      ? "md:items-center"
                      : "md:items-start"),
                  isCompactListRow && "md:!py-1 md:text-[13px]",
                  isSplitActiveRow
                    ? "z-[1] bg-mail-row-active-split shadow-[inset_3px_0_0_0_#669df6]"
                    : isSelected
                      ? "bg-mail-row-selected"
                      : isRead
                        ? "bg-mail-row-read"
                        : "bg-mail-row-unread",
                  !isSplitActiveRow &&
                    "hover:z-1 hover:shadow-[inset_1px_0_0_#d2d5da,inset_-1px_0_0_#d2d5da,0_4px_10px_-3px_rgba(60,64,67,.16),0_2px_5px_0_rgba(60,64,67,.09)]"
                )}
              >
                {/* Compact < md */}
                <div
                  className={cn(
                    "flex w-full min-w-0 flex-col gap-0.5",
                    !splitView && "md:hidden",
                    mobileSelectionMode &&
                      "max-sm:flex-row max-sm:items-center max-sm:gap-2"
                  )}
                >
                  {mobileSelectionMode && (
                    <div
                      className="flex shrink-0 self-center sm:hidden"
                      onClick={(e) => e.stopPropagation()}
                      onClickCapture={(e) => handleRowCheckboxClickCapture(email.id, e)}
                    >
                      <Checkbox
                        className={listRowCheckboxClass(true)}
                        checked={isSelected}
                        onCheckedChange={() => {
                          toggleSelect(email.id)
                          lastSelectionAnchorIdRef.current = email.id
                        }}
                      />
                    </div>
                  )}
                  <div
                    className={cn(
                      "flex min-w-0 flex-1 flex-col gap-0.5",
                      mobileSelectionMode && "max-sm:pointer-events-none"
                    )}
                    data-selectable-text
                  >
                  <div className="flex w-full min-w-0 items-center gap-2">
                    <div
                      className="hidden shrink-0 items-center sm:flex"
                      onClick={(e) => e.stopPropagation()}
                      onClickCapture={(e) => handleRowCheckboxClickCapture(email.id, e)}
                    >
                      <Checkbox
                        className={listRowCheckboxClass(false)}
                        checked={isSelected}
                        onCheckedChange={() => {
                          toggleSelect(email.id)
                          lastSelectionAnchorIdRef.current = email.id
                        }}
                      />
                    </div>
                    <div className="flex min-w-0 flex-1 items-center justify-between gap-2">
                      <div className="flex min-w-0 flex-1 items-center gap-1">
                        <button
                          type="button"
                          onClick={(e) => {
                            e.stopPropagation()
                            toggleImportant(email.id)
                          }}
                          className={cn(
                            "flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-full",
                            isSpam
                              ? "text-[#d93025] hover:bg-[#d93025]/10 hover:text-[#b3261e]"
                              : "text-[#c2c2c2] hover:bg-black/4 hover:text-[#5f6368]"
                          )}
                          aria-label={
                            isSpam
                              ? "Marqué comme spam"
                              : isImportant
                                ? "Retirer important"
                                : "Marquer important"
                          }
                        >
                          <Icon
                            icon={importantSignalIcon(isSpam, isImportant)}
                            className={cn(
                              "size-4 shrink-0",
                              isSpam && "text-[#d93025]",
                              !isSpam &&
                                (isImportant ? "text-[#f4cc70]" : "text-[#c2c2c2]")
                            )}
                            aria-hidden
                          />
                        </button>
                        {isScheduled && (
                          <span
                            className="flex h-7 w-6 shrink-0 items-center justify-center text-[#5f6368]"
                            aria-hidden
                          >
                            <Send className="size-3.5" strokeWidth={2} />
                          </span>
                        )}
                    {isScheduled ? (
                      <span
                        className={cn(
                          "min-w-0 truncate text-sm",
                          !isRead ? "font-semibold text-gray-900" : "font-normal text-gray-700"
                        )}
                      >
                        À : {email.scheduledToName ?? email.sender}
                      </span>
                    ) : (
                    <ContactHoverCard displayName={email.sender} email={senderHoverEmail}>
                      <span
                        className={cn(
                          "min-w-0 truncate text-sm",
                          !isRead ? "font-semibold text-gray-900" : "font-normal text-gray-700"
                        )}
                      >
                        {showDraftBadge && (
                          <span className="font-medium text-[#d93025]">Brouillon </span>
                        )}
                        {email.sender}
                      </span>
                    </ContactHoverCard>
                    )}
                        {threadMessageCount > 1 && (
                          <span className="shrink-0 text-sm font-normal text-gray-500">
                            {threadMessageCount}
                          </span>
                        )}
                      </div>
                      <div className="flex shrink-0 items-center gap-1">
                        {(parsedInvitation || hasInvitation) && (
                          <Icon
                            icon={
                              parsedInvitation
                                ? VIDEO_CONFERENCE_LOGOS[
                                    parsedInvitation.conferenceProvider
                                  ]
                                : "mdi:calendar"
                            }
                            className="size-4 shrink-0 text-[#5f6368]"
                            aria-label={
                              parsedInvitation
                                ? "Invitation visioconférence"
                                : "Invitation calendrier"
                            }
                          />
                        )}
                        {attachmentList.length > 0 && (
                          <Paperclip
                            className="size-4 shrink-0 text-[#5f6368]"
                            strokeWidth={1.75}
                            aria-label="Pièces jointes"
                          />
                        )}
                        {listRowExtras.categoryTabsById.get(email.id) ? (
                          <MailInboxCategoryTabIcons
                            tabs={listRowExtras.categoryTabsById.get(email.id)!}
                            onTabClick={handleCategoryInboxTabClick}
                          />
                        ) : null}
                        <span
                          className={cn(
                            "shrink-0 text-sm font-semibold tabular-nums tracking-tight",
                            !isRead ? "text-gray-900" : "text-gray-700"
                          )}
                        >
                          {isScheduled ? (
                            formatScheduledDateTimeDisplay(email.scheduledSendAt)
                          ) : (
                            <MailDateText iso={email.date} variant="list" />
                          )}
                        </span>
                      </div>
                    </div>
                  </div>

                  <div className={cn("flex min-w-0 flex-wrap items-center gap-1 sm:pl-6")}>
                      {email.tag && (
                        <span className="shrink-0 rounded bg-gray-200 px-1.5 py-0.5 text-xs text-gray-600 opacity-[0.92]">
                          {email.tag}
                        </span>
                      )}
                      <MailLabelPillStrip
                        variant="list"
                        labels={email.labels}
                        labelBgByText={listRowLabelBgByTextLower}
                        emailLabelToSidebarFolderId={sidebarNav.emailLabelToSidebarFolderId}
                        getNavItemPrefs={sidebarNav.getNavItemPrefs}
                        labelRows={sidebarNav.labelRows}
                        onLabelNavigate={handleNavigateToLabel}
                        currentFolderId={selectedFolder}
                        folderTree={sidebarNav.folderTree}
                      />
                      <span
                        className={cn(
                          "min-w-0 flex-1 text-sm leading-snug line-clamp-1",
                          !isRead ? "font-semibold text-gray-900" : "font-semibold text-[#202124]"
                        )}
                      >
                        {email.subject}
                      </span>
                    </div>

                    <div className={cn("flex min-w-0 items-start gap-1.5 sm:pl-6")}>
                      <p className="min-w-0 flex-1 text-sm leading-snug text-[#5f6368] line-clamp-1">
                        {email.preview}
                      </p>
                      <button
                        type="button"
                        onClick={(e) => {
                          e.stopPropagation()
                          toggleStar(email.id)
                        }}
                        className="mt-0.5 flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-full text-[#c2c2c2] hover:bg-black/4 hover:text-[#5f6368]"
                        aria-label={isStarred ? "Retirer des favoris" : "Marquer comme favori"}
                      >
                        <Star
                          strokeWidth={isStarred ? 0 : 1.25}
                          className={cn(
                            "size-4",
                            isStarred
                              ? "fill-[#f4cc70] stroke-none text-[#f4cc70]"
                              : "fill-transparent stroke-[#c2c2c2]"
                          )}
                        />
                      </button>
                    </div>
                  </div>
                </div>

                {/* Desktop >= md */}
                <div
                  className={cn(
                    "hidden w-full items-start gap-2",
                    !splitView && "md:flex"
                  )}
                >
                <div className="flex shrink-0 items-center gap-2">
                  <div
                    className="flex shrink-0"
                    onClick={(e) => e.stopPropagation()}
                    onClickCapture={(e) => handleRowCheckboxClickCapture(email.id, e)}
                  >
                    <Checkbox
                      className="size-4 min-h-4 min-w-4 shrink-0 rounded-[2.5px] border-[1.5px] border-[#c2c2c2] bg-transparent shadow-none dark:bg-transparent focus-visible:ring-[#c2c2c2]/30 data-[state=checked]:border-[#1a73e8] data-[state=checked]:bg-[#1a73e8] data-[state=checked]:text-white"
                      checked={isSelected}
                      onCheckedChange={() => {
                        toggleSelect(email.id)
                        lastSelectionAnchorIdRef.current = email.id
                      }}
                    />
                  </div>

                  <div className="flex items-center gap-0">
                    <button
                      type="button"
                      onClick={(e) => {
                        e.stopPropagation()
                        toggleStar(email.id)
                      }}
                      className="flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-full text-[#c2c2c2] hover:bg-black/4 hover:text-[#5f6368]"
                      aria-label={isStarred ? "Retirer des favoris" : "Marquer comme favori"}
                    >
                      <Star
                        strokeWidth={isStarred ? 0 : 1.25}
                        className={cn(
                          "size-4",
                          isStarred
                            ? "fill-[#f4cc70] stroke-none text-[#f4cc70]"
                            : "fill-transparent stroke-[#c2c2c2]"
                        )}
                      />
                    </button>

                    <button
                      type="button"
                      onClick={(e) => {
                        e.stopPropagation()
                        toggleImportant(email.id)
                      }}
                      className={cn(
                        "flex h-7 w-7 shrink-0 cursor-pointer items-center justify-center rounded-full",
                        isSpam
                          ? "text-[#d93025] hover:bg-[#d93025]/10 hover:text-[#b3261e]"
                          : "text-[#c2c2c2] hover:bg-black/4 hover:text-[#5f6368]"
                      )}
                      aria-label={
                        isSpam
                          ? "Marqué comme spam"
                          : isImportant
                            ? "Retirer important"
                            : "Marquer important"
                      }
                    >
                      <Icon
                        icon={importantSignalIcon(isSpam, isImportant)}
                        className={cn(
                          "size-4 shrink-0",
                          isSpam && "text-[#d93025]",
                          !isSpam &&
                            (isImportant ? "text-[#f4cc70]" : "text-[#c2c2c2]")
                        )}
                        aria-hidden
                      />
                    </button>
                    {isScheduled && (
                      <span
                        className="flex h-7 w-6 shrink-0 items-center justify-center text-[#5f6368]"
                        aria-hidden
                      >
                        <Send className="size-3.5" strokeWidth={2} />
                      </span>
                    )}
                  </div>
                </div>

                <div
                  className={cn(
                    "w-44 shrink-0 truncate pl-2 lg:w-40",
                    listRowPadTop,
                    isCompactListRow &&
                      "flex min-h-7 items-center leading-tight"
                  )}
                  data-selectable-text
                >
                  {isScheduled ? (
                    <span
                      className={cn(
                        "text-sm",
                        !isRead ? "font-semibold text-gray-900" : "text-gray-700"
                      )}
                    >
                      À : {email.scheduledToName ?? email.sender}
                    </span>
                  ) : (
                  <ContactHoverCard displayName={email.sender} email={senderHoverEmail}>
                    <span className={cn(
                      "text-sm",
                      !isRead ? "font-semibold text-gray-900" : "text-gray-700"
                    )}>
                      {showDraftBadge && (
                        <span className="font-medium text-[#d93025]">Brouillon </span>
                      )}
                      {email.sender}
                    </span>
                  </ContactHoverCard>
                  )}
                  {threadMessageCount > 1 && (
                    <span className="text-sm text-gray-500 ml-1">
                      {threadMessageCount}
                    </span>
                  )}
                </div>

                <div
                  className="flex min-w-0 flex-1 flex-col justify-start gap-0.5 px-2 pb-0.5"
                  data-selectable-text
                >
                  <div
                    className={cn(
                      "flex min-w-0 items-center gap-1",
                      listRowPadTop,
                      isCompactListRow && "leading-tight"
                    )}
                  >
                    {email.tag && (
                      <span className="shrink-0 rounded bg-gray-200 px-1.5 py-0.5 text-xs text-gray-600 opacity-[0.92]">
                        {email.tag}
                      </span>
                    )}
                    <MailLabelPillStrip
                      variant="list"
                      labels={email.labels}
                      labelBgByText={listRowLabelBgByTextLower}
                      emailLabelToSidebarFolderId={sidebarNav.emailLabelToSidebarFolderId}
                      getNavItemPrefs={sidebarNav.getNavItemPrefs}
                      labelRows={sidebarNav.labelRows}
                      onLabelNavigate={handleNavigateToLabel}
                      currentFolderId={selectedFolder}
                      folderTree={sidebarNav.folderTree}
                    />
                    <span
                      className={cn(
                        "min-w-0 shrink truncate text-sm",
                        !isRead ? "font-semibold text-gray-900" : "font-normal text-[#3c4043]"
                      )}
                    >
                      {email.subject}
                    </span>
                    <span className="min-w-0 flex-1 truncate text-sm text-gray-500">{email.preview}</span>
                  </div>
                  {showAttachmentPills && (
                    <EmailListAttachmentRow emailId={email.id} attachments={attachmentList} />
                  )}
                </div>

                <div
                  className={cn(
                    "flex shrink-0 flex-col items-end gap-1 pr-2 text-right md:max-w-[150px] md:min-w-0",
                    listRowPadTop,
                    isCompactListRow && !showAttachmentPills
                      ? "self-center"
                      : "self-start"
                  )}
                >
                  {isScheduled ? (
                    <div className="relative flex w-full min-w-0 shrink-0 items-center justify-end">
                      <span
                        className={cn(
                          "block max-w-full truncate text-sm font-semibold tabular-nums text-[#c65308]",
                          "transition-opacity duration-[50ms] ease-out",
                          isRescheduleOpenThisRow
                            ? "opacity-0"
                            : "opacity-100 group-hover:opacity-0"
                        )}
                      >
                        {formatScheduledDateTimeDisplay(email.scheduledSendAt)}
                      </span>
                      <div
                        className={cn(
                          "pointer-events-none absolute right-0 top-1/2 z-[1] flex w-max -translate-y-1/2 flex-nowrap items-center gap-0.5 rounded-md py-0.5 pl-1 opacity-0 transition-opacity duration-[50ms] ease-out",
                          listRowQuickHoverTrayToneClass(isSelected, isRead),
                          isRescheduleOpenThisRow
                            ? "pointer-events-auto opacity-100"
                            : "group-hover:pointer-events-auto group-hover:opacity-100"
                        )}
                      >
                        {!spamRowHoverNoArchive && (
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label="Archiver"
                              onClick={(e) => {
                                e.stopPropagation()
                                void requestArchiveScheduled(email.id)
                              }}
                            >
                              <Archive className="h-[18px] w-[18px]" strokeWidth={1.5} />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            Archiver
                          </TooltipContent>
                        </Tooltip>
                        )}
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label="Supprimer"
                              onClick={(e) => {
                                e.stopPropagation()
                                void requestDeleteScheduled(email.id)
                              }}
                            >
                              <Trash2 className="h-[18px] w-[18px]" strokeWidth={1.5} />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            Supprimer
                          </TooltipContent>
                        </Tooltip>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label={isRead ? "Marquer comme non lu" : "Marquer comme lu"}
                              onClick={(e) => {
                                e.stopPropagation()
                                const next = !isRead
                                setReadOverrides((prev) => ({ ...prev, [email.id]: next }))
                                void requestToggleReadScheduled(email.id, next)
                              }}
                            >
                              {isRead ? (
                                <Mail className="h-[18px] w-[18px]" strokeWidth={1.5} />
                              ) : (
                                <MailOpen className="h-[18px] w-[18px]" strokeWidth={1.5} />
                              )}
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            {isRead ? "Marquer comme non lu" : "Marquer comme lu"}
                          </TooltipContent>
                        </Tooltip>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label="Mettre en attente"
                              onClick={(e) => {
                                e.stopPropagation()
                                void requestSnoozeScheduled(email.id)
                              }}
                            >
                              <Clock className="h-[18px] w-[18px]" strokeWidth={1.5} />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            Mettre en attente
                          </TooltipContent>
                        </Tooltip>
                        <Popover
                          open={
                            rescheduleTarget?.id === email.id &&
                            rescheduleTarget.panelOpen
                          }
                          onOpenChange={(open) => {
                            if (open) {
                              const pending =
                                rescheduleDismissTimeoutsRef.current.get(
                                  email.id
                                )
                              if (pending) {
                                clearTimeout(pending)
                                rescheduleDismissTimeoutsRef.current.delete(
                                  email.id
                                )
                              }
                              setRescheduleTarget({
                                id: email.id,
                                value: scheduledIsoToDatetimeLocalValue(
                                  email.scheduledSendAt
                                ),
                                panelOpen: true,
                              })
                            } else {
                              setRescheduleTarget((prev) =>
                                prev?.id === email.id
                                  ? { ...prev, panelOpen: false }
                                  : prev
                              )
                              scheduleReschedulePopoverDismiss(email.id)
                            }
                          }}
                        >
                          <Tooltip>
                            <TooltipTrigger asChild>
                              <PopoverTrigger asChild>
                                <Button
                                  type="button"
                                  variant="ghost"
                                  size="icon"
                                  className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                                  aria-label="Reprogrammer"
                                  onClick={(e) => e.stopPropagation()}
                                >
                                  <CalendarClock
                                    className="h-[18px] w-[18px]"
                                    strokeWidth={1.5}
                                  />
                                </Button>
                              </PopoverTrigger>
                            </TooltipTrigger>
                            <TooltipContent side="bottom" className="text-xs">
                              Reprogrammer
                            </TooltipContent>
                          </Tooltip>
                          <PopoverContent
                            className="w-[min(100vw-2rem,280px)] p-3"
                            align="end"
                            side="bottom"
                            sideOffset={6}
                            collisionPadding={12}
                            onClick={(e) => e.stopPropagation()}
                          >
                            <p className="mb-2 text-xs font-medium text-[#3c4043]">
                              Nouvelle date d&apos;envoi
                            </p>
                            <input
                              type="datetime-local"
                              className="mb-3 w-full rounded border border-[#dadce0] px-2 py-1.5 text-sm text-[#3c4043]"
                              value={
                                rescheduleTarget?.id === email.id
                                  ? rescheduleTarget.value
                                  : ""
                              }
                              onChange={(e) =>
                                setRescheduleTarget((prev) =>
                                  prev?.id === email.id
                                    ? {
                                        ...prev,
                                        value: e.target.value,
                                        panelOpen: true,
                                      }
                                    : prev
                                )
                              }
                            />
                            <div className="flex justify-end gap-2">
                              <Button
                                type="button"
                                variant="outline"
                                size="sm"
                                className="h-8 text-xs"
                                onClick={() => {
                                  setRescheduleTarget((prev) =>
                                    prev?.id === email.id
                                      ? { ...prev, panelOpen: false }
                                      : prev
                                  )
                                  scheduleReschedulePopoverDismiss(email.id)
                                }}
                              >
                                Annuler
                              </Button>
                              <Button
                                type="button"
                                size="sm"
                                className="h-8 text-xs"
                                onClick={() => {
                                  if (rescheduleTarget?.id !== email.id) return
                                  const iso = parseDatetimeLocalToIso(
                                    rescheduleTarget.value
                                  )
                                  if (!iso) return
                                  void requestRescheduleScheduled(email.id, iso)
                                  setRescheduleTarget((prev) =>
                                    prev?.id === email.id
                                      ? { ...prev, panelOpen: false }
                                      : prev
                                  )
                                  scheduleReschedulePopoverDismiss(email.id)
                                }}
                              >
                                Valider
                              </Button>
                            </div>
                          </PopoverContent>
                        </Popover>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label="Modifier le mail"
                              onClick={(e) => {
                                e.stopPropagation()
                                void handleEditScheduledMail(email.id)
                              }}
                            >
                              <Pencil
                                className="h-[18px] w-[18px]"
                                strokeWidth={1.5}
                              />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            Modifier le mail
                          </TooltipContent>
                        </Tooltip>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label="Envoyer maintenant"
                              onClick={(e) => {
                                e.stopPropagation()
                                void requestSendScheduledNow(email.id)
                              }}
                            >
                              <Send
                                className="h-[18px] w-[18px]"
                                strokeWidth={1.5}
                              />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            Envoyer maintenant
                          </TooltipContent>
                        </Tooltip>
                      </div>
                    </div>
                  ) : (
                    <div className="relative flex w-full min-w-0 shrink-0 items-center justify-end">
                      <div
                        className={cn(
                          "flex min-w-0 max-w-full items-center justify-end gap-1.5 overflow-hidden",
                          "transition-opacity duration-[50ms] ease-out",
                          "group-hover:opacity-0"
                        )}
                      >
                        {(parsedInvitation || hasInvitation) && (
                          <Icon
                            icon={
                              parsedInvitation
                                ? VIDEO_CONFERENCE_LOGOS[
                                    parsedInvitation.conferenceProvider
                                  ]
                                : "mdi:calendar"
                            }
                            className="size-[18px] shrink-0 text-[#5f6368]"
                            aria-label={
                              parsedInvitation
                                ? "Invitation visioconférence"
                                : "Invitation calendrier"
                            }
                          />
                        )}
                        {listRowExtras.categoryTabsById.get(email.id) ? (
                          <MailInboxCategoryTabIcons
                            tabs={listRowExtras.categoryTabsById.get(email.id)!}
                            onTabClick={handleCategoryInboxTabClick}
                            iconClassName="size-[18px] shrink-0"
                          />
                        ) : null}
                        {showListPaperclip && (
                          <Paperclip
                            className="size-[18px] shrink-0 text-[#5f6368]"
                            strokeWidth={1.75}
                            aria-label="Pièces jointes"
                          />
                        )}
                        <span
                          className={cn(
                            "min-w-0 truncate text-sm tabular-nums",
                            !isRead ? "font-semibold text-gray-900" : "text-gray-600"
                          )}
                        >
                          <MailDateText iso={email.date} variant="list" />
                        </span>
                      </div>
                      <div
                        className={cn(
                          "pointer-events-none absolute right-0 top-1/2 z-[1] flex w-max -translate-y-1/2 flex-nowrap items-center gap-0.5 rounded-md py-0.5 pl-1 opacity-0 transition-opacity duration-[50ms] ease-out",
                          listRowQuickHoverTrayToneClass(isSelected, isRead),
                          "group-hover:pointer-events-auto group-hover:opacity-100"
                        )}
                      >
                        {!spamRowHoverNoArchive && (
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label="Archiver"
                              onClick={(e) => {
                                e.stopPropagation()
                                mailActions.hideEmail(email.id)
                                closeViewIfShowingEmail(email.id)
                              }}
                            >
                              <Archive className="h-[18px] w-[18px]" strokeWidth={1.5} />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            Archiver
                          </TooltipContent>
                        </Tooltip>
                        )}
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label="Supprimer"
                              onClick={(e) => {
                                e.stopPropagation()
                                mailActions.hideEmail(email.id)
                                closeViewIfShowingEmail(email.id)
                              }}
                            >
                              <Trash2 className="h-[18px] w-[18px]" strokeWidth={1.5} />
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            Supprimer
                          </TooltipContent>
                        </Tooltip>
                        <Tooltip>
                          <TooltipTrigger asChild>
                            <Button
                              type="button"
                              variant="ghost"
                              size="icon"
                              className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                              aria-label={isRead ? "Marquer comme non lu" : "Marquer comme lu"}
                              onClick={(e) => {
                                e.stopPropagation()
                                const next = !isRead
                                setReadOverrides((prev) => ({ ...prev, [email.id]: next }))
                              }}
                            >
                              {isRead ? (
                                <Mail className="h-[18px] w-[18px]" strokeWidth={1.5} />
                              ) : (
                                <MailOpen className="h-[18px] w-[18px]" strokeWidth={1.5} />
                              )}
                            </Button>
                          </TooltipTrigger>
                          <TooltipContent side="bottom" className="text-xs">
                            {isRead ? "Marquer comme non lu" : "Marquer comme lu"}
                          </TooltipContent>
                        </Tooltip>
                        {spamRowHoverNoArchive && (
                          <Tooltip>
                            <TooltipTrigger asChild>
                              <Button
                                type="button"
                                variant="ghost"
                                size="icon"
                                className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                                aria-label="Déplacer vers la boîte de réception"
                                onClick={(e) => {
                                  e.stopPropagation()
                                  mailActions.markNotSpam(email.id)
                                  onSelectFolder?.("inbox")
                                  closeViewIfShowingEmail(email.id)
                                }}
                              >
                                <InboxIcon
                                  className="h-[18px] w-[18px]"
                                  strokeWidth={1.5}
                                />
                              </Button>
                            </TooltipTrigger>
                            <TooltipContent side="bottom" className="text-xs">
                              Boîte de réception
                            </TooltipContent>
                          </Tooltip>
                        )}
                        {!spamRowHoverNoArchive &&
                          (snoozedFolderRow ? (
                          <Tooltip>
                            <TooltipTrigger asChild>
                              <Button
                                type="button"
                                variant="ghost"
                                size="icon"
                                className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                                aria-label={
                                  email.id.startsWith("snz-")
                                    ? "Déplacer vers la boîte de réception"
                                    : "Remettre dans les mails planifiés"
                                }
                                onClick={(e) => {
                                  e.stopPropagation()
                                  restoreSnoozedRowToMailbox(email)
                                }}
                              >
                                <InboxIcon
                                  className="h-[18px] w-[18px]"
                                  strokeWidth={1.5}
                                />
                              </Button>
                            </TooltipTrigger>
                            <TooltipContent side="bottom" className="text-xs">
                              {email.id.startsWith("snz-")
                                ? "Boîte de réception"
                                : "Planifiés"}
                            </TooltipContent>
                          </Tooltip>
                        ) : (
                          <Tooltip>
                            <TooltipTrigger asChild>
                              <Button
                                type="button"
                                variant="ghost"
                                size="icon"
                                className="h-8 w-8 shrink-0 text-[#444746] hover:bg-[#f1f3f4]"
                                aria-label="Mettre en attente"
                                onClick={(e) => {
                                  e.stopPropagation()
                                  void requestSnoozeMailboxEmail(email)
                                  if (email.labels?.includes("snoozed")) return
                                  mailActions.hideEmail(email.id)
                                  closeViewIfShowingEmail(email.id)
                                }}
                              >
                                <Clock className="h-[18px] w-[18px]" strokeWidth={1.5} />
                              </Button>
                            </TooltipTrigger>
                            <TooltipContent side="bottom" className="text-xs">
                              Mettre en attente
                            </TooltipContent>
                          </Tooltip>
                        ))}
                      </div>
                    </div>
                  )}
                </div>
                </div>
              </div>
              </MailListSwipeRow>
                </ContextMenuTrigger>

                <ContextMenuContent
                  onCloseAutoFocus={(e) => e.preventDefault()}
                  onPointerDownOutside={(event) => {
                    const native = event.detail.originalEvent
                    if (
                      native.pointerType === "mouse" &&
                      native.button === 2 &&
                      Date.now() - rowContextMenuOpenedAtRef.current < 450
                    ) {
                      event.preventDefault()
                    }
                  }}
                  className={cn(
                    cn(MAIL_MENU_SURFACE_WIDE_CLASS, "overflow-visible"),
                    "[&_[data-slot=context-menu-item]]:gap-3 [&_[data-slot=context-menu-item]]:rounded-none [&_[data-slot=context-menu-item]]:px-3 [&_[data-slot=context-menu-item]]:py-2 [&_[data-slot=context-menu-item]]:text-sm",
                    "[&_[data-slot=context-menu-item]:focus]:bg-[#f1f3f4] [&_[data-slot=context-menu-item]:focus]:text-[#3c4043]",
                    "[&_[data-slot=context-menu-sub-trigger]]:gap-3 [&_[data-slot=context-menu-sub-trigger]]:rounded-none [&_[data-slot=context-menu-sub-trigger]]:px-3 [&_[data-slot=context-menu-sub-trigger]]:py-2 [&_[data-slot=context-menu-sub-trigger]]:text-sm",
                    "[&_[data-slot=context-menu-sub-trigger]:focus]:bg-[#f1f3f4] [&_[data-slot=context-menu-sub-trigger]:focus]:text-[#3c4043]",
                    "[&_[data-slot=context-menu-separator]]:mx-0 [&_[data-slot=context-menu-separator]]:my-1 [&_[data-slot=context-menu-separator]]:h-px [&_[data-slot=context-menu-separator]]:bg-[#eceff1]",
                    "[&_[data-slot=context-menu-sub-content]]:min-w-[200px] [&_[data-slot=context-menu-sub-content]]:rounded-lg [&_[data-slot=context-menu-sub-content]]:border [&_[data-slot=context-menu-sub-content]]:border-border [&_[data-slot=context-menu-sub-content]]:bg-popover [&_[data-slot=context-menu-sub-content]]:shadow-lg"
                  )}
                >
                  {allContextTargetsScheduled ? (
                    <>
                      <ContextMenuItem
                        onSelect={() => {
                          const ids = [...contextMenuTargetIdsRef.current]
                          void Promise.all(
                            ids.map((id) => requestArchiveScheduled(id))
                          )
                        }}
                      >
                        <Archive
                          strokeWidth={1.5}
                          className="size-[18px] text-[#5f6368]"
                        />
                        Archiver
                      </ContextMenuItem>
                      <ContextMenuItem
                        onSelect={() => {
                          const ids = [...contextMenuTargetIdsRef.current]
                          void Promise.all(
                            ids.map((id) => requestDeleteScheduled(id))
                          )
                        }}
                      >
                        <Trash2
                          strokeWidth={1.5}
                          className="size-[18px] text-[#5f6368]"
                        />
                        Supprimer
                      </ContextMenuItem>
                      <ContextMenuItem
                        onSelect={() => {
                          const ids = [...contextMenuTargetIdsRef.current]
                          const markRead = scheduledCtxAnyUnread
                          setReadOverrides((prev) => {
                            const next = { ...prev }
                            for (const id of ids) next[id] = markRead
                            return next
                          })
                          void Promise.all(
                            ids.map((id) =>
                              requestToggleReadScheduled(id, markRead)
                            )
                          )
                        }}
                      >
                        {scheduledCtxAnyUnread ? (
                          <Mail
                            strokeWidth={1.5}
                            className="size-[18px] text-[#5f6368]"
                          />
                        ) : (
                          <MailOpen
                            strokeWidth={1.5}
                            className="size-[18px] text-[#5f6368]"
                          />
                        )}
                        {scheduledCtxAnyUnread
                          ? "Marquer comme lu"
                          : "Marquer comme non lu"}
                      </ContextMenuItem>
                      <ContextMenuItem
                        onSelect={() => {
                          const ids = [...contextMenuTargetIdsRef.current]
                          void Promise.all(
                            ids.map((id) => requestSnoozeScheduled(id))
                          )
                        }}
                      >
                        <Clock
                          strokeWidth={1.5}
                          className="size-[18px] text-[#5f6368]"
                        />
                        Mettre en attente
                      </ContextMenuItem>
                      <ContextMenuSeparator />
                      <ContextMenuSub
                        onOpenChange={(subOpen) => {
                          if (!subOpen) return
                          const ids = contextMenuTargetIdsRef.current
                          const first = allEmails.find((e) => e.id === ids[0])
                          setCmScheduledRescheduleValue(
                            scheduledIsoToDatetimeLocalValue(
                              first?.scheduledSendAt
                            )
                          )
                        }}
                      >
                        <ContextMenuSubTrigger className="[&>svg:last-child]:text-[#5f6368]">
                          <CalendarClock
                            strokeWidth={1.5}
                            className="size-[18px] text-[#5f6368]"
                          />
                          Reprogrammer
                        </ContextMenuSubTrigger>
                        <ContextMenuSubContent
                          className={cn(
                            "min-w-[288px] rounded-lg border border-border bg-popover px-4 py-3.5 text-[#3c4043] shadow-lg"
                          )}
                        >
                          <div
                            className="flex flex-col gap-3.5"
                            onPointerDown={(e) => e.stopPropagation()}
                          >
                            <p className="text-xs font-medium leading-snug text-[#3c4043]">
                              Nouvelle date d&apos;envoi
                              {contextTargetIds.length > 1
                                ? ` (${contextTargetIds.length} messages)`
                                : null}
                            </p>
                            <input
                              type="datetime-local"
                              className="w-full rounded border border-[#dadce0] px-2.5 py-1.5 text-sm text-[#3c4043]"
                              value={cmScheduledRescheduleValue}
                              onChange={(e) =>
                                setCmScheduledRescheduleValue(e.target.value)
                              }
                              onPointerDown={(e) => e.stopPropagation()}
                            />
                            <button
                              type="button"
                              className="rounded bg-[#0b57d0] px-3 py-1.5 text-xs font-medium text-white hover:bg-[#0842a0]"
                              onPointerDown={(e) => {
                                e.stopPropagation()
                                e.preventDefault()
                              }}
                              onClick={(e) => {
                                e.stopPropagation()
                                const iso = parseDatetimeLocalToIso(
                                  cmScheduledRescheduleValue
                                )
                                if (!iso) return
                                const ids = [
                                  ...contextMenuTargetIdsRef.current,
                                ]
                                void Promise.all(
                                  ids.map((id) =>
                                    requestRescheduleScheduled(id, iso)
                                  )
                                )
                              }}
                            >
                              Valider
                            </button>
                          </div>
                        </ContextMenuSubContent>
                      </ContextMenuSub>
                      <ContextMenuItem
                        disabled={contextTargetIds.length > 1}
                        onSelect={() => {
                          if (contextTargetIds.length !== 1) return
                          void handleEditScheduledMail(contextTargetIds[0]!)
                        }}
                      >
                        <Pencil
                          strokeWidth={1.5}
                          className="size-[18px] text-[#5f6368]"
                        />
                        Modifier le mail
                      </ContextMenuItem>
                      <ContextMenuItem
                        onSelect={() => {
                          const ids = [...contextMenuTargetIdsRef.current]
                          void Promise.all(
                            ids.map((id) => requestSendScheduledNow(id))
                          )
                        }}
                      >
                        <Send
                          strokeWidth={1.5}
                          className="size-[18px] text-[#5f6368]"
                        />
                        Envoyer maintenant
                      </ContextMenuItem>
                    </>
                  ) : (
                    <>
                  <ContextMenuItem>
                    <Reply strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Répondre
                  </ContextMenuItem>
                  <ContextMenuItem>
                    <ReplyAll strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Répondre à tous
                  </ContextMenuItem>
                  <ContextMenuItem>
                    <Forward strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Transférer
                  </ContextMenuItem>
                  <ContextMenuItem>
                    <Paperclip strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Transférer en tant que pièce jointe
                  </ContextMenuItem>

                  <ContextMenuSeparator />

                  <ContextMenuItem>
                    <Archive strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Archiver
                  </ContextMenuItem>
                  <ContextMenuItem>
                    <Trash2 strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Supprimer
                  </ContextMenuItem>
                  <ContextMenuItem
                    onSelect={() => {
                      const newRead = !isRead
                      const ids = contextMenuTargetIdsRef.current
                      setReadOverrides((prev) => {
                        const next = { ...prev }
                        for (const id of ids) {
                          next[id] = newRead
                        }
                        return next
                      })
                    }}
                  >
                    {!isRead ? (
                      <Mail strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    ) : (
                      <MailOpen strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    )}
                    {isRead ? "Marquer comme non lu" : "Marquer comme lu"}
                  </ContextMenuItem>
                  <ContextMenuItem>
                    <Clock strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Mettre en attente
                  </ContextMenuItem>
                  <ContextMenuItem>
                    <ListTodo strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Ajouter à Tasks
                  </ContextMenuItem>

                  <ContextMenuSeparator />

                  <ContextMenuSub>
                    <ContextMenuSubTrigger className="[&>svg:last-child]:text-[#5f6368]">
                      <FolderInput strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                      Déplacer vers
                    </ContextMenuSubTrigger>
                    <ContextMenuSubContent
                      className={cn(
                        cn(MAIL_MENU_SURFACE_CLASS, "max-h-80 overflow-y-auto"),
                        "[&_[data-slot=context-menu-item]]:gap-3 [&_[data-slot=context-menu-item]]:rounded-none [&_[data-slot=context-menu-item]]:px-3 [&_[data-slot=context-menu-item]]:py-2 [&_[data-slot=context-menu-item]]:text-sm",
                        "[&_[data-slot=context-menu-item]:focus]:bg-[#f1f3f4]"
                      )}
                    >
                      <MoveToContextMenuItems
                        targets={moveTargets}
                        onMoveTo={(targetId) => {
                          moveEmailsToTarget(contextTargetIds, targetId)
                          if (targetId !== "inbox") {
                            setSelectedEmails((prev) => prev.filter((id) => !contextTargetIds.includes(id)))
                          }
                        }}
                      />
                    </ContextMenuSubContent>
                  </ContextMenuSub>

                  <ContextMenuSub>
                    <ContextMenuSubTrigger className="[&>svg:last-child]:text-[#5f6368]">
                      <Tag strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                      Ajouter le libellé
                    </ContextMenuSubTrigger>
                    <ContextMenuSubContent
                      className={cn(
                        "z-[100] flex max-h-72 min-w-[260px] flex-col overflow-hidden rounded-lg border border-border bg-popover p-0 py-0 text-[#3c4043] shadow-lg",
                        "[&_[data-slot=context-menu-item]]:gap-3 [&_[data-slot=context-menu-item]]:rounded-none [&_[data-slot=context-menu-item]]:px-3 [&_[data-slot=context-menu-item]]:py-2 [&_[data-slot=context-menu-item]]:text-sm",
                        "[&_[data-slot=context-menu-item]:focus]:bg-[#f1f3f4]"
                      )}
                    >
                      <EmailLabelPickerBlock
                        query={labelPickerQuery}
                        onQueryChange={setLabelPickerQuery}
                        catalogLabels={catalogLabels}
                        resolveLabelVisual={resolveLabelVisual}
                        Item={ContextMenuItem}
                        getLabelPresence={(lab) =>
                          getCatalogLabelPresence(contextTargetIds, lab)
                        }
                        onToggleCatalogLabel={(lab) =>
                          toggleLabelOnEmails(contextTargetIds, lab)
                        }
                        onCreateLabel={(lab) => {
                          addLabelToEmails(contextTargetIds, lab)
                          setLabelPickerQuery("")
                        }}
                      />
                    </ContextMenuSubContent>
                  </ContextMenuSub>

                  <ContextMenuItem>
                    <VolumeX strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Ignorer la conversation
                  </ContextMenuItem>

                  <ContextMenuSeparator />

                  <ContextMenuItem title={`Recherche : ${senderForSearch}`}>
                    <Search strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    <span className="min-w-0 truncate">
                      Rech. e-mails de {senderForSearch}
                    </span>
                  </ContextMenuItem>

                  <ContextMenuSeparator />

                  <ContextMenuItem>
                    <SquareArrowOutUpRight strokeWidth={1.5} className="size-[18px] text-[#5f6368]" />
                    Ouvrir dans une nouvelle fenêtre
                  </ContextMenuItem>
                    </>
                  )}
                </ContextMenuContent>
              </ContextMenu>
              )
            })}
          </div>
          )}
        </>
        </TooltipProvider>
      )}
      </div>
      </div>
      {listToolbarMode ? (
        <div className="pointer-events-none absolute inset-x-0 bottom-0 z-20 hidden sm:flex sm:justify-start">
          <MailFolderStackIndicator
            currentKey={mailNavVisitKey(selectedFolder, inboxTab)}
            folderTree={sidebarNav.folderTree}
            folderIdToLabel={sidebarNav.folderIdToLabel}
            labelRows={sidebarNav.labelRows}
            onNavigate={handleBreadcrumbNavigate}
            className="pointer-events-auto"
          />
        </div>
      ) : null}
      </div>

          {splitView ? (
            <button
              type="button"
              onClick={openCompose}
              className="absolute bottom-4 right-4 z-30 flex size-14 cursor-pointer items-center justify-center rounded-2xl border border-border bg-mail-surface text-[#444746] shadow-[0_1px_3px_rgba(60,64,67,.3),0_4px_8px_rgba(60,64,67,.15)] transition-[box-shadow,background-color] hover:bg-[#f6f8fc] hover:shadow-[0_1px_3px_rgba(60,64,67,.35),0_6px_12px_rgba(60,64,67,.2)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40"
              aria-label="Nouveau message"
            >
              <Pencil className="size-6" strokeWidth={1.5} />
            </button>
          ) : null}
        </div>
      {splitView ? (
          <section className="flex min-h-0 min-w-0 flex-1 flex-col bg-mail-surface">
            {openEmail ? (
              <>
                <div className="relative z-20 flex shrink-0 min-h-12 items-start gap-2 border-b border-gray-200 py-1.5 pl-2 pr-4">
                  {openMailToolbar(false)}
                  <div className="flex-1" />
                  {mailPaginationControls("view")}
                </div>
                <div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-none">
                  <EmailView
                    email={openEmail}
                    threadRoot={openEmailThreadRoot}
                    isSingleMessageView={isSingleMessageView}
                    onToggleStar={toggleStar}
                    isStarred={
                      starredEmails.includes(threadStoreId(openEmail)) ||
                      openEmail.starred
                    }
                    onNavigateToLabel={handleNavigateToLabel}
                    onNotSpam={openEmail.spam === true ? singleNotSpam : undefined}
                    labelBgByText={listRowLabelBgByTextLower}
                    emailLabelToSidebarFolderId={sidebarNav.emailLabelToSidebarFolderId}
                    getNavItemPrefs={sidebarNav.getNavItemPrefs}
                    folderTree={sidebarNav.folderTree}
                    labelRows={sidebarNav.labelRows}
                    currentFolderId={selectedFolder}
                    showLabelChip={(lab) => {
                      if (LABEL_PICKER_EXCLUDE.has(lab)) return true
                      return mailLabelShouldShowInListStrip(
                        lab,
                        sidebarNav.emailLabelToSidebarFolderId,
                        sidebarNav.getNavItemPrefs,
                        sidebarNav.labelRows
                      )
                    }}
                  />
                </div>
              </>
            ) : (
              <Empty className="min-h-[240px] flex-1 border-0 bg-mail-surface py-10 shadow-none">
                <EmptyHeader className="max-w-md">
                  <EmptyMedia
                    variant="icon"
                    className="mb-1 border-0 bg-[#f1f3f4] text-[#5f6368] [&_svg]:size-6"
                  >
                    <Mail className="size-6" strokeWidth={1.5} aria-hidden />
                  </EmptyMedia>
                  <EmptyTitle className="text-[15px] font-medium text-[#3c4043]">
                    Aucun message sélectionné
                  </EmptyTitle>
                  <EmptyDescription className="text-[13px] text-[#5f6368]">
                    Choisissez un message dans la liste ou ouvrez une boîte contenant des messages.
                  </EmptyDescription>
                </EmptyHeader>
              </Empty>
            )}
          </section>
        ) : null}
      </div>

    </div>
  )
}
