<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted } from 'vue'
import { useChatStore } from '@/stores/chat'
import { useSettingsStore } from '@/stores/settings'
import { useStreamingTts } from '@/composables/useStreamingTts'
import { useVoiceAudio, setVoiceVolume } from '@/composables/useVoiceAudio'
import { useListenMode } from '@/composables/useListenMode'
import { apiGet } from '@/api/client'
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'

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()
const settingsStore = useSettingsStore()

// ── Voice / TTS ───────────────────────────────────────────────────────────────
const voiceTtsEnabled = computed(() => settingsStore.voiceTtsReady)
const listenMode = useListenMode()
const audio = useVoiceAudio()
const showVolumeSlider = ref(false)

const tts = useStreamingTts({
  streamingContent: computed(() => store.streamingContent),
  streaming: computed(() => store.streaming),
  enabled: computed(() => listenMode.value && voiceTtsEnabled.value),
})

function lastAssistantContent(): string {
  const msgs = store.currentConversation?.messages ?? []
  return [...msgs].reverse().find((m) => m.role === 'assistant')?.content ?? ''
}

function toggleListen() {
  listenMode.value = !listenMode.value
  if (listenMode.value) {
    tts.speak(lastAssistantContent())
  } else {
    tts.stop()
  }
}

// ── 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() })

// ── RAG scope chip (full, non-briefing, non-workspace) ────────────────────────
const projects = ref<{ id: number; title: string }[]>([])
const scopeDropdownOpen = ref(false)
const scopePulse = ref(false)

const showScopeChip = computed(
  () => props.variant === 'full' && !props.briefingMode && !props.projectId
)

const scopeLabel = computed(() => {
  const id = store.ragProjectId
  if (id === -1) return 'All notes'
  if (id === null) return 'Orphan notes'
  return projects.value.find((p) => p.id === id)?.title ?? `Project ${id}`
})

async function loadProjects() {
  try {
    const data = await apiGet<{ projects: { id: number; title: string }[] }>('/api/projects?status=active')
    projects.value = data.projects ?? []
  } catch {
    projects.value = []
  }
}

async function onScopeSelect(value: number | null) {
  scopeDropdownOpen.value = false
  const convId = store.currentConversation?.id
  if (!convId) return
  await store.updateRagScope(convId, value)
  scopePulse.value = true
  setTimeout(() => { scopePulse.value = false }, 600)
}

// Pulse when model-driven scope change fires
watch(() => store.ragProjectId, () => {
  if (!showScopeChip.value) return
  scopePulse.value = true
  setTimeout(() => { scopePulse.value = false }, 600)
})

// ── 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 hasContextSidebar = computed(
  () => props.variant === 'full' && !props.briefingMode && !props.projectId &&
    (autoInjectedNotes.value.length > 0 || includedNotes.value.length > 0 || suggestedNotes.value.length > 0)
)

// ── 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,
    true,
    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, true)

  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(async () => {
  if (showScopeChip.value) await loadProjects()
  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 })
}

defineExpose({ focus, prefill, send })
</script>

<template>
  <!-- ═══════════════════════════════ FULL VARIANT ══════════════════════════════ -->
  <template v-if="variant === 'full'">
    <div class="chat-body" :class="{ 'chat-body--has-sidebar': hasContextSidebar }">
      <!-- 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: shown before a 2nd+ briefing slot message -->
            <div
              v-if="briefingMode && index > 0 && msg.role === 'assistant' && msg.metadata?.rss_item_ids"
              class="briefing-slot-separator"
            >
              <span>New Briefing Update</span>
            </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>
          <p
            v-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">
          <div class="context-sidebar-header">Auto-included</div>
          <div v-for="note in autoInjectedNotes" :key="note.id" class="context-note context-note-auto">
            <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)">&times;</button>
          </div>
        </template>
        <template v-if="suggestedNotes.length">
          <div class="context-sidebar-header" :class="{ 'context-sidebar-header-gap': autoInjectedNotes.length }">Suggested</div>
          <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>
        </template>
        <template v-if="includedNotes.length">
          <div class="context-sidebar-header" :class="{ 'context-sidebar-header-gap': autoInjectedNotes.length || suggestedNotes.length }">In Context</div>
          <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)">&times;</button>
          </div>
        </template>
      </aside>
    </div>

    <!-- Input area (hidden when readOnly) -->
    <div v-if="!readOnly" class="input-wrapper">
      <!-- Scope chip -->
      <div v-if="showScopeChip" class="scope-chip-row">
        <div class="scope-chip-wrapper">
          <button
            class="scope-chip"
            :class="{ pulse: scopePulse }"
            @click="scopeDropdownOpen = !scopeDropdownOpen"
            title="Change RAG scope"
          >
            <span class="scope-dot">⊙</span> {{ scopeLabel }}
          </button>
          <div v-if="scopeDropdownOpen" class="scope-dropdown">
            <button class="scope-option" :class="{ active: store.ragProjectId === null }" @click="onScopeSelect(null)">Orphan notes only</button>
            <button
              v-for="p in projects"
              :key="p.id"
              class="scope-option"
              :class="{ active: store.ragProjectId === p.id }"
              @click="onScopeSelect(p.id)"
            >{{ p.title }}</button>
            <button class="scope-option" :class="{ active: store.ragProjectId === -1 }" @click="onScopeSelect(-1)">All notes</button>
          </div>
        </div>
      </div>

      <!-- Listen / volume controls -->
      <div v-if="voiceTtsEnabled" class="voice-controls">
        <div class="volume-wrapper">
          <button
            class="btn-voice"
            :class="{ 'btn-voice--active': listenMode, 'btn-voice--busy': tts.speaking.value }"
            @click="toggleListen"
            :title="listenMode ? 'Stop auto-read' : 'Read responses aloud'"
            aria-label="Toggle listen mode"
          >
            <svg v-if="!tts.speaking.value" width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
              <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
            </svg>
            <svg v-else width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
              <path d="M18 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C21.8 14.82 22 13.43 22 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
            </svg>
          </button>
          <button
            class="btn-voice btn-volume-icon"
            @click="showVolumeSlider = !showVolumeSlider"
            :title="`Volume: ${Math.round(audio.volume.value * 100)}%`"
            aria-label="Volume"
          >
            <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
              <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>
            </svg>
          </button>
          <div v-if="showVolumeSlider" class="volume-popup">
            <input
              type="range" min="0" max="1" step="0.05"
              :value="audio.volume.value"
              @input="setVoiceVolume(+($event.target as HTMLInputElement).value)"
              class="volume-range"
              aria-label="Volume"
            />
            <span class="volume-pct">{{ Math.round(audio.volume.value * 100) }}%</span>
          </div>
        </div>
        <button
          v-if="tts.speaking.value"
          class="btn-voice"
          @click="tts.stop()"
          title="Stop playback"
        >
          <svg width="13" height="13" viewBox="0 0 14 14" fill="currentColor"><rect width="14" height="14" rx="2"/></svg>
        </button>
      </div>

      <!-- Unified input bar -->
      <ChatInputBar
        ref="inputBarRef"
        :placeholder="placeholder"
        :briefing-mode="briefingMode"
        @submit="onSubmit"
        @abort="store.cancelGeneration()"
      />
    </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 ── */
.chat-body {
  flex: 1;
  display: flex;
  min-height: 0;
  overflow: hidden;
}

.messages-container {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  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 {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.25rem 0 0.5rem;
  color: var(--color-text-muted, #888);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.7;
}
.briefing-slot-separator::before,
.briefing-slot-separator::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--color-border, #333);
  opacity: 0.5;
}

/* Context sidebar */
.context-sidebar {
  width: 200px;
  min-width: 160px;
  max-width: 220px;
  border-left: 1px solid var(--color-border);
  padding: 0.75rem 0.5rem;
  overflow-y: auto;
  background: var(--color-bg-secondary);
  font-size: 0.78rem;
}
.context-sidebar-header {
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  margin-bottom: 0.35rem;
}
.context-sidebar-header-gap { margin-top: 0.75rem; }
.context-note {
  display: flex;
  align-items: center;
  gap: 0.2rem;
  margin-bottom: 0.25rem;
}
.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 */
.input-wrapper {
  border-top: 1px solid var(--color-border);
  padding: 0.5rem 1rem 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* Scope chip */
.scope-chip-row { display: flex; }
.scope-chip-wrapper { position: relative; }
.scope-chip {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: 20px;
  padding: 0.2rem 0.65rem;
  font-size: 0.75rem;
  color: var(--color-text-muted);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.scope-chip:hover { border-color: var(--color-primary); color: var(--color-primary); }
.scope-chip.pulse { animation: pulse-chip 0.6s ease; }
@keyframes pulse-chip {
  0%, 100% { border-color: var(--color-border); }
  50% { border-color: var(--color-primary); color: var(--color-primary); box-shadow: 0 0 6px rgba(99,102,241,0.4); }
}
.scope-dot { font-size: 0.6rem; }
.scope-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 180px;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 16px var(--color-shadow);
  z-index: 20;
  overflow: hidden;
}
.scope-option {
  display: block;
  width: 100%;
  padding: 0.45rem 0.75rem;
  text-align: left;
  background: none;
  border: none;
  font-size: 0.85rem;
  color: var(--color-text);
  cursor: pointer;
}
.scope-option:hover { background: var(--color-bg-secondary); }
.scope-option.active { color: var(--color-primary); font-weight: 600; }

/* Voice controls */
.voice-controls {
  display: flex;
  align-items: center;
  gap: 0.2rem;
}
.volume-wrapper { position: relative; display: flex; align-items: center; gap: 0.1rem; }
.btn-voice {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-text-muted);
  opacity: 0.65;
  padding: 0.2rem;
  display: flex;
  align-items: center;
  border-radius: var(--radius-sm);
  transition: opacity 0.15s, color 0.15s;
}
.btn-voice:hover { opacity: 1; }
.btn-voice--active { opacity: 1; color: var(--color-primary); }
.btn-voice--busy { opacity: 1; color: #f59e0b; }
.volume-popup {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  z-index: 20;
  box-shadow: 0 4px 16px var(--color-shadow);
}
.volume-range { width: 80px; }
.volume-pct { font-size: 0.75rem; color: var(--color-text-muted); white-space: nowrap; }

/* 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: 700;
  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-muted);
  font-size: 0.9rem;
  text-align: center;
  padding: 2rem 1rem;
  font-family: 'Fraunces', Georgia, serif;
  font-style: italic;
  color: var(--color-accent-warm, #d4a017);
}

/* ── 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);
  font-style: italic;
}
.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: 600;
}
.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: 600;
  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>
