<template>
  <div class="mlist" :class="{ 'mlist-compact': compact }">
    <div class="mlist-head">
      <q-input v-model="search" dense outlined clearable debounce="200" placeholder="Rechercher (nom, courriel, sujet…)" class="mlist-search">
        <template #prepend><q-icon name="search" size="18px" /></template>
      </q-input>
      <q-btn flat dense round :icon="compact ? 'density_small' : 'density_medium'" size="sm" @click="toggleDensity"><q-tooltip>{{ compact ? 'Affichage confortable' : 'Affichage compact' }}</q-tooltip></q-btn>
      <q-btn flat dense round :icon="notifyEnabled ? 'notifications_active' : 'notifications_none'" :color="notifyEnabled ? 'indigo-6' : 'grey-6'" size="sm" @click="toggleNotify"><q-tooltip>{{ notifyEnabled ? 'Notifications navigateur activées' : 'Activer les notifications de nouveaux courriels' }}</q-tooltip></q-btn>
      <q-btn flat dense round icon="refresh" size="sm" :loading="loading" @click="fetchList" />
    </div>

    <!-- Case « tout cocher » + filtres OU actions de lot DANS LA MÊME LIGNE (hauteur constante → pas de décalage au clic) -->
    <div class="mlist-tabs">
      <q-checkbox dense size="xs" class="mlist-allcb" :model-value="headState" @update:model-value="toggleAll"><q-tooltip>{{ checked.size ? 'Tout décocher' : 'Tout cocher' }}</q-tooltip></q-checkbox>
      <template v-if="checked.size">
        <span class="text-weight-medium text-grey-8">{{ checked.size }} sélectionné(s)</span>
        <q-space />
        <q-btn dense flat no-caps size="sm" color="teal-7" icon="confirmation_number" label="→ Ticket" :loading="batchBusy">
          <q-menu auto-close>
            <q-list dense style="min-width:190px">
              <q-item-label header class="q-py-xs">Créer un ticket dans…</q-item-label>
              <q-item v-for="dep in TICKET_DEPTS" :key="dep.cat" clickable @click="batchTicket(dep)">
                <q-item-section avatar><q-icon :name="dep.icon" :color="dep.color" size="18px" /></q-item-section>
                <q-item-section>{{ dep.label }}</q-item-section>
              </q-item>
            </q-list>
          </q-menu>
        </q-btn>
        <q-btn dense flat no-caps size="sm" color="grey-7" icon="archive" label="Archiver" :loading="batchBusy" @click="batchArchive" />
        <q-btn dense flat no-caps size="sm" color="red-6" icon="delete" label="Supprimer" :loading="batchBusy" @click="batchDelete" />
        <q-btn dense flat round size="sm" icon="close" @click="checked = new Set()"><q-tooltip>Annuler la sélection</q-tooltip></q-btn>
      </template>
      <template v-else>
        <q-chip dense clickable size="sm" :color="sel.size === 0 ? 'indigo-6' : 'grey-3'" :text-color="sel.size === 0 ? 'white' : 'grey-8'" @click="toggle('all')">
          Tous<span class="mlist-cnt">{{ visible.length }}</span>
        </q-chip>
        <q-chip v-for="f in chips" :key="f.key" dense clickable size="sm"
          :color="sel.has(f.key) ? 'indigo-6' : 'grey-3'" :text-color="sel.has(f.key) ? 'white' : 'grey-8'"
          @click="toggle(f.key)">
          {{ f.label }}<span v-if="f.count" class="mlist-cnt">{{ f.count }}</span>
        </q-chip>
      </template>
    </div>

    <div class="mlist-body">
      <div v-for="d in shown" :key="d.id" class="mrow" :class="{ 'mrow-unread': isUnread(d), 'mrow-checked': checked.has(d.id) }" @click="open(d)">
        <q-checkbox :model-value="checked.has(d.id)" dense size="xs" class="mrow-cb" @click.stop @update:model-value="toggleCheck(d.id)" />
        <!-- icône de canal seulement pour SMS/chat/Facebook ; pour le courriel (cas par défaut) elle est redondante -->
        <q-icon v-if="d.channel && d.channel !== 'email'" :name="chanIcon(d.channel)" :color="chanColor(d.channel)" size="17px" class="mrow-chan" />
        <!-- Ligne unique pleine largeur (style Gmail) : Nom · file · Sujet — aperçu · indicateurs · heure -->
        <span class="mrow-name">{{ d.customerName || d.email || d.phone || 'Inconnu' }}</span>
        <span v-if="replyInfo(d).count > 1" class="mrow-count">{{ replyInfo(d).count }}</span>
        <q-badge v-if="d.queue" class="mrow-tag" color="blue-grey-1" text-color="blue-grey-8">{{ qLabel(d.queue) }}</q-badge>
        <div class="mrow-subject"><span v-if="subj(d)" class="mrow-subj-strong">{{ subj(d) }}</span><span class="mrow-snip">{{ subj(d) && snip(d) ? ' — ' : '' }}{{ snip(d) }}</span></div>
        <div class="mrow-right">
          <q-icon v-if="replyInfo(d).client" name="reply" size="14px" color="teal-6" class="mrow-rep"><q-tooltip>Le client a répondu</q-tooltip></q-icon>
          <q-icon v-if="replyInfo(d).team" name="forum" size="13px" color="indigo-5" class="mrow-rep"><q-tooltip>Un collègue a répondu</q-tooltip></q-icon>
          <span v-if="typingLabel(d)" class="mrow-typing"><q-icon name="edit" size="11px" /> {{ typingLabel(d) }}</span>
          <ReaderStack :readers="readersOf(d)" :me="meEmail" :max="3" :size="17" />
          <span class="mrow-time">{{ relTime(lastTs(d)) }}</span>
        </div>
        <!-- Actions au survol (style Gmail) : répondre / classer en ticket par dépt / archiver / supprimer -->
        <div class="mrow-actions" @click.stop>
          <q-spinner v-if="rowBusy === d.id" size="18px" color="indigo-6" />
          <template v-else>
            <q-btn flat dense round size="sm" icon="reply" color="grey-7" @click.stop="open(d)"><q-tooltip>Répondre (ouvrir le fil)</q-tooltip></q-btn>
            <q-btn flat dense round size="sm" icon="confirmation_number" color="teal-7">
              <q-tooltip>Classer en ticket…</q-tooltip>
              <q-menu auto-close anchor="bottom right" self="top right">
                <q-list dense style="min-width:190px">
                  <q-item-label header class="q-py-xs">Créer un ticket dans…</q-item-label>
                  <q-item v-for="dep in TICKET_DEPTS" :key="dep.cat" clickable @click="ticketTo(d, dep)">
                    <q-item-section avatar><q-icon :name="dep.icon" :color="dep.color" size="18px" /></q-item-section>
                    <q-item-section>{{ dep.label }}</q-item-section>
                  </q-item>
                </q-list>
              </q-menu>
            </q-btn>
            <q-btn flat dense round size="sm" icon="filter_alt" color="blue-grey-6" @click.stop="openFilterLike(d)"><q-tooltip>Filtrer les messages comme celui-ci (masquer / afficher)…</q-tooltip></q-btn>
            <q-btn flat dense round size="sm" icon="archive" color="grey-7" @click.stop="quickArchive(d)"><q-tooltip>Archiver (retirer de la boîte)</q-tooltip></q-btn>
            <q-btn flat dense round size="sm" icon="delete" color="red-4" @click.stop="quickDelete(d)"><q-tooltip>Supprimer</q-tooltip></q-btn>
          </template>
        </div>
      </div>
      <div v-if="!shown.length" class="mlist-empty">Aucune conversation{{ sel.size || search ? ' (filtre actif)' : '' }}</div>
    </div>

    <!-- « Filtrer les messages comme celui-ci » : crée une règle masquer/afficher (style Gmail) -->
    <q-dialog v-model="filterDialog.open">
      <q-card style="min-width:340px;max-width:92vw">
        <q-card-section class="row items-center q-pb-none"><q-icon name="filter_alt" color="blue-grey-6" size="20px" class="q-mr-sm" /><span class="text-subtitle1 text-weight-bold">Filtrer les messages comme celui-ci</span></q-card-section>
        <q-card-section>
          <div class="text-caption text-grey-7 q-mb-sm">Les messages correspondants (futurs <b>et déjà reçus</b>) seront <b>{{ filterDialog.action === 'mask' ? 'masqués de la boîte' : 'toujours affichés' }}</b>.</div>
          <q-option-group :model-value="filterDialog.field" @update:model-value="onFilterField" inline dense color="indigo-6" :options="[{ label: 'Expéditeur', value: 'from' }, { label: 'Sujet', value: 'subject' }]" />
          <q-input dense outlined v-model="filterDialog.contains" class="q-mt-sm" :label="filterDialog.field === 'from' ? 'Expéditeur contient' : 'Sujet contient'" hint="Courriel complet, domaine (ex. targo.ca) ou mot-clé" />
          <q-option-group v-model="filterDialog.action" inline dense color="indigo-6" class="q-mt-md" :options="[{ label: 'Masquer', value: 'mask' }, { label: 'Toujours afficher', value: 'allow' }]" />
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat no-caps label="Annuler" v-close-popup />
          <q-btn unelevated no-caps color="indigo-6" icon="check" label="Créer la règle" @click="doSaveFilter" />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { useQuasar } from 'quasar'
import { useConversations } from 'src/composables/useConversations'
import { useAuthStore } from 'src/stores/auth'
import { relTime } from 'src/composables/useFormatters'
import { useRouter } from 'vue-router'
import ReaderStack from './ReaderStack.vue'

const $q = useQuasar()
const router = useRouter()
const { discussions, QUEUES, fetchList, loading, openDiscussion, panelOpen, agentTyping, bulkDelete, bulkArchive, createTicket, archiveDiscussion, notifyEnabled, requestNotifyPermission, fetchInboxRules, saveInboxRules } = useConversations()
// « Filtrer comme ceci » (style Gmail) : crée une règle masquer/afficher par expéditeur ou sujet
const filterDialog = ref({ open: false, field: 'from', contains: '', action: 'mask', srcEmail: '', srcSubject: '' })
function openFilterLike (d) { filterDialog.value = { open: true, field: 'from', contains: d.email || d.customerName || '', action: 'mask', srcEmail: d.email || '', srcSubject: subj(d) || '' } }
function onFilterField (v) { filterDialog.value.field = v; filterDialog.value.contains = v === 'from' ? filterDialog.value.srcEmail : filterDialog.value.srcSubject }
async function doSaveFilter () {
  const f = filterDialog.value
  if (!String(f.contains || '').trim()) { $q.notify({ type: 'warning', message: 'Précisez un texte à filtrer.' }); return }
  try {
    const rules = await fetchInboxRules()
    rules.push({ id: 'r' + Date.now(), field: f.field, contains: String(f.contains).trim(), action: f.action })
    const r = await saveInboxRules(rules)
    $q.notify({ type: 'positive', message: (f.action === 'mask' ? 'Règle : masquer' : 'Règle : toujours afficher') + ` « ${f.contains} »` + (r.retro ? ` · ${r.retro} conv. mise(s) à jour` : '') })
    filterDialog.value.open = false
  } catch (e) { $q.notify({ type: 'negative', message: e.message }) }
}
async function toggleNotify () {
  if (notifyEnabled.value) { $q.notify({ type: 'info', message: 'Pour les désactiver, utilisez les réglages de notification du navigateur.' }); return }
  const ok = await requestNotifyPermission()
  $q.notify({ type: ok ? 'positive' : 'warning', message: ok ? 'Notifications activées — alerte à chaque nouveau courriel.' : 'Refusé — autorisez les notifications dans le navigateur.' })
}
const meEmail = computed(() => { try { return useAuthStore().user } catch { return null } })

const sel = ref(new Set())     // filtres cumulables (départements OU entre eux ; unread/mine = ET)
const search = ref('')
const compact = ref(false); try { compact.value = localStorage.getItem('mlist-density') === 'compact' } catch (e) { /* */ }
function toggleDensity () { compact.value = !compact.value; try { localStorage.setItem('mlist-density', compact.value ? 'compact' : '') } catch (e) { /* */ } }
const checked = ref(new Set()) // ids de discussions cochées (batch)
const batchBusy = ref(false)

const QLABELS = { Facturations: 'Facturation', 'Service à la clientèle': 'Service client', Supports: 'Support', Technicien: 'Technicien' }
function qLabel (q) { return QLABELS[q] || q }
function chanIcon (c) { return c === 'email' ? 'mail' : c === 'webchat' ? 'forum' : c === 'facebook' ? 'chat' : 'sms' }
function chanColor (c) { return c === 'email' ? 'red-5' : c === 'webchat' ? 'purple-5' : c === 'facebook' ? 'blue-6' : 'teal-6' }
function lastTs (d) { return d.lastMessage && d.lastMessage.ts }
function subjectOf (d) { return d.subject || (d.lastMessage && (d.lastMessage.text || '')) || '—' }
function subj (d) { const s = d.subject; return (s && s !== '(courriel)') ? s : '' } // ligne de sujet (vide pour SMS/chat)
function snip (d) { return String((d.lastMessage && d.lastMessage.text) || '').replace(/^✉️[^\n]*\n+/, '').replace(/\s+/g, ' ').trim() } // aperçu du dernier message
function discToken (d) { const a = (d.conversations || []).find(c => c.status === 'active'); return (a && a.token) || d.token || ((d.conversations || [])[0] || {}).token || null }

function isUnread (d) {
  if (!d.lastMessage || d.lastMessage.from === 'agent') return false
  const mine = (d.readBy || {})[meEmail.value]
  if (!mine) return true
  return !mine.lastTs || String(mine.lastTs) < String(d.lastMessage.ts)
}
function readersOf (d) {
  const rb = d.readBy || {}
  return Object.keys(rb).map(e => ({ email: e, readAt: rb[e] && rb[e].readAt })).sort((a, b) => String(b.readAt || '').localeCompare(String(a.readAt || '')))
}
function typingLabel (d) {
  for (const c of (d.conversations || [])) { const a = agentTyping.value[c.token]; if (a) return a.includes('@') ? a.split('@')[0] : a }
  return ''
}
// Indicateurs « il y a eu des réponses » : nb total de messages + un collègue (agent) a répondu + le client a répondu (≥2 msgs client)
function replyInfo (d) {
  const ms = d.messages || []
  return { count: d.messageCount || ms.length, team: ms.some(m => m.from === 'agent'), client: ms.filter(m => m.from === 'customer').length > 1 }
}
function matchSearch (d, q) {
  return [d.customerName, d.email, d.phone, d.subject, d.lastMessage && d.lastMessage.text].some(x => String(x || '').toLowerCase().includes(q))
}

const CH_LABELS = { email: 'Courriel', sms: 'SMS', webchat: 'Chat web', facebook: 'Messenger' }
// Vue « Masqués » : affiche les conversations masquées (noise) au lieu de l'inbox normale
const maskedMode = computed(() => sel.value.has('masked'))
const visible = computed(() => (discussions.value || []).filter(d => d.status === 'active' && (maskedMode.value ? !!d.noise : !d.noise)))
const maskedCount = computed(() => (discussions.value || []).filter(d => d.status === 'active' && d.noise).length)
const chips = computed(() => {
  const base = [
    { key: 'unread', label: 'Non lus', count: visible.value.filter(isUnread).length },
    { key: 'mine', label: 'À moi', count: visible.value.filter(d => d.assignee === meEmail.value).length },
  ]
  for (const q of QUEUES) base.push({ key: q, label: qLabel(q), count: visible.value.filter(d => (d.queue || '') === q).length })
  // Filtre par CANAL — seulement si plusieurs canaux présents (sinon inutile dans une boîte tout-courriel)
  const chans = [...new Set(visible.value.map(d => d.channel || 'email'))]
  if (chans.length > 1) for (const ch of chans) base.push({ key: 'ch:' + ch, label: CH_LABELS[ch] || ch, count: visible.value.filter(d => (d.channel || 'email') === ch).length })
  if (maskedCount.value) base.push({ key: 'masked', label: '🔇 Masqués', count: maskedCount.value }) // voir ce qui est filtré/masqué
  return base
})
function toggle (k) {
  const s = new Set(sel.value)
  if (k === 'all') s.clear()
  else if (s.has(k)) s.delete(k); else s.add(k)
  sel.value = s
}
const shown = computed(() => {
  const q = search.value.trim().toLowerCase()
  const depts = [...sel.value].filter(k => QUEUES.includes(k))
  const chans = [...sel.value].filter(k => k.startsWith('ch:')).map(k => k.slice(3))
  const needU = sel.value.has('unread'); const needM = sel.value.has('mine')
  return visible.value.filter(d => {
    if (depts.length && !depts.includes(d.queue || '')) return false
    if (chans.length && !chans.includes(d.channel || 'email')) return false
    if (needU && !isUnread(d)) return false
    if (needM && d.assignee !== meEmail.value) return false
    if (q && !matchSearch(d, q)) return false
    return true
  }).sort((a, b) => String(lastTs(b) || '').localeCompare(String(lastTs(a) || '')))
})

function toggleCheck (id) { const s = new Set(checked.value); s.has(id) ? s.delete(id) : s.add(id); checked.value = s }
const checkedDiscs = computed(() => shown.value.filter(d => checked.value.has(d.id)))
// Case maîtresse « tout cocher » : true=tout, null=indéterminé (partiel), false=rien
const allChecked = computed(() => shown.value.length > 0 && shown.value.every(d => checked.value.has(d.id)))
const headState = computed(() => checked.value.size ? (allChecked.value ? true : null) : false)
function toggleAll () { if (allChecked.value) { checked.value = new Set() } else { const s = new Set(); shown.value.forEach(d => s.add(d.id)); checked.value = s } }
async function batchDelete () {
  if (!checkedDiscs.value.length) return
  $q.dialog({ title: 'Supprimer', message: `Supprimer ${checkedDiscs.value.length} conversation(s) ? (courriels liés → corbeille Gmail)`, cancel: true, ok: { label: 'Supprimer', color: 'negative' } }).onOk(async () => {
    batchBusy.value = true
    try { await bulkDelete(checkedDiscs.value); $q.notify({ type: 'positive', message: 'Supprimé.' }); checked.value = new Set() } catch (e) { $q.notify({ type: 'negative', message: e.message }) } finally { batchBusy.value = false }
  })
}
async function batchTicket (dep) {
  const list = checkedDiscs.value.slice()
  if (!list.length) return
  batchBusy.value = true
  let ok = 0
  for (const d of list) {
    const tok = discToken(d); if (!tok) continue
    try { await createTicket(tok, { title: subjectOf(d).slice(0, 90), category: dep.cat, priority: 'Medium' }); ok++ } catch (e) { /* continue */ }
  }
  try { await bulkArchive(list) } catch (e) { /* */ } // retire de la boîte ; les tickets suivent le travail
  batchBusy.value = false
  checked.value = new Set()
  $q.notify({ type: ok ? 'positive' : 'negative', message: `${ok} ticket(s) « ${dep.label} » créé(s) · conversations archivées.` })
}
async function batchArchive () {
  const list = checkedDiscs.value.slice()
  if (!list.length) return
  batchBusy.value = true
  try { await bulkArchive(list); $q.notify({ type: 'positive', message: `${list.length} archivé(s).` }); checked.value = new Set() } catch (e) { $q.notify({ type: 'negative', message: e.message }) } finally { batchBusy.value = false }
}

// Actions au survol (style Gmail) : répondre / classer en ticket par département / archiver / supprimer
const TICKET_DEPTS = [
  { label: 'Support technique', cat: 'Support', icon: 'build', color: 'indigo-6' },
  { label: 'Facturation', cat: 'Facturation', icon: 'receipt_long', color: 'green-7' },
  { label: 'Service client', cat: 'Service client', icon: 'support_agent', color: 'blue-6' },
  { label: 'Installation', cat: 'Installation', icon: 'cable', color: 'deep-orange-6' },
]
const rowBusy = ref(null)
async function ticketTo (d, dep) {
  const tok = discToken(d); if (!tok) return
  rowBusy.value = d.id
  try { await createTicket(tok, { title: subjectOf(d).slice(0, 90), category: dep.cat, priority: 'Medium' }); await archiveDiscussion(d); $q.notify({ type: 'positive', message: 'Ticket « ' + dep.label + ' » créé — conversation archivée' }) } catch (e) { $q.notify({ type: 'negative', message: e.message }) } finally { rowBusy.value = null }
}
async function quickArchive (d) {
  rowBusy.value = d.id
  try { await archiveDiscussion(d); $q.notify({ type: 'positive', message: 'Archivé' }) } catch (e) { $q.notify({ type: 'negative', message: e.message }) } finally { rowBusy.value = null }
}
function quickDelete (d) {
  $q.dialog({ title: 'Supprimer', message: 'Supprimer cette conversation ? (courriels liés → corbeille Gmail)', cancel: true, ok: { label: 'Supprimer', color: 'negative' } }).onOk(async () => {
    try { await bulkDelete([d]); $q.notify({ type: 'positive', message: 'Supprimé' }) } catch (e) { $q.notify({ type: 'negative', message: e.message }) }
  })
}
function open (d) { const tok = discToken(d); if (tok) router.push('/communications/c/' + tok); else { panelOpen.value = true; openDiscussion(d) } }
onMounted(() => { if (!discussions.value || !discussions.value.length) fetchList() })
</script>

<style scoped>
/* Pleine hauteur ; largeur PLAFONNÉE à une taille confortable (ni étriquée, ni étirée à l'infini sur grand écran), ancrée à gauche */
.mlist { display: flex; flex-direction: column; height: calc(100vh - 50px); max-width: 1280px; }
.mlist-head { display: flex; align-items: center; gap: 6px; padding: 8px 12px 4px; }
.mlist-search { flex: 1; max-width: 520px; }
/* Une SEULE ligne (les puces défilent horizontalement si trop nombreuses) → hauteur constante, donc la sélection ne décale jamais la liste */
.mlist-tabs { display: flex; flex-wrap: nowrap; align-items: center; gap: 4px; padding: 6px 12px; border-bottom: 1px solid var(--ops-border, #e2e8f0); overflow-x: auto; min-height: 40px; }
.mlist-tabs::-webkit-scrollbar { height: 0; }
.mlist-tabs .q-chip { flex: 0 0 auto; }
.mlist-allcb { flex: 0 0 auto; margin-right: 4px; }
.mlist-cnt { margin-left: 5px; font-size: 0.66rem; opacity: 0.8; }
.mlist-batch { display: flex; align-items: center; gap: 4px; padding: 6px 12px; background: #eef2ff; border-bottom: 1px solid #c7d2fe; font-size: 0.82rem; }
.mlist-body { flex: 1; overflow-y: auto; overflow-x: hidden; }
/* garantit que chaque ligne tient dans la largeur (pas de débordement horizontal ; le sujet/aperçu s'ellipse) */
.mrow { width: 100%; box-sizing: border-box; }
.mrow { display: flex; gap: 8px; padding: 8px 16px; border-bottom: 1px solid #f1f5f9; cursor: pointer; align-items: center; position: relative; }
.mrow:hover { background: var(--ops-bg-hover, #eef2ff); }
/* Actions au survol : apparaissent à droite (par-dessus heure/lecteurs), comme Gmail */
.mrow-actions { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); display: none; align-items: center; gap: 1px; background: var(--ops-bg-hover, #eef2ff); padding: 2px 5px; border-radius: 8px; box-shadow: 0 1px 5px rgba(15, 23, 42, .12); }
.mrow:hover .mrow-actions { display: flex; }
.mrow-checked { background: #eef2ff; }
.mrow-cb { margin-top: 1px; flex: 0 0 auto; }
.mrow-chan { margin-top: 2px; flex: 0 0 auto; }
.mrow-main { flex: 1; min-width: 0; }
.mrow-line1 { display: flex; align-items: center; gap: 6px; }
.mlist-compact .mrow { padding-top: 5px; padding-bottom: 5px; }
.mlist-compact .mrow-subject, .mlist-compact .mrow-name { font-size: 0.76rem; }
.mrow-name { flex: 0 0 auto; width: 200px; max-width: 26%; min-width: 110px; font-size: 0.86rem; color: var(--ops-text, #1e293b); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Sujet — aperçu : occupe toute la largeur restante, sur une seule ligne */
.mrow-subject { flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 0.82rem; }
.mrow-subj-strong { color: var(--ops-text, #1e293b); }
.mrow-snip { color: var(--ops-text-muted, #64748b); }
.mrow-right { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.mrow-tag { font-size: 0.62rem; }
.mrow-count { font-size: 0.66rem; color: #64748b; background: #eef2f7; border-radius: 9px; padding: 0 6px; line-height: 1.5; flex: 0 0 auto; }
.mrow-rep { margin-left: 4px; flex: 0 0 auto; }
.mrow-time { font-size: 0.7rem; color: var(--ops-text-muted, #64748b); flex: 0 0 auto; }
.mrow-sub { font-size: 0.78rem; color: var(--ops-text-muted, #64748b); margin-top: 1px; }
.mrow-foot { display: flex; align-items: center; min-height: 20px; margin-top: 5px; }
.mrow-typing { font-size: 0.7rem; color: #0d9488; font-weight: 500; display: flex; align-items: center; gap: 3px; }
.mrow-unread .mrow-name, .mrow-unread .mrow-subj-strong { font-weight: 700; }
.mrow-unread .mrow-snip { color: var(--ops-text, #334155); }
.mrow-unread { background: #fafbff; }
.mlist-empty { text-align: center; color: #94a3b8; padding: 32px; font-size: 0.85rem; }
</style>
