<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'
import { useChatStore } from '@/stores/chat'
import ChatMessage from '@/components/ChatMessage.vue'
import ChatStreamingBubble from '@/components/ChatStreamingBubble.vue'
import ChatInputBar from '@/components/ChatInputBar.vue'
import ToolCallCard from '@/components/ToolCallCard.vue'
import type { Message, ToolCallRecord } from '@/types/chat'
import { Mic, ChevronDown, X } from 'lucide-vue-next'

const props = withDefaults(defineProps<{
  variant: 'full' | 'widget'
  /** Workspace: pins RAG scope and workspace_project_id */
  projectId?: number
  /** Briefing: hides scope chip, hides note picker */
  briefingMode?: boolean
  /** Hides input bar — for read-only historical views */
  readOnly?: boolean
  placeholder?: string
  autoFocus?: boolean
}>(), {
  briefingMode: false,
  readOnly: false,
  autoFocus: false,
})

const emit = defineEmits<{
  /** Widget only: emitted when a new conversation is created */
  'conversation-started': [convId: number]
}>()

const store = useChatStore()

// ── Scroll ────────────────────────────────────────────────────────────────────
const messagesEl = ref<HTMLElement | null>(null)

function scrollToBottom() {
  nextTick(() => {
    if (messagesEl.value) {
      messagesEl.value.scrollTop = messagesEl.value.scrollHeight
    }
  })
}

watch(() => store.streamingContent, () => scrollToBottom())
watch(() => store.currentConversation?.messages.length, () => scrollToBottom())

// Notify CalendarView when an event is created/updated/deleted via tool
const _calendarToolNames = new Set(['create_event', 'update_event', 'delete_event'])
const _seenCalendarToolIdx = new Set<number>()
watch(() => store.streamingToolCalls, (calls) => {
  calls.forEach((tc, i) => {
    if (!_seenCalendarToolIdx.has(i) && _calendarToolNames.has(tc.function) && tc.status === 'success') {
      _seenCalendarToolIdx.add(i)
      document.dispatchEvent(new CustomEvent('fable:calendar-changed'))
    }
  })
}, { deep: true })
watch(() => store.streaming, (s) => { if (!s) _seenCalendarToolIdx.clear() })

// ── Note context (full variant — included / suggested / auto-injected notes) ──
const includedNoteIds = ref<Set<number>>(new Set())
const includedNotes = ref<{ id: number; title: string }[]>([])
const suggestedNotes = ref<{ id: number; title: string; score?: number | null }[]>([])
const autoInjectedNotes = ref<{ id: number; title: string; score?: number | null }[]>([])
const excludedNoteIds = ref<number[]>([])

watch(
  () => store.lastContextMeta,
  (meta) => {
    if (!meta || props.variant !== 'full') return
    const alreadyIncluded = includedNoteIds.value
    const alreadyAutoInjected = new Set(autoInjectedNotes.value.map((n) => n.id))
    const alreadySuggested = new Set(suggestedNotes.value.map((n) => n.id))

    for (const note of meta.auto_injected_notes ?? []) {
      if (!alreadyAutoInjected.has(note.id) && !alreadyIncluded.has(note.id)) {
        autoInjectedNotes.value.push(note)
        alreadyAutoInjected.add(note.id)
      }
    }
    for (const note of meta.auto_notes ?? []) {
      if (note.auto_injected) {
        if (!alreadyAutoInjected.has(note.id) && !alreadyIncluded.has(note.id)) {
          autoInjectedNotes.value.push(note)
          alreadyAutoInjected.add(note.id)
        }
      } else {
        if (!alreadyIncluded.has(note.id) && !alreadySuggested.has(note.id) && !alreadyAutoInjected.has(note.id)) {
          suggestedNotes.value.push(note)
        }
      }
    }
  }
)

function includeNote(note: { id: number; title: string }) {
  if (includedNoteIds.value.has(note.id)) return
  includedNoteIds.value = new Set([...includedNoteIds.value, note.id])
  includedNotes.value.push(note)
  suggestedNotes.value = suggestedNotes.value.filter((n) => n.id !== note.id)
  autoInjectedNotes.value = autoInjectedNotes.value.filter((n) => n.id !== note.id)
}

function excludeAutoNote(noteId: number) {
  autoInjectedNotes.value = autoInjectedNotes.value.filter((n) => n.id !== noteId)
  if (!excludedNoteIds.value.includes(noteId)) {
    excludedNoteIds.value = [...excludedNoteIds.value, noteId]
  }
}

function removeIncludedNote(noteId: number) {
  includedNoteIds.value = new Set([...includedNoteIds.value].filter((id) => id !== noteId))
  const removed = includedNotes.value.find((n) => n.id === noteId)
  includedNotes.value = includedNotes.value.filter((n) => n.id !== noteId)
  if (removed && !suggestedNotes.value.some((n) => n.id === noteId)) {
    suggestedNotes.value.push(removed)
  }
}

const contextCount = computed(
  () => autoInjectedNotes.value.length + suggestedNotes.value.length + includedNotes.value.length
)

const hasContextData = computed(
  () => props.variant === 'full' && !props.briefingMode && !props.projectId && contextCount.value > 0
)

// ── Narrow-viewport sidebar toggle ────────────────────────────────────────────
const NARROW_BREAKPOINT = 1200
const isNarrow = ref(typeof window !== 'undefined' && window.innerWidth < NARROW_BREAKPOINT)
const sidebarOpen = ref(false)

function onResize() {
  isNarrow.value = window.innerWidth < NARROW_BREAKPOINT
}
onMounted(() => window.addEventListener('resize', onResize))
onUnmounted(() => window.removeEventListener('resize', onResize))

function toggleContextSidebar() {
  sidebarOpen.value = !sidebarOpen.value
}

const hasContextSidebar = computed(() => hasContextData.value && sidebarOpen.value)

// ── Collapsible sections (per-conversation, localStorage) ─────────────────────
type SectionKey = 'auto' | 'suggested' | 'included'
const collapsedSections = ref<Set<SectionKey>>(new Set())

function storageKey(): string | null {
  const id = store.currentConversation?.id
  return id ? `fa_chat_ctx_collapsed_${id}` : null
}

function loadCollapsed() {
  const key = storageKey()
  if (!key) { collapsedSections.value = new Set(); return }
  try {
    const raw = localStorage.getItem(key)
    if (!raw) { collapsedSections.value = new Set(); return }
    const arr = JSON.parse(raw) as SectionKey[]
    collapsedSections.value = new Set(arr)
  } catch {
    collapsedSections.value = new Set()
  }
}

function saveCollapsed() {
  const key = storageKey()
  if (!key) return
  try {
    localStorage.setItem(key, JSON.stringify([...collapsedSections.value]))
  } catch { /* ignore */ }
}

function toggleSection(key: SectionKey) {
  const next = new Set(collapsedSections.value)
  if (next.has(key)) next.delete(key)
  else next.add(key)
  collapsedSections.value = next
  saveCollapsed()
}

watch(() => store.currentConversation?.id, () => loadCollapsed(), { immediate: true })

// ── Empty-state (full variant, fresh/empty conversation) ─────────────────────
const showEmptyState = computed(
  () =>
    props.variant === 'full'
    && !props.briefingMode
    && !props.projectId
    && !store.streaming
    && (store.currentConversation?.messages.length ?? 0) === 0
)

const recentConversations = computed(() => {
  const currentId = store.currentConversation?.id
  return store.conversations
    .filter((c) => c.id !== currentId && c.message_count > 0)
    .slice(0, 5)
})

function startVoiceMode() {
  window.dispatchEvent(new CustomEvent('voice:ptt-toggle'))
}

// ── Send ──────────────────────────────────────────────────────────────────────
const inputBarRef = ref<InstanceType<typeof ChatInputBar> | null>(null)

async function onSubmit(payload: { content: string; contextNoteId?: number }) {
  if (props.variant === 'widget') {
    await widgetSend(payload)
    return
  }
  if (!store.currentConversation) return
  await store.sendMessage(
    payload.content,
    payload.contextNoteId,
    includedNoteIds.value.size ? [...includedNoteIds.value] : undefined,
    false,
    undefined,
    excludedNoteIds.value.length ? excludedNoteIds.value : undefined,
    props.projectId ?? store.ragProjectId,
    props.projectId ?? undefined,
  )
  scrollToBottom()
  nextTick(() => inputBarRef.value?.focus())
}

// ── Widget state ──────────────────────────────────────────────────────────────
const widgetConvId = ref<number | null>(null)
const widgetQuery = ref('')
const widgetDone = ref(false)
const widgetFinalContent = ref('')
const widgetFinalToolCalls = ref<ToolCallRecord[]>([])

const isConversational = computed(
  () => widgetDone.value && widgetFinalToolCalls.value.length === 0
)

function clearWidget() {
  widgetConvId.value = null
  widgetDone.value = false
  widgetQuery.value = ''
  widgetFinalContent.value = ''
  widgetFinalToolCalls.value = []
}

async function widgetSend(payload: { content: string; contextNoteId?: number }) {
  widgetConvId.value = null
  widgetDone.value = false
  widgetFinalContent.value = ''
  widgetFinalToolCalls.value = []
  widgetQuery.value = payload.content

  const conv = await store.createConversation()
  await store.fetchConversation(conv.id)
  widgetConvId.value = conv.id
  emit('conversation-started', conv.id)

  await store.sendMessage(payload.content, payload.contextNoteId, undefined, false)

  const msgs = store.currentConversation?.messages ?? []
  const lastAssistant = [...msgs].reverse().find((m: Message) => m.role === 'assistant')
  widgetFinalContent.value = lastAssistant?.content ?? ''
  widgetFinalToolCalls.value = (lastAssistant?.tool_calls ?? []) as ToolCallRecord[]
  widgetDone.value = true
}

// ── Save as note ──────────────────────────────────────────────────────────────
async function handleSaveAsNote(messageId: number) {
  try {
    await store.saveMessageAsNote(messageId)
    const { useToastStore } = await import('@/stores/toast')
    useToastStore().show('Saved as note')
  } catch {
    const { useToastStore } = await import('@/stores/toast')
    useToastStore().show('Failed to save as note', 'error')
  }
}

// ── Lifecycle ─────────────────────────────────────────────────────────────────
onMounted(() => {
  if (props.autoFocus) nextTick(() => inputBarRef.value?.focus())
})

// ── Exposed ───────────────────────────────────────────────────────────────────
function focus() {
  inputBarRef.value?.focus()
}

function prefill(text: string) {
  inputBarRef.value?.prefill(text)
}

async function send(text: string) {
  await onSubmit({ content: text })
}

// ── Briefing slot separator helper ────────────────────────────────────────────
function hasEarlierBriefingSlot(index: number): boolean {
  const msgs = store.currentConversation?.messages ?? []
  for (let i = 0; i < index; i++) {
    const m = msgs[i]
    const meta = m?.metadata as Record<string, unknown> | null | undefined
    if (
      m?.role === 'assistant' &&
      meta?.briefing_slot != null &&
      !meta?.briefing_intermediate
    ) {
      return true
    }
  }
  return false
}

defineExpose({ focus, prefill, send, contextCount, sidebarOpen, toggleContextSidebar, hasContextData })
</script>

<template>
  <!-- ═══════════════════════════════ FULL VARIANT ══════════════════════════════ -->
  <template v-if="variant === 'full'">
    <div class="chat-full">
      <!-- Message list -->
      <div ref="messagesEl" class="messages-container">
        <div class="messages-inner">
          <template
            v-for="(msg, index) in store.currentConversation?.messages ?? []"
            :key="msg.id"
          >
            <!-- Briefing slot separator: before any non-first slot message (skip intermediate tool-call rows) -->
            <div
              v-if="briefingMode && msg.role === 'assistant' && msg.metadata?.briefing_slot && !msg.metadata?.briefing_intermediate && hasEarlierBriefingSlot(index)"
              class="briefing-slot-separator"
            ></div>
            <ChatMessage
              :message="msg"
              @save-as-note="handleSaveAsNote"
            />
          </template>
          <!-- Streaming bubble -->
          <ChatStreamingBubble v-if="store.streaming" />
          <!-- Queued messages -->
          <template v-if="store.queuedMessages.length">
            <div
              v-for="(q, i) in store.queuedMessages"
              :key="`queued-${i}`"
              class="chat-message role-user queued-message"
            >
              <div class="message-bubble queued-bubble">
                <div class="queued-badge">Queued</div>
                <div class="message-content">{{ q.content }}</div>
              </div>
            </div>
            <div class="queued-clear-row">
              <button class="queued-clear-btn" @click="store.clearQueue()">
                Cancel {{ store.queuedMessages.length }} queued
              </button>
            </div>
          </template>
          <div v-if="showEmptyState" class="chat-empty-state">
            <h2 class="empty-greeting">What's on your mind?</h2>
            <div v-if="recentConversations.length" class="empty-recent">
              <div class="empty-section-label">Jump back in</div>
              <router-link
                v-for="conv in recentConversations"
                :key="conv.id"
                :to="`/chat/${conv.id}`"
                class="empty-recent-item"
              >
                <span class="empty-recent-title">{{ conv.title || 'Untitled conversation' }}</span>
                <span class="empty-recent-count">{{ conv.message_count }} msg</span>
              </router-link>
            </div>
            <button class="empty-voice-btn" @click="startVoiceMode" title="Start in voice mode">
              <Mic class="voice-icon" :size="16" />
              <span>Start in voice mode</span>
            </button>
          </div>
          <p
            v-else-if="!store.currentConversation?.messages.length && !store.streaming"
            class="empty-msg"
          >Start a conversation.</p>
        </div>
      </div>

      <!-- Context sidebar (full, non-briefing, non-workspace) -->
      <aside v-if="hasContextSidebar" class="context-sidebar">
        <template v-if="autoInjectedNotes.length">
          <button
            class="context-sidebar-header"
            :class="{ collapsed: collapsedSections.has('auto') }"
            @click="toggleSection('auto')"
          >
            <ChevronDown class="chev" :size="16" />
            <span>Auto-included</span>
            <span class="section-count">{{ autoInjectedNotes.length }}</span>
          </button>
          <div v-show="!collapsedSections.has('auto')">
            <div v-for="note in autoInjectedNotes" :key="note.id" class="context-note context-note-auto">
              <span class="auto-pill" title="Auto-included by relevance">AUTO</span>
              <router-link :to="`/notes/${note.id}`" class="context-note-name">{{ note.title }}</router-link>
              <span
                v-if="note.score != null"
                class="context-note-score"
                :class="{ 'score-high': note.score >= 0.75, 'score-medium': note.score >= 0.60 && note.score < 0.75, 'score-low': note.score < 0.60 }"
              >{{ Math.round(note.score * 100) }}%</span>
              <button class="context-note-remove" @click="excludeAutoNote(note.id)" title="Remove from context"><X :size="16" /></button>
            </div>
          </div>
        </template>
        <template v-if="suggestedNotes.length">
          <button
            class="context-sidebar-header"
            :class="{ collapsed: collapsedSections.has('suggested'), 'context-sidebar-header-gap': autoInjectedNotes.length }"
            @click="toggleSection('suggested')"
          >
            <ChevronDown class="chev" :size="16" />
            <span>Suggested</span>
            <span class="section-count">{{ suggestedNotes.length }}</span>
          </button>
          <div v-show="!collapsedSections.has('suggested')">
            <div v-for="note in suggestedNotes" :key="note.id" class="context-note context-note-suggested">
              <router-link :to="`/notes/${note.id}`" class="context-note-name">{{ note.title }}</router-link>
              <span
                v-if="note.score != null"
                class="context-note-score"
                :class="{ 'score-high': note.score >= 0.75, 'score-medium': note.score >= 0.60 && note.score < 0.75, 'score-low': note.score < 0.60 }"
              >{{ Math.round(note.score * 100) }}%</span>
              <button class="context-note-add" @click="includeNote(note)" title="Add to context">+</button>
            </div>
          </div>
        </template>
        <template v-if="includedNotes.length">
          <button
            class="context-sidebar-header"
            :class="{ collapsed: collapsedSections.has('included'), 'context-sidebar-header-gap': autoInjectedNotes.length || suggestedNotes.length }"
            @click="toggleSection('included')"
          >
            <ChevronDown class="chev" :size="16" />
            <span>In Context</span>
            <span class="section-count">{{ includedNotes.length }}</span>
          </button>
          <div v-show="!collapsedSections.has('included')">
            <div v-for="note in includedNotes" :key="note.id" class="context-note context-note-included">
              <router-link :to="`/notes/${note.id}`" class="context-note-name">{{ note.title }}</router-link>
              <button class="context-note-remove" @click="removeIncludedNote(note.id)" title="Remove from context"><X :size="16" /></button>
            </div>
          </div>
        </template>
      </aside>

      <!-- Input area (hidden when readOnly) -->
      <div v-if="!readOnly" class="input-wrapper">
      <!-- Unified input bar -->
      <ChatInputBar
        ref="inputBarRef"
        :placeholder="placeholder"
        :briefing-mode="briefingMode"
        @submit="onSubmit"
        @abort="store.cancelGeneration()"
      />
      </div>
    </div>
  </template>

  <!-- ══════════════════════════════ WIDGET VARIANT ══════════════════════════════ -->
  <template v-else>
    <ChatInputBar
      ref="inputBarRef"
      pill
      :placeholder="placeholder ?? 'Start a new chat… (Enter to send)'"
      :briefing-mode="false"
      @submit="onSubmit"
      @abort="store.cancelGeneration()"
    />

    <!-- Inline response area -->
    <div v-if="widgetConvId" class="widget-response">
      <div class="widget-query">{{ widgetQuery }}</div>

      <!-- Tool calls -->
      <div v-if="store.streaming && store.streamingToolCalls.length" class="widget-tool-calls">
        <ToolCallCard v-for="(tc, i) in store.streamingToolCalls" :key="i" :tool-call="tc" />
      </div>
      <div v-else-if="widgetDone && widgetFinalToolCalls.length" class="widget-tool-calls">
        <ToolCallCard v-for="(tc, i) in widgetFinalToolCalls" :key="i" :tool-call="tc" />
      </div>

      <!-- Streaming / final text -->
      <div v-if="store.streaming" class="widget-text streaming">
        <div v-if="store.streamingStatus && !store.streamingContent" class="widget-status">
          <span class="widget-status-dot"></span>{{ store.streamingStatus }}
        </div>
        <span v-else-if="store.streamingContent">{{ store.streamingContent }}</span>
        <span v-else class="thinking-dots">...</span>
      </div>
      <div v-else-if="widgetDone && widgetFinalContent" class="widget-text">
        {{ widgetFinalContent }}
      </div>

      <!-- Actions -->
      <div class="widget-actions" :class="{ conversational: isConversational }">
        <router-link
          :to="`/chat/${widgetConvId}`"
          class="btn-open-chat"
          :class="{ prominent: isConversational }"
        >{{ isConversational ? 'Continue the conversation →' : 'Think it through in Chat →' }}</router-link>
        <button class="btn-clear-response" @click="clearWidget">Clear</button>
      </div>
    </div>
  </template>
</template>

<style scoped>
/* ── Full variant layout ──
 * Single grid owns the reading column + context sidebar + input bar so
 * messages and input bar share one centered reading track while the
 * context sidebar spans the full height from header to bottom of view.
 * Reading column is always centered (3-column grid: 1fr | content | 1fr).
 * The context sidebar overlays the right gutter so it never shifts layout.
 */
.chat-full {
  position: relative;
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns:
    1fr
    minmax(0, var(--chat-reading-width))
    1fr;
  grid-template-rows: minmax(0, 1fr) auto;
  overflow: hidden;
}

.messages-container {
  grid-column: 2;
  grid-row: 1;
  overflow-y: auto;
  padding: 1rem 1rem 0.75rem;
  mask-image: linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent);
  -webkit-mask-image: linear-gradient(to bottom, transparent, black 20px, black calc(100% - 20px), transparent);
}
.messages-inner {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  justify-content: flex-end;
}

/* Briefing slot separator */
.briefing-slot-separator {
  height: 1px;
  margin: 1.25rem 0 0.75rem;
  background: var(--color-border, #333);
  opacity: 0.35;
}

/* Context sidebar — overlays right gutter, never shifts reading column */
.context-sidebar {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--chat-context-sidebar-width);
  border-left: 1px solid var(--color-border);
  padding: 0.75rem 0.5rem;
  overflow-y: auto;
  background: var(--color-bg-secondary);
  font-size: 0.78rem;
  z-index: 2;
}
.context-sidebar-header {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  width: 100%;
  background: none;
  border: none;
  padding: 0.15rem 0.1rem;
  font-family: inherit;
  font-size: 0.65rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  margin-bottom: 0.35rem;
  cursor: pointer;
  text-align: left;
}
.context-sidebar-header:hover { color: var(--color-text); }
.context-sidebar-header .chev {
  transition: transform 0.15s ease;
}
.context-sidebar-header.collapsed .chev {
  transform: rotate(-90deg);
}
.context-sidebar-header .section-count {
  margin-left: auto;
  color: var(--color-text-muted);
  font-weight: 500;
  background: var(--color-bg);
  padding: 0.05rem 0.3rem;
  border-radius: 8px;
}
.context-sidebar-header-gap { margin-top: 0.75rem; }
.context-note {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-bottom: 0.25rem;
  padding: 0.2rem 0.35rem;
  border-radius: var(--radius-sm);
}
.context-note-auto {
  background: color-mix(in srgb, var(--color-text-muted) 8%, transparent);
  opacity: 0.85;
}
.context-note-included {
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
  border-left: 2px solid var(--color-primary);
}
.auto-pill {
  font-size: 0.55rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
  background: var(--color-bg);
  color: var(--color-text-muted);
  flex-shrink: 0;
}
.context-note-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.78rem;
}
.context-note-name:hover { color: var(--color-primary); }
.context-note-score {
  font-size: 0.65rem;
  padding: 0.05rem 0.25rem;
  border-radius: 4px;
  background: var(--color-bg);
}
.score-high { color: #22c55e; }
.score-medium { color: #f59e0b; }
.score-low { color: var(--color-text-muted); }
.context-note-remove, .context-note-add {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: 0.9rem;
  line-height: 1;
  padding: 0 0.1rem;
  flex-shrink: 0;
}
.context-note-remove:hover { color: #ef4444; }
.context-note-add:hover { color: var(--color-primary); }

/* Input wrapper — lives in the same reading column as messages */
.input-wrapper {
  grid-column: 2;
  grid-row: 2;
  padding: 0.5rem 1rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* Queued messages */
.queued-message { opacity: 0.45; }
.queued-bubble {
  background: var(--color-bg-secondary);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.75rem;
  font-size: 0.85rem;
}
.queued-badge {
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  font-weight: 500;
  margin-bottom: 0.2rem;
}
.queued-clear-row {
  display: flex;
  justify-content: center;
  padding: 0.25rem 0;
}
.queued-clear-btn {
  background: none;
  border: none;
  font-size: 0.75rem;
  color: var(--color-text-muted);
  cursor: pointer;
  text-decoration: underline;
}

.empty-msg {
  color: var(--color-text-secondary);
  font-size: 0.9rem;
  text-align: center;
  padding: 2rem 1rem;
  font-family: 'Fraunces', Georgia, serif;
}

.chat-empty-state {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 1.5rem;
  padding: 3rem 1rem 2rem;
  max-width: 520px;
  margin: 0 auto;
}

.empty-greeting {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 400;
  font-size: 1.75rem;
  color: var(--color-text-secondary);
  text-align: center;
  margin: 0;
}

.empty-section-label {
  font-size: 0.7rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  margin-bottom: 0.5rem;
  padding-left: 0.25rem;
}

.empty-recent {
  display: flex;
  flex-direction: column;
}

.empty-recent-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.75rem 0.9rem;
  border-radius: var(--radius-md, 10px);
  border: 1px solid var(--color-border);
  background: var(--color-bg-elevated, rgba(255, 255, 255, 0.02));
  color: var(--color-text);
  text-decoration: none;
  font-size: 0.9rem;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}

.empty-recent-item + .empty-recent-item {
  margin-top: 0.4rem;
}

.empty-recent-item:hover {
  border-color: var(--color-primary);
  background: rgba(91, 74, 138, 0.05);
  transform: translateX(2px);
}

.empty-recent-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
}

.empty-recent-count {
  font-size: 0.7rem;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.empty-voice-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.75rem 1.2rem;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text);
  font-size: 0.9rem;
  cursor: pointer;
  align-self: center;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.empty-voice-btn:hover {
  border-color: var(--color-primary);
  background: linear-gradient(135deg, #5B4A8A, #3F3560);
  color: #fff;
  box-shadow: 0 4px 16px rgba(91, 74, 138, 0.35);
}

.empty-voice-btn .voice-icon {
  font-size: 1.05rem;
}

/* ── Widget variant ── */
.widget-response {
  margin-top: 0.75rem;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 0.75rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.widget-query {
  font-size: 0.8rem;
  color: var(--color-text-muted);
}
.widget-text {
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--color-text);
}
.widget-text.streaming { color: var(--color-text-muted); }
.thinking-dots { color: var(--color-text-muted); letter-spacing: 0.2em; }
.widget-status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--color-text-muted);
}
.widget-status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary);
  animation: blink 1s infinite;
  flex-shrink: 0;
}
@keyframes blink { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }
.widget-tool-calls { display: flex; flex-direction: column; gap: 0.25rem; }
.widget-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-top: 0.25rem;
  border-top: 1px solid var(--color-border);
  flex-wrap: wrap;
}
.btn-open-chat {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  text-decoration: none;
}
.btn-open-chat:hover { color: var(--color-primary); }
.btn-open-chat.prominent {
  font-size: 0.9rem;
  color: var(--color-primary);
  font-weight: 500;
}
.btn-clear-response {
  background: none;
  border: none;
  font-size: 0.75rem;
  color: var(--color-text-muted);
  cursor: pointer;
  margin-left: auto;
}
.btn-clear-response:hover { color: var(--color-text); }

/* Streaming bubble styles — applied via :deep to ChatStreamingBubble children */
:deep(.streaming-bubble) {
  max-width: 85%;
  padding: 0.75rem 1rem;
  border-radius: 16px 16px 16px 4px;
  background: var(--color-bg-card);
  border-left: 2px solid var(--color-primary);
  box-shadow: 0 1px 4px var(--color-shadow);
}
:deep(.streaming-tool-calls) { margin-bottom: 0.5rem; }
:deep(.streaming-status-line) {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin-bottom: 0.25rem;
}
:deep(.streaming-status-dot) {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary);
  animation: blink 1s infinite;
  flex-shrink: 0;
}
:deep(.thinking-block) {
  margin-bottom: 0.5rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
:deep(.thinking-summary) {
  padding: 0.25rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  cursor: pointer;
  list-style: none;
  background: var(--color-bg-secondary);
  display: flex;
  align-items: center;
  gap: 0.3rem;
}
:deep(.thinking-summary::-webkit-details-marker) { display: none; }
:deep(.thinking-text) {
  margin: 0;
  padding: 0.5rem;
  font-size: 0.8rem;
  color: var(--color-text-secondary);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 300px;
  overflow-y: auto;
  background: var(--color-bg-secondary);
  border-top: 1px solid var(--color-border);
}
:deep(.typing-indicator) {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-primary);
  animation: blink 1s infinite;
  margin-left: 4px;
  vertical-align: middle;
}
</style>
