<!--
  Staff live-chat panel: left = colleague list (with presence dots, per-conversation
  unread pills and message search), right = the 1:1 conversation with an optimistic
  composer, read receipts, day separators, typing indicator, entity share cards,
  voice messages (record + inline playback + transcript), an emoji picker and
  one-tap emoji reactions.
  Teleported to <body> with a NON-scoped style block (teleported nodes don't reliably
  get scoped styles — see project memory). Auto-imported as <ChatDrawer>.
  NOTE: keep this in sync with pages/chat.vue (the mobile clone) — see CLAUDE.md.
-->
<script setup>
import { linkifyHtml } from '~/utils/linkify'

const open = useChatOpen()
const activePeer = useChatActivePeer()
const presence = useChatPresence()
const connected = useChatConnected()
const typing = useChatTyping()
const share = useChatShare()
const soundMuted = useChatSoundMuted()

// Self-gate: this drawer is mounted ONCE globally (in the layout), so it must
// stay inert when chat is disabled, the role is limited, or the user isn't
// chat-enabled (mirrors <ChatBadge>; single source of truth in useChatEligible).
const enabled = useChatEligible()

const { list, load, loading } = useStaffDirectory()
const { threads, loadHistory, sendMessage, sendVoice, sendImage, react, sendTyping, markRead, me } = useChat()
const { picker: emojiList, quick: quickReactions } = useChatEmojis()
// Voice transcription language is left to Whisper's own auto-detection: the SPOKEN language is not the
// UI language, and Whisper's `-l` is a hard override — forcing the UI language makes it translate the
// audio (e.g. German speech decoded as English). So the composer always sends 'auto'.

const draft = ref('')
const bodyRef = ref(null)

// Mobile uses a dedicated full-screen page (pages/chat.vue) instead of this
// teleported drawer (the teleport+overlay renders unreliably on phones). Hide
// the drawer at the app's mobile breakpoint (≤1024, matching <ChatBadge>) so a
// stray open (e.g. an incoming-message socket event) can't surface the broken
// overlay on mobile.
const isNarrow = ref(false)
const syncNarrow = () => { isNarrow.value = import.meta.client && window.innerWidth <= 1024 }

// Typing-signal bookkeeping (declared before the watchers that use them).
let typingActive = false
let typingIdleTimer = null

const activeUser = computed(() =>
  list.value.find(u => Number(u.id) === Number(activePeer.value)) || null)

const messages = computed(() =>
  activePeer.value != null ? (threads.value[activePeer.value] || []) : [])

const scrollBottom = () => nextTick(() => {
  const el = bodyRef.value
  if (el) el.scrollTop = el.scrollHeight
})

const close = () => { open.value = false }
const backToList = () => { activePeer.value = null }
const selectPeer = (id) => { activePeer.value = Number(id) }

// ── typing signal (debounced) ────────────────────────────────────────────────
const stopTyping = () => {
  if (typingIdleTimer) { clearTimeout(typingIdleTimer); typingIdleTimer = null }
  if (typingActive && activePeer.value != null) sendTyping(activePeer.value, false)
  typingActive = false
}
const onComposerInput = () => {
  if (activePeer.value == null) return
  if (!typingActive) { typingActive = true; sendTyping(activePeer.value, true) }
  if (typingIdleTimer) clearTimeout(typingIdleTimer)
  typingIdleTimer = setTimeout(stopTyping, 2500)
}
const onComposerFocus = () => scrollBottom()

const submit = () => {
  if (activePeer.value == null) return
  const pending = (share.value && share.value.kind === 'entity') ? { ...share.value } : null
  const text = draft.value.trim()
  const body = text || (pending?.label || '')
  if (!body) return
  sendMessage(activePeer.value, body, pending)
  draft.value = ''
  showEmoji.value = false
  if (pending) share.value = null
  stopTyping()
  scrollBottom()
}

// ── emoji picker ───────────────────────────────────────────────────────────────
const showEmoji = ref(false)
const insertEmoji = (e) => { draft.value += e }

// ── voice recording ─────────────────────────────────────────────────────────────
const isRecording = ref(false)
const recordingTime = ref(0)
const recordedBlob = ref(null)
const recordedUrl = ref(null)
let mediaRecorder = null
let audioChunks = []
let recTimer = null
let recStream = null
const REC_MAX = 300 // hard cap 5 min

const recFmt = computed(() => {
  const m = Math.floor(recordingTime.value / 60), s = recordingTime.value % 60
  return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
})
const pickMime = () => {
  const cands = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4', 'audio/ogg']
  for (const c of cands) {
    try { if (window.MediaRecorder && MediaRecorder.isTypeSupported(c)) return { mimeType: c } } catch { /* */ }
  }
  return {}
}
const resetRecording = () => {
  if (recTimer) { clearInterval(recTimer); recTimer = null }
  if (recordedUrl.value) { try { URL.revokeObjectURL(recordedUrl.value) } catch { /* */ } }
  if (recStream) { try { recStream.getTracks().forEach(t => t.stop()) } catch { /* */ } recStream = null }
  recordedBlob.value = null; recordedUrl.value = null; recordingTime.value = 0
  isRecording.value = false; audioChunks = []; mediaRecorder = null
}
const startRecording = async () => {
  if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) return
  showEmoji.value = false
  try {
    recStream = await navigator.mediaDevices.getUserMedia({ audio: true })
    audioChunks = []
    const mr = new MediaRecorder(recStream, pickMime())
    mediaRecorder = mr
    mr.ondataavailable = (e) => { if (e.data.size > 0) audioChunks.push(e.data) }
    mr.onstop = () => {
      const type = mr.mimeType || 'audio/webm'
      const blob = new Blob(audioChunks, { type })
      recordedBlob.value = blob
      recordedUrl.value = URL.createObjectURL(blob)
      if (recStream) { recStream.getTracks().forEach(t => t.stop()); recStream = null }
    }
    mr.start()
    isRecording.value = true
    recordingTime.value = 0
    recTimer = setInterval(() => { recordingTime.value++; if (recordingTime.value >= REC_MAX) stopRecording() }, 1000)
  } catch (err) {
    console.error('[chat] microphone unavailable:', err)
    resetRecording()
  }
}
const stopRecording = () => {
  if (mediaRecorder && mediaRecorder.state !== 'inactive') { try { mediaRecorder.stop() } catch { /* */ } }
  isRecording.value = false
  if (recTimer) { clearInterval(recTimer); recTimer = null }
}
const cancelRecording = () => {
  if (mediaRecorder && mediaRecorder.state !== 'inactive') { try { mediaRecorder.stop() } catch { /* */ } }
  resetRecording()
}
const sendRecording = () => {
  if (!recordedBlob.value || activePeer.value == null) return
  sendVoice(activePeer.value, recordedBlob.value, recordingTime.value, 'auto')
  resetRecording()
  scrollBottom()
}
const voiceSrc = (m) => m.meta?.audioId ? chatMediaUrl('/api/chat/voice/' + m.meta.audioId) : (m.meta?.localUrl || '')
const imageSrc = (m) => m.meta?.thumbUrl || m.meta?.localUrl || ''
const imageFullSrc = (m) => m.meta?.fullUrl || m.meta?.thumbUrl || m.meta?.localUrl || ''

// Image sharing: a hidden file input (accept=image/* opens the gallery/camera chooser). Downscaled
// client-side before upload. Kept in sync with pages/chat.vue.
const imgInput = ref(null)
const pickImage = () => { showEmoji.value = false; imgInput.value?.click() }
const onImagePicked = (e) => {
  const f = e.target.files?.[0]; e.target.value = ''
  if (!f || activePeer.value == null) return
  sendImage(activePeer.value, f)
  scrollBottom()
}

// ── reactions ────────────────────────────────────────────────────────────────
const reactingTo = ref(null)
const openReactions = (m) => { reactingTo.value = (reactingTo.value === m.id) ? null : m.id }
const myReacted = (m, emoji) =>
  Array.isArray(m.reactions) && m.reactions.some(r => Number(r.userId) === Number(me) && r.emoji === emoji)
const toggleReaction = (m, emoji) => {
  if (!m.id) return
  react(m.id, emoji, !myReacted(m, emoji))
  reactingTo.value = null
}
const reactionGroups = (m) => {
  if (!Array.isArray(m.reactions) || !m.reactions.length) return []
  const map = {}
  for (const r of m.reactions) {
    if (!map[r.emoji]) map[r.emoji] = { emoji: r.emoji, count: 0, mine: false }
    map[r.emoji].count++
    if (Number(r.userId) === Number(me)) map[r.emoji].mine = true
  }
  return Object.values(map)
}

// ── message search ───────────────────────────────────────────────────────────
const searchQuery = ref('')
const searchResults = ref([])
let searchTimer = null
const runSearch = () => {
  const q = searchQuery.value.trim()
  if (searchTimer) clearTimeout(searchTimer)
  if (q.length < 2) { searchResults.value = []; return }
  searchTimer = setTimeout(async () => {
    try {
      const res = await $fetch('/api/chat/search', {
        query: { q },
        headers: useRequestHeaders(['cookie'])
      })
      searchResults.value = res?.records || []
    } catch { searchResults.value = [] }
  }, 300)
}
const clearSearch = () => { searchQuery.value = ''; searchResults.value = [] }
const peerName = (id) =>
  list.value.find(u => Number(u.id) === Number(id))?.name || ('User ' + id)
const openResult = (r) => { clearSearch(); selectPeer(r.peer) }

// ── share-to-chat (entity cards) ──────────────────────────────────────────────
const entityIcon = (type) => ({
  order: '📦', shipment: '🚚', invoice: '🧾', product: '🏷️', partner: '🏢'
}[type] || '🔗')
const clearShare = () => { share.value = null }
const onShareEvent = (e) => {
  const detail = e?.detail
  if (!detail) return
  share.value = detail
  open.value = true
}
const openEntity = (meta) => {
  if (!meta?.url) return
  close()
  navigateTo(meta.url)
}

// ── presence / time / day helpers ─────────────────────────────────────────────
const dotClass = (s) => s === 'online' ? 'is-online' : s === 'away' ? 'is-away' : 'is-offline'
const statusLabel = (s) => s === 'online' ? 'Online' : s === 'away' ? 'Abwesend' : 'Offline'
const fmtTime = (ms) => {
  try { return new Date(ms).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }
  catch { return '' }
}
const sameDay = (a, b) => {
  const da = new Date(a), db = new Date(b)
  return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate()
}
const showDaySep = (i) =>
  i === 0 || !sameDay(messages.value[i - 1]?.createdAt, messages.value[i]?.createdAt)
const dayLabel = (ms) => {
  try {
    const d = new Date(ms), now = new Date()
    const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
    const that = new Date(d.getFullYear(), d.getMonth(), d.getDate())
    const diff = Math.round((today - that) / 86400000)
    if (diff === 0) return 'Heute'
    if (diff === 1) return 'Gestern'
    return d.toLocaleDateString()
  } catch { return '' }
}

const toggleMute = () => {
  soundMuted.value = !soundMuted.value
  try { localStorage.setItem('logship_chat_sound', soundMuted.value ? 'muted' : 'on') } catch { /* */ }
}

// ── lifecycle & watchers ──────────────────────────────────────────────────────
onMounted(() => {
  syncNarrow()
  if (enabled.value) load()
  if (typeof window !== 'undefined') {
    window.addEventListener('logship:chat-share', onShareEvent)
    window.addEventListener('resize', syncNarrow)
  }
})
onBeforeUnmount(() => {
  if (typeof window !== 'undefined') {
    window.removeEventListener('logship:chat-share', onShareEvent)
    window.removeEventListener('resize', syncNarrow)
  }
  stopTyping()
  resetRecording()
})

watch(open, (v) => {
  if (!v) return
  if (enabled.value) load()
  if (activePeer.value != null) markRead(activePeer.value)
})
watch(enabled, (v) => { if (v) load() })

watch(activePeer, async (id, prev) => {
  if (prev != null && typingActive) { sendTyping(prev, false); typingActive = false }
  if (typingIdleTimer) { clearTimeout(typingIdleTimer); typingIdleTimer = null }
  reactingTo.value = null
  if (isRecording.value || recordedUrl.value) resetRecording()
  if (id == null) return
  await loadHistory(id)
  await markRead(id)
  scrollBottom()
})

watch(messages, () => {
  scrollBottom()
  if (open.value && activePeer.value != null) markRead(activePeer.value)
}, { deep: true })
</script>

<template>
  <Teleport to="body">
    <div v-if="open && enabled && !isNarrow" class="chat-drawer-overlay" @click.self="close">
      <aside class="chat-drawer">
        <header class="chat-drawer__head">
          <span class="chat-drawer__title">
            Chat
            <span class="chat-conn" :class="connected ? 'is-up' : 'is-down'"
                  :title="connected ? 'Verbunden' : 'Getrennt'"></span>
          </span>
          <div class="chat-drawer__actions">
            <button class="chat-drawer__icon-btn" @click="toggleMute"
                    :title="soundMuted ? 'Ton einschalten' : 'Ton ausschalten'">
              {{ soundMuted ? '🔕' : '🔔' }}
            </button>
            <button class="chat-drawer__close" @click="close" aria-label="Schließen">&times;</button>
          </div>
        </header>

        <div class="chat-drawer__body" :class="{ 'peer-active': activePeer != null }">
          <!-- colleague list / search -->
          <div class="chat-people">
            <div class="chat-search">
              <input v-model="searchQuery" @input="runSearch" class="chat-search__input"
                     placeholder="Nachrichten suchen…" />
              <button v-if="searchQuery" class="chat-search__clear" @click="clearSearch" aria-label="Leeren">&times;</button>
            </div>

            <!-- search results -->
            <div v-if="searchResults.length" class="chat-results">
              <button v-for="(r, i) in searchResults" :key="r.id || i" class="chat-result" @click="openResult(r)">
                <span class="chat-result__name">{{ peerName(r.peer) }}</span>
                <span class="chat-result__snippet">{{ r.body }}</span>
                <span class="chat-result__time">{{ fmtTime(r.createdAt) }}</span>
              </button>
            </div>
            <div v-else-if="searchQuery.trim().length >= 2" class="chat-empty">Keine Treffer.</div>

            <!-- colleagues -->
            <template v-else>
              <div v-if="loading && list.length === 0" class="chat-empty">Lädt…</div>
              <div v-else-if="list.length === 0" class="chat-empty">Keine Kollegen gefunden.</div>
              <button
                v-for="u in list" :key="u.id"
                class="chat-person"
                :class="{ active: Number(activePeer) === Number(u.id) }"
                @click="selectPeer(u.id)">
                <span class="chat-dot" :class="dotClass(u.status)"></span>
                <span class="chat-person__name">{{ u.name }}</span>
                <span v-if="u.unread > 0" class="chat-person__unread">{{ u.unread > 99 ? '99+' : u.unread }}</span>
                <span class="chat-person__status">{{ statusLabel(u.status) }}</span>
              </button>
            </template>
          </div>

          <!-- conversation -->
          <div class="chat-thread">
            <template v-if="activeUser">
              <div class="chat-thread__head">
                <button class="chat-thread__back" @click="backToList" aria-label="Zurück">&lsaquo;</button>
                <span class="chat-dot" :class="dotClass(activeUser.status)"></span>
                <strong>{{ activeUser.name }}</strong>
                <small v-if="typing[activePeer]" class="chat-typing">tippt…</small>
                <small v-else>{{ statusLabel(activeUser.status) }}</small>
              </div>

              <div ref="bodyRef" class="chat-thread__messages" @click="reactingTo = null">
                <template v-for="(m, i) in messages" :key="m.id || m.clientMsgId || i">
                  <div v-if="showDaySep(i)" class="chat-day"><span>{{ dayLabel(m.createdAt) }}</span></div>
                  <div class="chat-msg" :class="Number(m.senderUserId) === Number(me) ? 'mine' : 'theirs'">
                    <div class="chat-msg__wrap">
                      <div class="chat-msg__line">
                        <div class="chat-msg__bubble" :class="{ pending: m.pending }">
                          <!-- voice message -->
                          <template v-if="m.meta && m.meta.kind === 'voice'">
                            <div class="chat-voice">
                              <span class="chat-voice__icon">🎤</span>
                              <audio class="chat-voice__audio" controls preload="none" :src="voiceSrc(m)"></audio>
                            </div>
                            <div v-if="m.meta.error" class="chat-voice__tx is-muted">Senden fehlgeschlagen.</div>
                            <div v-else-if="m.meta.transcript" class="chat-voice__tx">{{ m.meta.transcript }}</div>
                            <div v-else-if="m.meta.transcribing" class="chat-voice__tx is-muted">Transkription…</div>
                          </template>
                          <!-- image -->
                          <template v-else-if="m.meta && m.meta.kind === 'image'">
                            <a class="chat-image" :href="imageFullSrc(m)" target="_blank" rel="noopener">
                              <img class="chat-image__thumb" :src="imageSrc(m)" alt="Bild" loading="lazy" />
                            </a>
                            <div v-if="m.meta.error" class="chat-voice__tx is-muted">Senden fehlgeschlagen.</div>
                          </template>
                          <!-- shared entity card -->
                          <template v-else-if="m.meta && m.meta.kind === 'entity'">
                            <a class="chat-card" @click.prevent="openEntity(m.meta)">
                              <span class="chat-card__icon">{{ entityIcon(m.meta.entityType) }}</span>
                              <span class="chat-card__label">{{ m.meta.label }}</span>
                            </a>
                            <span v-if="m.body && m.body !== m.meta.label" class="chat-msg__body" v-html="linkifyHtml(m.body)"></span>
                          </template>
                          <!-- plain text -->
                          <span v-else class="chat-msg__body" v-html="linkifyHtml(m.body)"></span>

                          <span class="chat-msg__foot">
                            <span class="chat-msg__time">{{ fmtTime(m.createdAt) }}</span>
                            <span v-if="Number(m.senderUserId) === Number(me)"
                                  class="chat-msg__receipt" :class="{ read: m.readAt }"
                                  :title="m.pending ? 'Senden…' : (m.readAt ? 'Gelesen' : 'Gesendet')">
                              <template v-if="m.pending">🕓</template>
                              <template v-else-if="m.readAt">✓✓</template>
                              <template v-else>✓</template>
                            </span>
                          </span>
                        </div>

                        <button v-if="m.id" class="chat-msg__react-btn" @click.stop="openReactions(m)" title="Reagieren">🙂</button>
                      </div>

                      <!-- quick-reaction popover -->
                      <div v-if="reactingTo === m.id" class="chat-quick" @click.stop>
                        <button v-for="e in quickReactions" :key="e" class="chat-quick__btn" @click="toggleReaction(m, e)">{{ e }}</button>
                      </div>

                      <!-- reaction chips -->
                      <div v-if="reactionGroups(m).length" class="chat-reactions">
                        <button v-for="g in reactionGroups(m)" :key="g.emoji"
                                class="chat-reaction" :class="{ mine: g.mine }"
                                @click.stop="toggleReaction(m, g.emoji)">
                          <span>{{ g.emoji }}</span><span class="chat-reaction__n">{{ g.count }}</span>
                        </button>
                      </div>
                    </div>
                  </div>
                </template>
                <div v-if="messages.length === 0" class="chat-empty">
                  Noch keine Nachrichten. Schreib die erste!
                </div>
              </div>

              <!-- pending entity share -->
              <div v-if="share && share.kind === 'entity'" class="chat-share-chip">
                <span class="chat-share-chip__icon">{{ entityIcon(share.entityType) }}</span>
                <span class="chat-share-chip__label">{{ share.label }}</span>
                <button class="chat-share-chip__x" @click="clearShare" title="Verwerfen">&times;</button>
              </div>

              <!-- recording bar -->
              <div v-if="isRecording" class="chat-recbar">
                <span class="chat-recdot"></span>
                <span class="chat-rectime">{{ recFmt }}</span>
                <span class="chat-recbar__hint">Aufnahme…</span>
                <button class="chat-recbar__btn cancel" @click="cancelRecording">Abbrechen</button>
                <button class="chat-recbar__btn stop" @click="stopRecording">Stopp</button>
              </div>
              <!-- preview bar -->
              <div v-else-if="recordedUrl" class="chat-recbar">
                <audio class="chat-recbar__audio" controls :src="recordedUrl"></audio>
                <button class="chat-recbar__btn cancel" @click="cancelRecording">Verwerfen</button>
                <button class="chat-recbar__btn send" @click="sendRecording">Senden</button>
              </div>
              <!-- normal composer -->
              <div v-else class="chat-composer">
                <div v-if="showEmoji" class="chat-emoji">
                  <button v-for="e in emojiList" :key="e" class="chat-emoji__btn" @click="insertEmoji(e)">{{ e }}</button>
                </div>
                <button class="chat-composer__icon" @click.stop="showEmoji = !showEmoji" title="Emoji">😊</button>
                <button class="chat-composer__icon" @click="startRecording" title="Sprachnachricht">🎤</button>
                <button class="chat-composer__icon" @click="pickImage" title="Bild senden">📷</button>
                <input ref="imgInput" type="file" accept="image/*" style="display:none" @change="onImagePicked" />
                <textarea
                  v-model="draft"
                  class="chat-composer__input"
                  rows="1"
                  placeholder="Nachricht…"
                  @input="onComposerInput"
                  @focus="onComposerFocus"
                  @keydown.enter.exact.prevent="submit"
                  @keydown.ctrl.enter.prevent="submit"
                  @keydown.meta.enter.prevent="submit"></textarea>
                <button class="chat-composer__send"
                        :disabled="!draft.trim() && !(share && share.kind === 'entity')"
                        @click="submit">
                  Senden
                </button>
              </div>
            </template>

            <div v-else class="chat-thread__placeholder">
              <template v-if="share && share.kind === 'entity'">
                Wähle links einen Kollegen, um „{{ share.label }}“ zu teilen.
              </template>
              <template v-else>
                Wähle links einen Kollegen aus, um zu chatten.
              </template>
            </div>
          </div>
        </div>
      </aside>
    </div>
  </Teleport>
</template>

<!-- NON-scoped on purpose: teleported to body, scoped styles don't apply reliably. -->
<style>
.chat-drawer-overlay {
  position: fixed;
  inset: 0;
  z-index: 6000;
  background: rgba(0, 0, 0, 0.25);
  display: flex;
  justify-content: flex-end;
}
.chat-drawer {
  width: 680px;
  max-width: 96vw;
  height: 100%;
  background: var(--theme-bg-primary, #fff);
  color: var(--theme-text-primary, #1a1a1a);
  display: flex;
  flex-direction: column;
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.18);
}
.chat-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  border-bottom: 1px solid var(--theme-border, #e5e7eb);
  font-weight: 600;
}
.chat-drawer__title { display: inline-flex; align-items: center; gap: 8px; }
.chat-drawer__actions { display: inline-flex; align-items: center; gap: 4px; }
.chat-conn { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.chat-conn.is-up { background: #2ecc71; }
.chat-conn.is-down { background: #e74c3c; }
.chat-drawer__icon-btn {
  border: 0; background: transparent; font-size: 16px; line-height: 1;
  cursor: pointer; color: inherit; padding: 2px 4px; border-radius: 6px;
}
.chat-drawer__icon-btn:hover { background: var(--theme-bg-secondary, #f1f1f1); }
.chat-drawer__close {
  border: 0; background: transparent; font-size: 24px; line-height: 1;
  cursor: pointer; color: inherit;
}
.chat-drawer__body { flex: 1; display: flex; min-height: 0; }

.chat-people {
  width: 240px;
  border-right: 1px solid var(--theme-border, #e5e7eb);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.chat-search { position: relative; padding: 8px; border-bottom: 1px solid var(--theme-border, #f1f1f1); }
.chat-search__input {
  width: 100%; padding: 6px 26px 6px 10px; font-size: 13px;
  border: 1px solid var(--theme-border, #d6dae0); border-radius: 8px;
  background: var(--theme-bg-primary, #fff); color: inherit; font-family: inherit;
}
.chat-search__clear {
  position: absolute; right: 14px; top: 50%; transform: translateY(-50%);
  border: 0; background: transparent; cursor: pointer; font-size: 16px; color: inherit; opacity: 0.6;
}
.chat-results { display: flex; flex-direction: column; }
.chat-result {
  width: 100%; display: flex; flex-direction: column; gap: 2px;
  padding: 8px 12px; border: 0; background: transparent; cursor: pointer;
  text-align: left; color: inherit; border-bottom: 1px solid var(--theme-border, #f1f1f1);
}
.chat-result:hover { background: var(--theme-bg-secondary, #f6f7f9); }
.chat-result__name { font-size: 12px; font-weight: 600; }
.chat-result__snippet { font-size: 12px; opacity: 0.75; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-result__time { font-size: 10px; opacity: 0.55; }

.chat-person {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: left;
  color: inherit;
  border-bottom: 1px solid var(--theme-border, #f1f1f1);
}
.chat-person:hover { background: var(--theme-bg-secondary, #f6f7f9); }
.chat-person.active { background: var(--theme-bg-secondary, #eef2ff); }
.chat-person__name { flex: 1; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-person__status { font-size: 11px; opacity: 0.6; }
.chat-person__unread {
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--theme-danger, #ef4444); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 18px; text-align: center; flex: 0 0 auto;
}

.chat-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.chat-dot.is-online { background: #2ecc71; }
.chat-dot.is-away { background: #f1c40f; }
.chat-dot.is-offline { background: #b0b6bd; }

.chat-thread { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.chat-thread__head {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; border-bottom: 1px solid var(--theme-border, #e5e7eb);
}
.chat-thread__head small { opacity: 0.6; }
.chat-typing { color: #2f6df6; opacity: 1 !important; font-style: italic; }
.chat-thread__back {
  display: none; /* desktop shows both panes — only needed on mobile single-pane */
  border: 0; background: transparent; cursor: pointer;
  font-size: 26px; line-height: 1; color: inherit;
  padding: 0 6px 0 0; margin: 0;
}
.chat-thread__messages { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 8px; }
.chat-thread__placeholder, .chat-empty {
  margin: auto; padding: 24px; text-align: center; opacity: 0.6; font-size: 14px;
}

.chat-day { display: flex; justify-content: center; margin: 6px 0 2px; }
.chat-day span {
  font-size: 11px; opacity: 0.6; background: var(--theme-bg-secondary, #eef0f3);
  padding: 2px 10px; border-radius: 999px;
}

.chat-msg { display: flex; }
.chat-msg.mine { justify-content: flex-end; }
.chat-msg.theirs { justify-content: flex-start; }
.chat-msg__wrap { display: flex; flex-direction: column; max-width: 78%; }
.chat-msg.mine .chat-msg__wrap { align-items: flex-end; }
.chat-msg.theirs .chat-msg__wrap { align-items: flex-start; }
.chat-msg__line { display: flex; align-items: center; gap: 6px; }
.chat-msg.mine .chat-msg__line { flex-direction: row-reverse; }
.chat-msg__bubble {
  padding: 7px 10px;
  border-radius: 12px;
  font-size: 14px;
  line-height: 1.35;
  word-break: break-word;
  white-space: pre-wrap;
}
.chat-msg.mine .chat-msg__bubble { background: #2f6df6; color: #fff; border-bottom-right-radius: 4px; }
.chat-msg.theirs .chat-msg__bubble { background: var(--theme-bg-secondary, #eef0f3); color: var(--theme-text-primary, #1a1a1a); border-bottom-left-radius: 4px; }
.chat-msg__bubble.pending { opacity: 0.55; }
.chat-msg__body { display: block; }
.chat-msg__body a { color: #2563eb; text-decoration: underline; word-break: break-all; }
.chat-msg__body a:hover { color: #1d4ed8; }
/* own (outgoing) bubbles have a blue background → keep links legible (white) */
.chat-msg.mine .chat-msg__body a { color: #fff; text-decoration: underline; }
.chat-msg__foot { display: flex; align-items: center; justify-content: flex-end; gap: 4px; margin-top: 2px; }
.chat-msg__time { font-size: 10px; opacity: 0.65; }
.chat-msg__receipt { font-size: 10px; opacity: 0.7; }
.chat-msg.mine .chat-msg__receipt.read { color: #b9f6ca; opacity: 1; }

.chat-msg__react-btn {
  border: 0; background: transparent; cursor: pointer; font-size: 14px; line-height: 1;
  opacity: 0.35; padding: 2px; border-radius: 50%; flex: 0 0 auto; transition: opacity .15s;
}
.chat-msg__line:hover .chat-msg__react-btn { opacity: 0.85; }
.chat-msg__react-btn:hover { opacity: 1 !important; background: var(--theme-bg-secondary, #eef0f3); }

/* voice message */
.chat-voice { display: flex; align-items: center; gap: 8px; }
.chat-voice__icon { font-size: 16px; line-height: 1; }
.chat-voice__audio { height: 32px; max-width: 220px; }
.chat-msg.mine .chat-voice__audio { filter: invert(1) hue-rotate(180deg); } /* legible controls on the blue bubble */
.chat-voice__tx { margin-top: 4px; font-size: 13px; line-height: 1.3; }
.chat-voice__tx.is-muted { opacity: 0.7; font-style: italic; }
.chat-image { display: block; line-height: 0; }
.chat-image__thumb { max-width: 220px; max-height: 240px; border-radius: 8px; cursor: pointer; object-fit: cover; }

/* reactions */
.chat-quick {
  display: inline-flex; gap: 2px; margin-top: 4px; padding: 3px 4px;
  background: var(--theme-bg-primary, #fff); border: 1px solid var(--theme-border, #d6dae0);
  border-radius: 999px; box-shadow: 0 2px 10px rgba(0,0,0,.12);
}
.chat-quick__btn {
  border: 0; background: transparent; cursor: pointer; font-size: 18px; line-height: 1;
  padding: 3px 5px; border-radius: 50%;
}
.chat-quick__btn:hover { background: var(--theme-bg-secondary, #eef0f3); transform: scale(1.15); }
.chat-reactions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.chat-reaction {
  display: inline-flex; align-items: center; gap: 3px;
  border: 1px solid var(--theme-border, #d6dae0); background: var(--theme-bg-secondary, #f1f3f5);
  border-radius: 999px; padding: 1px 7px; cursor: pointer; font-size: 12px; color: inherit;
}
.chat-reaction.mine { border-color: #2f6df6; background: rgba(47,109,246,.12); }
.chat-reaction__n { font-size: 11px; opacity: 0.75; }

/* entity share card inside a bubble */
.chat-card {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 8px; cursor: pointer; text-decoration: none;
  background: rgba(255, 255, 255, 0.18);
}
.chat-msg.theirs .chat-card { background: rgba(0, 0, 0, 0.05); }
.chat-card:hover { filter: brightness(1.05); text-decoration: underline; }
.chat-card__icon { font-size: 18px; line-height: 1; }
.chat-card__label { font-size: 13px; font-weight: 600; color: inherit; }

/* pending share chip above the composer */
.chat-share-chip {
  display: flex; align-items: center; gap: 8px;
  margin: 0 12px; padding: 6px 10px; border-radius: 8px;
  background: var(--theme-bg-secondary, #eef2ff);
  border: 1px solid var(--theme-border, #d6dae0);
}
.chat-share-chip__icon { font-size: 16px; }
.chat-share-chip__label { flex: 1; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-share-chip__x { border: 0; background: transparent; cursor: pointer; font-size: 18px; line-height: 1; color: inherit; opacity: 0.6; }

.chat-composer {
  position: relative;
  display: flex; align-items: flex-end; gap: 6px; padding: 10px 12px;
  border-top: 1px solid var(--theme-border, #e5e7eb);
}
.chat-composer__icon {
  border: 0; background: transparent; cursor: pointer; font-size: 20px; line-height: 1;
  padding: 6px; border-radius: 8px; flex: 0 0 auto;
}
.chat-composer__icon:hover { background: var(--theme-bg-secondary, #eef0f3); }
.chat-composer__input {
  flex: 1; resize: none; max-height: 120px;
  padding: 8px 10px; border: 1px solid var(--theme-border, #d6dae0);
  border-radius: 8px; font-size: 14px; font-family: inherit;
  background: var(--theme-bg-primary, #fff); color: inherit;
}
.chat-composer__send {
  border: 0; border-radius: 8px; padding: 0 16px; height: 38px;
  background: #2f6df6; color: #fff; cursor: pointer; font-size: 14px; flex: 0 0 auto;
}
.chat-composer__send:disabled { opacity: 0.5; cursor: default; }

/* emoji picker popover */
.chat-emoji {
  position: absolute; bottom: calc(100% + 6px); left: 8px; right: 8px;
  max-height: 200px; overflow-y: auto;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(34px, 1fr)); gap: 2px;
  padding: 8px; border-radius: 10px;
  background: var(--theme-bg-primary, #fff); border: 1px solid var(--theme-border, #d6dae0);
  box-shadow: 0 6px 24px rgba(0,0,0,.18); z-index: 10;
}
.chat-emoji__btn {
  border: 0; background: transparent; cursor: pointer; font-size: 20px; line-height: 1;
  padding: 5px; border-radius: 6px;
}
.chat-emoji__btn:hover { background: var(--theme-bg-secondary, #eef0f3); }

/* recording / preview bar */
.chat-recbar {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border-top: 1px solid var(--theme-border, #e5e7eb);
}
.chat-recbar__audio { height: 34px; flex: 1; min-width: 0; }
.chat-recbar__hint { font-size: 13px; opacity: 0.7; flex: 1; }
.chat-rectime { font-variant-numeric: tabular-nums; font-size: 14px; font-weight: 600; }
.chat-recdot { width: 11px; height: 11px; border-radius: 50%; background: #e74c3c; animation: chat-rec-pulse 1s infinite; }
@keyframes chat-rec-pulse { 0%,100% { opacity: 1 } 50% { opacity: 0.25 } }
.chat-recbar__btn {
  border: 0; border-radius: 8px; padding: 7px 14px; cursor: pointer; font-size: 13px; font-weight: 600;
}
.chat-recbar__btn.cancel { background: var(--theme-bg-secondary, #eef0f3); color: inherit; }
.chat-recbar__btn.stop { background: #e74c3c; color: #fff; }
.chat-recbar__btn.send { background: #2f6df6; color: #fff; }

@media (max-width: 1024px) {
  .chat-drawer-overlay {
    background: var(--theme-bg-primary, #fff);
    justify-content: stretch;
    overflow: hidden;
  }
  .chat-drawer {
    flex: 1 1 auto;
    width: 100%;
    max-width: 100%;
    height: 100%;
    box-shadow: none;
  }
}

@media (max-width: 768px) {
  .chat-people { width: 100%; border-right: 0; }
  .chat-thread { width: 100%; }
  .chat-drawer__body.peer-active .chat-people { display: none; }
  .chat-drawer__body:not(.peer-active) .chat-thread { display: none; }

  .chat-thread__back { display: inline-flex; align-items: center; }

  .chat-person { padding: 14px; }
  .chat-search__input,
  .chat-composer__input { font-size: 16px; }
  .chat-drawer__head { padding-top: calc(12px + env(safe-area-inset-top)); }
  .chat-composer { padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
}
</style>
