<script setup lang="ts">
import { ref, reactive, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import type { Agent } from '../../types/agent'
import { endpoints } from '../../config/endpoints'
import { useVoiceCapture } from '../../composables/useVoiceCapture'
import { useAgentTerminal } from '../../composables/useAgentTerminal'
import { useCanvasStore } from '../../stores/canvas'
import ChatInput from './ChatInput.vue'
import TranscriptCard from './TranscriptCard.vue'
import InputSettings from './InputSettings.vue'
import ConversationHistory from './ConversationHistory.vue'
import AgentTerminal from './AgentTerminal.vue'

interface ClaudeUsage {
  subscription: { type: string; tier: string; label: string; multiplier: number }
  today: { messages: number; outputTokens: number; sessions: number }
  daily: { used: number; limit: number; percent: number }
  weekly: { used: number; limit: number; percent: number }
  status: 'normal' | 'elevated' | 'extended' | 'limit_approaching'
}

interface ChatMessage {
  id: number
  role: 'user' | 'agent'
  content: string
  status: 'sent' | 'thinking' | 'done'
  uuid?: string
  toolCalls?: string[]
  intervention?: {
    type: 'permission' | 'question' | 'plan'
    requestId?: string
    toolName?: string
    toolInput?: unknown
    options?: Array<{ label: string; description?: string }>
    resolved?: boolean
    decision?: string
  }
}

interface SessionInfo {
  id: string
  startTime: string
  messageCount: number
  model: string
}

let idCounter = 0

const props = defineProps<{
  agent: Agent
  anchorRect: DOMRect | null
  visible: boolean
  startRecording?: boolean
}>()

const emit = defineEmits<{
  close: []
  submit: [text: string]
}>()

const canvasStore = useCanvasStore()
const voice = useVoiceCapture({
  onNotification: (msg, type, duration) => canvasStore.showNotification(msg, type, duration)
})

// Agent terminal composable
const agentTerminal = useAgentTerminal(props.agent.id)
const showTerminal = ref(false)

const statusDot = computed<'green' | 'yellow' | 'red' | 'gray' | ''>(() => {
  switch (agentTerminal.terminalState.value) {
    case 'ready': return 'green'
    case 'connecting':
    case 'agent-starting': return 'yellow'
    case 'crashed': return 'red'
    case 'off': return 'gray'
    default: return ''
  }
})

const inputPlaceholder = computed(() => {
  const label = props.agent.uiConfig?.label || props.agent.name
  switch (agentTerminal.terminalState.value) {
    case 'off': return `Agent offline - type to start ${label}`
    case 'crashed': return `Agent crashed - type to restart ${label}`
    case 'connecting':
    case 'agent-starting': return `Starting ${label}...`
    default: return `Mensaje a ${label}...`
  }
})

const contentEl = ref<HTMLDivElement | null>(null)
const chatInputEl = ref<InstanceType<typeof ChatInput> | null>(null)
const isRecording = ref(false)
const showTranscript = ref(false)
const showHistory = ref(false)
const showSettings = ref(false)
const messages = reactive<ChatMessage[]>([])

// Real-time transcript state
const ws = ref<WebSocket | null>(null)
const sessions = ref<SessionInfo[]>([])
const activeSessionId = ref<string | null>(null)

// Session + global stats
const sessionStats = ref<{
  model: string
  startTime: string
  duration: number
  lastStopReason: string
  stats: { messageCount: number; toolCallCount: number; thinkingBlocks: number; errors: number }
  tokens: { totalInput: number; totalOutput: number; totalCacheRead: number; totalCacheCreation: number }
} | null>(null)

const claudeStats = ref<{
  today: { messageCount: number; sessionCount: number; toolCallCount: number; tokensByModel: Record<string, number> } | null
  totalSessions: number
  totalMessages: number
} | null>(null)

const claudeUsage = ref<ClaudeUsage | null>(null)

const panelStyle = computed(() => {
  if (!props.anchorRect) return {}
  const bubbleCenterX = props.anchorRect.left + props.anchorRect.width / 2
  const bottomOffset = window.innerHeight - props.anchorRect.top + 12

  const panelWidth = 420
  let left = bubbleCenterX - panelWidth / 2
  left = Math.max(12, Math.min(left, window.innerWidth - panelWidth - 12))

  return {
    position: 'fixed' as const,
    bottom: `${bottomOffset}px`,
    left: `${left}px`,
    width: `${panelWidth}px`
  }
})

const hasContent = computed(() =>
  messages.length > 0 || showTranscript.value || showHistory.value || showSettings.value
)

// ── Tool chip registry ──

interface ToolMeta { icon: string; color: string; label?: string }

const TOOL_CATEGORIES: Record<string, ToolMeta> = {
  // File ops — cyan
  Read:   { icon: '◉', color: '#22d3ee' },
  Edit:   { icon: '✎', color: '#22d3ee' },
  Write:  { icon: '✦', color: '#22d3ee' },
  Glob:   { icon: '⊞', color: '#22d3ee' },
  Grep:   { icon: '⊘', color: '#22d3ee' },
  // Terminal — green
  Bash:      { icon: '▸', color: '#34d399' },
  KillShell: { icon: '✕', color: '#34d399', label: 'Kill' },
  // Tasks — amber
  Task:       { icon: '◈', color: '#fbbf24' },
  TaskCreate: { icon: '+', color: '#fbbf24', label: 'Task+' },
  TaskUpdate: { icon: '↻', color: '#fbbf24', label: 'Task↻' },
  TaskList:   { icon: '≡', color: '#fbbf24', label: 'Tasks' },
  TaskOutput: { icon: '◎', color: '#fbbf24', label: 'TaskOut' },
  TodoWrite:  { icon: '☑', color: '#fbbf24', label: 'Todo' },
  // Web — purple
  WebSearch: { icon: '⌕', color: '#a78bfa' },
  WebFetch:  { icon: '↓', color: '#a78bfa' },
  // Interaction — rose
  AskUserQuestion: { icon: '?', color: '#fb7185', label: 'Ask' },
  EnterPlanMode:   { icon: '▤', color: '#fb7185', label: 'Plan↵' },
  ExitPlanMode:    { icon: '▤', color: '#fb7185', label: 'Plan✓' },
  Skill:           { icon: '⚡', color: '#fb7185' },
  // Resources — slate
  ListMcpResourcesTool: { icon: '☰', color: '#94a3b8', label: 'Resources' },
}

function getToolMeta(name: string): ToolMeta {
  if (TOOL_CATEGORIES[name]) return TOOL_CATEGORIES[name]
  if (name.startsWith('mcp__')) {
    const short = name.split('-').pop() || name
    return { icon: '◆', color: '#818cf8', label: short }
  }
  return { icon: '•', color: '#94a3b8', label: name }
}

// ── Display items with tool grouping ──

interface DisplayItem {
  type: 'message' | 'tool-group'
  msg?: ChatMessage
  tools?: { name: string; count: number; meta: ToolMeta }[]
  ids?: number[]
}

function buildToolGroup(tools: string[], ids: number[]): DisplayItem {
  const counts = new Map<string, number>()
  for (const t of tools) counts.set(t, (counts.get(t) || 0) + 1)
  return {
    type: 'tool-group',
    tools: [...counts.entries()].map(([name, count]) => ({
      name, count, meta: getToolMeta(name)
    })),
    ids
  }
}

const displayMessages = computed<DisplayItem[]>(() => {
  const items: DisplayItem[] = []
  let pendingTools: string[] = []
  let pendingIds: number[] = []

  for (const msg of messages) {
    const isToolOnly = msg.toolCalls?.length && msg.content.startsWith('[Tool calls:')

    if (isToolOnly) {
      pendingTools.push(...msg.toolCalls!)
      pendingIds.push(msg.id)
    } else {
      if (pendingTools.length) {
        items.push(buildToolGroup(pendingTools, pendingIds))
        pendingTools = []
        pendingIds = []
      }
      items.push({ type: 'message', msg })
    }
  }
  if (pendingTools.length) {
    items.push(buildToolGroup(pendingTools, pendingIds))
  }
  return items
})

// ── Formatting helpers ──

function shortModel(model: string): string {
  if (!model) return '?'
  if (model.includes('opus')) return 'opus-4'
  if (model.includes('sonnet')) return 'sonnet-4'
  if (model.includes('haiku')) return 'haiku-4'
  return model.split('-').slice(0, 2).join('-')
}

function formatTime(iso: string): string {
  if (!iso) return '--:--'
  return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
}

function formatTokens(n: number): string {
  if (!n || n <= 0) return '0'
  if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M'
  if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k'
  return String(n)
}

async function scrollToBottom() {
  await nextTick()
  if (contentEl.value) {
    contentEl.value.scrollTop = contentEl.value.scrollHeight
  }
}

// ── Agent matching ──

function matchesAgent(agentName: string): boolean {
  const a = props.agent
  const name = agentName.toLowerCase()
  return a.id.toLowerCase() === name ||
    a.name.toLowerCase() === name ||
    (a.uiConfig?.label || '').toLowerCase() === name
}

// ── WebSocket for real-time updates ──

function connectWs() {
  if (ws.value?.readyState === WebSocket.OPEN) return

  ws.value = new WebSocket(endpoints.claudeStatus)

  ws.value.onmessage = (event) => {
    try {
      const msg = JSON.parse(event.data)

      // Real-time transcript messages
      if (msg.type === 'transcript-update') {
        const agentName = msg.agent || 'main'
        if (matchesAgent(agentName)) {
          appendTranscriptMessages(msg.messages || [])
          // Increment session stats counts
          if (sessionStats.value && msg.messages?.length) {
            sessionStats.value.stats.messageCount += msg.messages.length
          }
        }
      }

      // Agent status (thinking indicator)
      if (msg.type === 'claude-status') {
        const agentName = msg.agent || 'main'
        if (matchesAgent(agentName)) {
          handleStatusUpdate(msg.status)
        }
      }

      // Permission requests → intervention card with buttons
      if (msg.type === 'claude-permission') {
        const agentName = msg.agent_name || msg.agent || 'main'
        if (!msg.agent_name && !msg.agent || matchesAgent(agentName)) {
          addPermissionCard(msg)
        }
      }

      // Hook events → detect AskUserQuestion and plan mode
      if (msg.type === 'claude-hook') {
        const agentName = msg.agent_name || 'main'
        const toolName = msg.tool_name || ''
        const event = msg.hook_event_name || ''

        if (matchesAgent(agentName)) {
          if (event === 'PreToolUse' && toolName === 'AskUserQuestion') {
            addQuestionCard(msg)
          }
          if (event === 'PreToolUse' && (toolName === 'ExitPlanMode' || toolName === 'EnterPlanMode')) {
            addPlanCard(msg, toolName)
          }
        }
      }
    } catch { /* ignore */ }
  }

  ws.value.onclose = () => {
    // Don't reconnect if panel is closed
    if (!props.visible) return
    setTimeout(connectWs, 2000)
  }
}

function disconnectWs() {
  if (ws.value) {
    ws.value.onclose = null // Prevent reconnect
    ws.value.close()
    ws.value = null
  }
}

// ── Transcript message handling ──

function appendTranscriptMessages(newMsgs: any[]) {
  // Remove thinking bubble before appending
  const hasAssistant = newMsgs.some(m => m.role === 'assistant')
  if (hasAssistant) {
    removeThinkingBubble()
  }

  for (const msg of newMsgs) {
    // Deduplicate by uuid
    if (msg.uuid && messages.some(m => m.uuid === msg.uuid)) continue

    messages.push({
      id: ++idCounter,
      role: msg.role === 'user' ? 'user' : 'agent',
      content: msg.content || '',
      status: 'done',
      uuid: msg.uuid,
      toolCalls: msg.toolCalls || undefined
    })
  }

  scrollToBottom()
}

function handleStatusUpdate(status: string) {
  if (status === 'processing' || status === 'thinking') {
    ensureThinkingBubble()
  } else if (status === 'idle') {
    removeThinkingBubble()
  }
}

function ensureThinkingBubble() {
  const last = messages[messages.length - 1]
  if (last?.status === 'thinking') return
  messages.push({ id: ++idCounter, role: 'agent', content: '', status: 'thinking' })
  scrollToBottom()
}

function removeThinkingBubble() {
  const idx = messages.findIndex(m => m.status === 'thinking')
  if (idx !== -1) messages.splice(idx, 1)
}

// ── Intervention cards ──

function addPermissionCard(msg: any) {
  // Use tool_use_id as requestId (hook payload has no requestId field)
  const rid = msg.requestId || msg.tool_use_id || `perm-${Date.now()}`
  if (messages.some(m => m.intervention?.requestId === rid)) return

  const input = msg.tool_input || {}
  let detail = ''
  if (msg.tool_name === 'Bash') {
    detail = input.command || ''
  } else if (msg.tool_name === 'Edit' || msg.tool_name === 'Write') {
    detail = input.file_path || ''
  } else {
    detail = JSON.stringify(input).slice(0, 200)
  }

  messages.push({
    id: ++idCounter,
    role: 'agent',
    content: detail,
    status: 'done',
    intervention: {
      type: 'permission',
      requestId: rid,
      toolName: msg.tool_name,
      toolInput: input,
      resolved: false
    }
  })
  scrollToBottom()
}

function addQuestionCard(msg: any) {
  const input = msg.tool_input || {}
  const questions = input.questions || []
  const q = questions[0]
  messages.push({
    id: ++idCounter,
    role: 'agent',
    content: q?.question || 'Claude is asking a question',
    status: 'done',
    intervention: {
      type: 'question',
      toolName: 'AskUserQuestion',
      toolInput: input,
      options: q?.options || [],
      resolved: false
    }
  })
  scrollToBottom()
}

function addPlanCard(_msg: any, toolName: string) {
  const isEnter = toolName === 'EnterPlanMode'
  messages.push({
    id: ++idCounter,
    role: 'agent',
    content: isEnter ? 'Entering plan mode...' : 'Plan ready for review',
    status: 'done',
    intervention: {
      type: 'plan',
      toolName,
      resolved: false
    }
  })
  scrollToBottom()
}

function respondPermission(msgId: number, _requestId: string, decision: 'allow' | 'deny') {
  agentTerminal.sendInput(decision === 'allow' ? 'y' : 'n')
  const msg = messages.find(m => m.id === msgId)
  if (msg?.intervention) {
    msg.intervention.resolved = true
    msg.intervention.decision = decision
  }
}

function respondQuestion(msgId: number, optionIndex: number) {
  agentTerminal.sendInput(String(optionIndex + 1))
  const msg = messages.find(m => m.id === msgId)
  if (msg?.intervention) {
    msg.intervention.resolved = true
    msg.intervention.decision = `Option ${optionIndex + 1}`
  }
}

function focusInputForQuestion(msgId: number) {
  const msg = messages.find(m => m.id === msgId)
  if (msg?.intervention) {
    msg.intervention.resolved = true
    msg.intervention.decision = 'Other (free text)'
  }
  chatInputEl.value?.focus()
}

function respondPlan(msgId: number, decision: 'approve' | 'reject') {
  agentTerminal.sendInput(decision === 'approve' ? 'y' : 'n')
  const msg = messages.find(m => m.id === msgId)
  if (msg?.intervention) {
    msg.intervention.resolved = true
    msg.intervention.decision = decision
  }
}

async function loadPendingPermissions() {
  try {
    const res = await fetch('/api/claude-permission')
    if (!res.ok) return
    const data = await res.json()
    for (const perm of data.pending || []) {
      addPermissionCard(perm)
    }
  } catch { /* silent */ }
}

// ── History loading ──

async function loadHistory(sessionId?: string) {
  try {
    const url = sessionId
      ? `/api/transcript/${sessionId}`
      : `/api/transcript/active?agent=${props.agent.id}`
    const res = await fetch(url)
    if (!res.ok) return

    const data = await res.json()
    activeSessionId.value = data.sessionId || null

    messages.length = 0
    idCounter = 0

    for (const msg of data.messages || []) {
      messages.push({
        id: ++idCounter,
        role: msg.role === 'user' ? 'user' : 'agent',
        content: msg.content || '',
        status: 'done',
        uuid: msg.uuid,
        toolCalls: msg.toolCalls || undefined
      })
    }

    // Extract session stats
    if (data.model || data.stats) {
      sessionStats.value = {
        model: data.model || '',
        startTime: data.startTime || '',
        duration: data.duration || 0,
        lastStopReason: data.lastStopReason || '',
        stats: data.stats || { messageCount: 0, toolCallCount: 0, thinkingBlocks: 0, errors: 0 },
        tokens: data.tokens || { totalInput: 0, totalOutput: 0, totalCacheRead: 0, totalCacheCreation: 0 }
      }
    }

    scrollToBottom()
  } catch (e) {
    console.error('[PromptBar] Failed to load history:', e)
  }
}

async function fetchSessions() {
  try {
    const res = await fetch('/api/transcript/sessions')
    if (!res.ok) return
    sessions.value = await res.json()
  } catch { /* silent */ }
}

async function fetchClaudeStats() {
  try {
    const res = await fetch('/api/claude-stats')
    if (res.ok) claudeStats.value = await res.json()
  } catch { /* silent */ }
}

async function fetchClaudeUsage() {
  try {
    const res = await fetch('/api/claude-usage')
    if (res.ok) claudeUsage.value = await res.json()
  } catch { /* silent */ }
}

function handleSessionChange(sessionId: string) {
  activeSessionId.value = sessionId
  loadHistory(sessionId)
}

// ── User actions ──

function handleSubmit(text: string) {
  messages.push({ id: ++idCounter, role: 'user', content: text, status: 'sent' })
  emit('submit', text)
  // Send text as input to the agent's terminal PTY
  agentTerminal.sendPrompt(text)
  // Real response will arrive via transcript-update WebSocket
}

function handleMic() {
  if (showTranscript.value) return
  isRecording.value = true
  showTranscript.value = true
  scrollToBottom()
}

function handleTranscriptDone(text: string) {
  isRecording.value = false
  showTranscript.value = false

  if (!text.trim()) return

  messages.push({ id: ++idCounter, role: 'user', content: text, status: 'sent' })
  emit('submit', text)
  agentTerminal.sendPrompt(text)
  // Real response will arrive via transcript-update WebSocket
}

function toggleSettings() {
  showSettings.value = !showSettings.value
}

function toggleHistory() {
  showHistory.value = !showHistory.value
  if (showHistory.value) scrollToBottom()
}

function toggleTerminal() {
  showTerminal.value = !showTerminal.value
}

function handleKeydown(e: KeyboardEvent) {
  if (e.key === 'Escape') emit('close')
}

watch(() => props.visible, async (v) => {
  if (v) {
    isRecording.value = false
    showTranscript.value = false
    showHistory.value = false
    showSettings.value = false
    showTerminal.value = false
    messages.length = 0
    idCounter = 0
    sessionStats.value = null
    claudeStats.value = null
    claudeUsage.value = null

    console.log('[PromptBar] Opening for agent:', props.agent.id)

    // Load real conversation history + sessions list + global stats + usage
    await Promise.all([loadHistory(), fetchSessions(), fetchClaudeStats(), fetchClaudeUsage(), loadPendingPermissions()])

    console.log('[PromptBar] Loaded', messages.length, 'messages, sessions:', sessions.value.length)

    // Connect WebSocket for real-time updates
    connectWs()

    // Connect agent terminal (lazy)
    agentTerminal.connect()
    agentTerminal.checkStatus()

    await voice.init()
    await nextTick()
    if (props.startRecording) {
      handleMic()
    } else {
      chatInputEl.value?.focus()
    }
  } else {
    disconnectWs()
    agentTerminal.disconnect()
    showTerminal.value = false
    if (voice.isRecording.value) {
      voice.stopRecording()
    }
    voice.cleanup()
  }
}, { immediate: true })

defineExpose({ isRecording })

onMounted(() => {
  document.addEventListener('keydown', handleKeydown)
})

onBeforeUnmount(() => {
  document.removeEventListener('keydown', handleKeydown)
  disconnectWs()
  agentTerminal.dispose()
  voice.cleanup()
})
</script>

<template>
  <Teleport to="body">
    <Transition name="prompt-bar">
      <div v-if="visible && anchorRect" class="prompt-bar-backdrop" @click.self="emit('close')">
        <div class="prompt-bar-panel" :style="panelStyle">
          <!-- Header -->
          <div class="pb-header">
            <div class="pb-agent-badge" :style="{ background: agent.uiConfig?.gradient || agent.uiConfig?.color }">
              {{ agent.uiConfig?.shortLabel }}
            </div>
            <span class="pb-agent-label">{{ agent.uiConfig?.label || agent.name }}</span>
            <button class="pb-close" @click="emit('close')">
              <svg width="10" height="10" viewBox="0 0 10 10">
                <line x1="1" y1="1" x2="9" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
                <line x1="9" y1="1" x2="1" y2="9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
              </svg>
            </button>
          </div>

          <!-- Info bar -->
          <div v-if="sessionStats || claudeUsage" class="pb-info-bar">
            <div v-if="sessionStats" class="pb-info-line">
              <span class="accent">{{ shortModel(sessionStats.model) }}</span>
              <span class="sep">&middot;</span>
              <span>{{ formatTime(sessionStats.startTime) }}</span>
              <span class="sep">&middot;</span>
              <span>{{ sessionStats.stats.messageCount }} msgs</span>
              <span class="sep">&middot;</span>
              <span>{{ sessionStats.stats.toolCallCount }} tools</span>
              <span v-if="sessionStats.stats.thinkingBlocks > 0" class="sep">&middot;</span>
              <span v-if="sessionStats.stats.thinkingBlocks > 0" title="Thinking blocks">&#129504; {{ sessionStats.stats.thinkingBlocks }}</span>
              <span v-if="sessionStats.stats.errors > 0" class="sep">&middot;</span>
              <span v-if="sessionStats.stats.errors > 0" class="error-count" title="Errors">{{ sessionStats.stats.errors }} err</span>
            </div>
            <div v-if="sessionStats" class="pb-info-line">
              <span>{{ formatTokens(sessionStats.tokens.totalInput + sessionStats.tokens.totalOutput) }} tokens</span>
              <template v-if="claudeStats?.today">
                <span class="sep">&middot;</span>
                <span>hoy: {{ claudeStats.today.sessionCount }} sesiones</span>
                <span class="sep">&middot;</span>
                <span>{{ claudeStats.today.messageCount }} msgs</span>
              </template>
            </div>
            <!-- Usage limits bar -->
            <div v-if="claudeUsage" class="pb-info-line pb-usage-line">
              <div class="pb-usage-track">
                <div
                  class="pb-usage-fill"
                  :class="claudeUsage.status"
                  :style="{ width: Math.min(claudeUsage.daily.percent, 100) + '%' }"
                ></div>
              </div>
              <span class="pb-usage-percent" :class="claudeUsage.status">{{ claudeUsage.daily.percent }}%</span>
              <span class="sep">&middot;</span>
              <span>{{ claudeUsage.subscription.label }}</span>
              <span class="sep">&middot;</span>
              <span>~{{ claudeUsage.daily.used }}/{{ claudeUsage.daily.limit }}</span>
              <span
                v-if="claudeUsage.status === 'extended'"
                class="pb-usage-badge extended"
              >uso extra</span>
              <span
                v-else-if="claudeUsage.status === 'limit_approaching'"
                class="pb-usage-badge approaching"
              >cerca del limite</span>
            </div>
          </div>

          <!-- Conversation content area -->
          <div ref="contentEl" class="pb-content" :class="{ empty: !hasContent }">
            <template v-for="(item, idx) in displayMessages" :key="item.msg?.id || item.ids?.join('-') || idx">
              <!-- Tool group: chips compactos -->
              <div v-if="item.type === 'tool-group'" class="tool-chips-row">
                <span
                  v-for="tool in item.tools"
                  :key="tool.name"
                  class="tool-chip"
                  :style="{ '--chip-color': tool.meta.color }"
                >
                  <span class="chip-icon">{{ tool.meta.icon }}</span>
                  <span class="chip-label">{{ tool.meta.label || tool.name }}</span>
                  <span v-if="tool.count > 1" class="chip-count">{{ tool.count }}</span>
                </span>
              </div>

              <!-- Regular message -->
              <div v-else class="chat-msg" :class="item.msg!.role">
                <template v-if="item.msg!.role === 'user'">
                  <div class="msg-bubble user-bubble">{{ item.msg!.content }}</div>
                </template>

                <!-- Intervention card -->
                <template v-else-if="item.msg!.intervention">
                  <div class="intervention-card" :class="`intervention--${item.msg!.intervention.type}`">
                    <!-- Permission card -->
                    <template v-if="item.msg!.intervention.type === 'permission'">
                      <div class="intv-header">
                        <svg class="intv-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
                          <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
                          <line x1="12" y1="9" x2="12" y2="13"/>
                          <line x1="12" y1="17" x2="12.01" y2="17"/>
                        </svg>
                        <span class="intv-title">Permission: {{ item.msg!.intervention.toolName }}</span>
                      </div>
                      <div class="intv-detail">{{ item.msg!.content }}</div>
                      <div v-if="!item.msg!.intervention.resolved" class="intv-actions">
                        <button class="intv-btn intv-btn--allow" @click="respondPermission(item.msg!.id, item.msg!.intervention.requestId!, 'allow')">Allow</button>
                        <button class="intv-btn intv-btn--deny" @click="respondPermission(item.msg!.id, item.msg!.intervention.requestId!, 'deny')">Deny</button>
                      </div>
                      <div v-else class="intv-resolved" :class="item.msg!.intervention.decision === 'allow' ? 'resolved--allow' : 'resolved--deny'">
                        {{ item.msg!.intervention.decision === 'allow' ? 'Allowed' : 'Denied' }}
                      </div>
                    </template>

                    <!-- Question card -->
                    <template v-else-if="item.msg!.intervention.type === 'question'">
                      <div class="intv-header">
                        <svg class="intv-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                          <circle cx="12" cy="12" r="10"/>
                          <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
                          <line x1="12" y1="17" x2="12.01" y2="17"/>
                        </svg>
                        <span class="intv-title">Question</span>
                      </div>
                      <div class="intv-detail">{{ item.msg!.content }}</div>
                      <div v-if="!item.msg!.intervention.resolved && item.msg!.intervention.options?.length" class="intv-options">
                        <button
                          v-for="(opt, i) in item.msg!.intervention.options"
                          :key="i"
                          class="intv-option intv-option--btn"
                          @click="respondQuestion(item.msg!.id, i)"
                        >
                          <span class="opt-number">{{ i + 1 }}</span>
                          <span class="opt-label">{{ opt.label }}</span>
                          <span v-if="opt.description" class="opt-desc">{{ opt.description }}</span>
                        </button>
                        <button class="intv-option intv-option--btn intv-option--other" @click="focusInputForQuestion(item.msg!.id)">
                          <span class="opt-label">Other...</span>
                        </button>
                      </div>
                      <div v-else-if="item.msg!.intervention.resolved" class="intv-resolved resolved--allow">
                        {{ item.msg!.intervention.decision }}
                      </div>
                    </template>

                    <!-- Plan card -->
                    <template v-else-if="item.msg!.intervention.type === 'plan'">
                      <div class="intv-header">
                        <svg class="intv-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                          <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
                          <polyline points="14 2 14 8 20 8"/>
                          <line x1="16" y1="13" x2="8" y2="13"/>
                          <line x1="16" y1="17" x2="8" y2="17"/>
                        </svg>
                        <span class="intv-title">{{ item.msg!.content }}</span>
                      </div>
                      <div v-if="!item.msg!.intervention.resolved && item.msg!.intervention.toolName === 'ExitPlanMode'" class="intv-actions">
                        <button class="intv-btn intv-btn--allow" @click="respondPlan(item.msg!.id, 'approve')">Approve</button>
                        <button class="intv-btn intv-btn--deny" @click="respondPlan(item.msg!.id, 'reject')">Reject</button>
                      </div>
                      <div v-else-if="item.msg!.intervention.resolved" class="intv-resolved" :class="item.msg!.intervention.decision === 'approve' ? 'resolved--allow' : 'resolved--deny'">
                        {{ item.msg!.intervention.decision === 'approve' ? 'Approved' : 'Rejected' }}
                      </div>
                    </template>
                  </div>
                </template>

                <template v-else>
                  <div class="msg-bubble agent-bubble">
                    <div v-if="item.msg!.status === 'thinking'" class="thinking-inline">
                      <span class="dot"></span>
                      <span class="dot"></span>
                      <span class="dot"></span>
                    </div>
                    <template v-else>
                      <div class="agent-text fade-in">{{ item.msg!.content }}</div>
                      <!-- Inline tool chips for mixed messages (text + tools) -->
                      <div v-if="item.msg!.toolCalls?.length && !item.msg!.content.startsWith('[Tool calls:')" class="inline-tools">
                        <span
                          v-for="t in item.msg!.toolCalls"
                          :key="t"
                          class="tool-chip-mini"
                          :style="{ '--chip-color': getToolMeta(t).color }"
                        >{{ getToolMeta(t).icon }} {{ getToolMeta(t).label || t }}</span>
                      </div>
                    </template>
                  </div>
                </template>
              </div>
            </template>

            <TranscriptCard v-if="showTranscript" :voice="voice" @done="handleTranscriptDone" />
            <InputSettings
              v-if="showSettings"
              :voice="voice"
              :sessions="sessions"
              :active-session-id="activeSessionId"
              @close="showSettings = false"
              @select-session="handleSessionChange"
            />
            <ConversationHistory v-if="showHistory" :agent="agent" />
          </div>

          <!-- Input -->
          <ChatInput
            ref="chatInputEl"
            :placeholder="inputPlaceholder"
            :recording="isRecording"
            :history-active="showHistory"
            :settings-active="showSettings"
            :terminal-active="showTerminal"
            :status-dot="statusDot"
            :autofocus="visible"
            @submit="handleSubmit"
            @mic="handleMic"
            @toggle-history="toggleHistory"
            @toggle-settings="toggleSettings"
            @toggle-terminal="toggleTerminal"
          />
        </div>

        <!-- Agent terminal floating window (shares composable instance) -->
        <AgentTerminal
          v-model="showTerminal"
          :agent="agent"
          :agent-terminal="agentTerminal"
        />
      </div>
    </Transition>
  </Teleport>
</template>

<style scoped>
.prompt-bar-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9999;
}

.prompt-bar-panel {
  background: rgba(15, 10, 26, 0.85);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 16px;
  box-shadow:
    0 8px 32px rgba(0, 0, 0, 0.4),
    0 0 0 1px rgba(255, 255, 255, 0.05) inset;
  overflow: hidden;
  transform-origin: bottom center;
}

/* Header */
.pb-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}

.pb-agent-badge {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  flex-shrink: 0;
}

.pb-agent-label {
  flex: 1;
  font-size: 13px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.7);
  font-family: system-ui, sans-serif;
}

.pb-close {
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.3);
  cursor: pointer;
  border-radius: 4px;
  transition: all 0.15s;
}

.pb-close:hover {
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
}

/* Info bar */
.pb-info-bar {
  padding: 6px 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  font-size: 11px;
  font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
  color: rgba(255, 255, 255, 0.4);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.pb-info-line {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.pb-info-line .sep {
  opacity: 0.3;
}

.pb-info-line .accent {
  color: rgba(99, 102, 241, 0.7);
}

.pb-info-line .error-count {
  color: rgba(239, 68, 68, 0.7);
}

/* Usage bar */
.pb-usage-line {
  margin-top: 2px;
}

.pb-usage-track {
  width: 60px;
  height: 4px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 2px;
  overflow: hidden;
  flex-shrink: 0;
}

.pb-usage-fill {
  height: 100%;
  border-radius: 2px;
  transition: width 0.4s ease;
}

.pb-usage-fill.normal { background: rgba(34, 197, 94, 0.7); }
.pb-usage-fill.elevated { background: rgba(234, 179, 8, 0.7); }
.pb-usage-fill.limit_approaching { background: rgba(249, 115, 22, 0.7); }
.pb-usage-fill.extended { background: rgba(239, 68, 68, 0.7); }

.pb-usage-percent {
  font-weight: 600;
  font-size: 10px;
}

.pb-usage-percent.normal { color: rgba(34, 197, 94, 0.7); }
.pb-usage-percent.elevated { color: rgba(234, 179, 8, 0.7); }
.pb-usage-percent.limit_approaching { color: rgba(249, 115, 22, 0.7); }
.pb-usage-percent.extended { color: rgba(239, 68, 68, 0.7); }

.pb-usage-badge {
  font-size: 9px;
  padding: 1px 5px;
  border-radius: 3px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  margin-left: 2px;
}

.pb-usage-badge.extended {
  background: rgba(239, 68, 68, 0.15);
  color: rgba(239, 68, 68, 0.8);
  border: 1px solid rgba(239, 68, 68, 0.2);
}

.pb-usage-badge.approaching {
  background: rgba(249, 115, 22, 0.15);
  color: rgba(249, 115, 22, 0.8);
  border: 1px solid rgba(249, 115, 22, 0.2);
}

/* Content area */
.pb-content {
  max-height: 350px;
  overflow-y: auto;
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.1) transparent;
  scroll-behavior: smooth;
}

.pb-content.empty {
  padding: 0;
  max-height: 0;
}

/* Chat messages */
.chat-msg {
  display: flex;
  animation: msg-in 0.2s ease-out;
}

.chat-msg.user {
  justify-content: flex-end;
}

.chat-msg.agent {
  justify-content: flex-start;
}

.msg-bubble {
  max-width: 85%;
  padding: 8px 12px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.5;
  font-family: system-ui, sans-serif;
  word-break: break-word;
}

.user-bubble {
  background: rgba(99, 102, 241, 0.25);
  border: 1px solid rgba(99, 102, 241, 0.3);
  color: rgba(255, 255, 255, 0.92);
  border-bottom-right-radius: 4px;
}

.agent-bubble {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.85);
  border-bottom-left-radius: 4px;
}

/* Thinking dots inline */
.thinking-inline {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 0;
}

.thinking-inline .dot {
  width: 6px;
  height: 6px;
  background: rgba(255, 255, 255, 0.4);
  border-radius: 50%;
  animation: dot-bounce 1.4s ease-in-out infinite;
}

.thinking-inline .dot:nth-child(1) { animation-delay: 0s; }
.thinking-inline .dot:nth-child(2) { animation-delay: 0.2s; }
.thinking-inline .dot:nth-child(3) { animation-delay: 0.4s; }

.agent-text {
  white-space: pre-wrap;
}

/* Animations */
.fade-in {
  animation: fade-in 0.25s ease-out;
}

@keyframes msg-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes dot-bounce {
  0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
  40% { transform: scale(1); opacity: 1; }
}

/* Transition — enter */
.prompt-bar-enter-active {
  transition: opacity 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

.prompt-bar-enter-active .prompt-bar-panel {
  animation: pb-enter 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

.prompt-bar-enter-from {
  opacity: 0;
}

/* Transition — leave */
.prompt-bar-leave-active {
  transition: opacity 0.2s ease;
}

.prompt-bar-leave-active .prompt-bar-panel {
  animation: pb-leave 0.2s ease both;
}

.prompt-bar-leave-to {
  opacity: 0;
}

@keyframes pb-enter {
  0% {
    opacity: 0;
    transform: translateY(16px) scale(0.85);
    filter: blur(4px);
  }
  60% {
    opacity: 1;
    filter: blur(0);
  }
  100% {
    transform: translateY(0) scale(1);
    filter: blur(0);
  }
}

@keyframes pb-leave {
  0% {
    opacity: 1;
    transform: translateY(0) scale(1);
    filter: blur(0);
  }
  100% {
    opacity: 0;
    transform: translateY(12px) scale(0.92);
    filter: blur(3px);
  }
}

/* Intervention cards */
.intervention-card {
  max-width: 95%;
  padding: 10px 12px;
  border-radius: 10px;
  font-size: 12px;
  font-family: system-ui, sans-serif;
  animation: msg-in 0.2s ease-out;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.intervention--permission {
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.2);
  animation: permission-pulse 2s ease-in-out infinite;
}

.intervention--question {
  background: rgba(99, 102, 241, 0.08);
  border: 1px solid rgba(99, 102, 241, 0.2);
}

.intervention--plan {
  background: rgba(168, 85, 247, 0.08);
  border: 1px solid rgba(168, 85, 247, 0.2);
}

.intv-header {
  display: flex;
  align-items: center;
  gap: 6px;
}

.intv-icon { flex-shrink: 0; }
.intervention--permission .intv-icon { color: #f87171; }
.intervention--question .intv-icon { color: #818cf8; }
.intervention--plan .intv-icon { color: #a78bfa; }

.intv-title {
  font-weight: 600;
  font-size: 11.5px;
  color: rgba(255, 255, 255, 0.8);
}

.intv-detail {
  font-size: 11px;
  font-family: 'SF Mono', monospace;
  color: rgba(255, 255, 255, 0.5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.intv-actions {
  display: flex;
  gap: 6px;
  margin-top: 2px;
}

.intv-btn {
  padding: 4px 14px;
  border-radius: 6px;
  border: none;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.15s;
}

.intv-btn--allow {
  background: rgba(16, 185, 129, 0.15);
  color: #34d399;
  border: 1px solid rgba(16, 185, 129, 0.25);
}
.intv-btn--allow:hover { background: rgba(16, 185, 129, 0.3); }

.intv-btn--deny {
  background: rgba(239, 68, 68, 0.15);
  color: #f87171;
  border: 1px solid rgba(239, 68, 68, 0.25);
}
.intv-btn--deny:hover { background: rgba(239, 68, 68, 0.3); }

.intv-resolved {
  font-size: 10px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 4px;
  width: fit-content;
}
.resolved--allow { background: rgba(16, 185, 129, 0.15); color: #34d399; }
.resolved--deny { background: rgba(239, 68, 68, 0.15); color: #f87171; }

.intv-options {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.intv-option {
  padding: 4px 8px;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  text-align: left;
}
.intv-option--btn {
  border: 1px solid rgba(99, 102, 241, 0.15);
  cursor: pointer;
  transition: all 0.15s;
  position: relative;
  padding-left: 28px;
}
.intv-option--btn:hover {
  background: rgba(99, 102, 241, 0.12);
  border-color: rgba(99, 102, 241, 0.3);
}
.opt-number {
  position: absolute;
  left: 8px;
  top: 5px;
  font-size: 10px;
  font-weight: 700;
  color: rgba(99, 102, 241, 0.6);
  font-family: 'SF Mono', monospace;
}
.intv-option--other {
  padding-left: 8px;
  border-style: dashed;
  opacity: 0.7;
}
.intv-option--other:hover { opacity: 1; }
.opt-label { font-size: 11px; color: rgba(255, 255, 255, 0.7); }
.opt-desc { font-size: 10px; color: rgba(255, 255, 255, 0.35); }

@keyframes permission-pulse {
  0%, 100% { border-color: rgba(239, 68, 68, 0.2); }
  50% { border-color: rgba(239, 68, 68, 0.4); }
}

/* Tool chips row — consecutive tool calls share horizontal space */
.tool-chips-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  animation: msg-in 0.2s ease-out;
  max-width: 95%;
}

.tool-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 10px;
  font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
  background: color-mix(in srgb, var(--chip-color) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--chip-color) 20%, transparent);
  color: var(--chip-color);
  white-space: nowrap;
  transition: background 0.15s;
}

.tool-chip:hover {
  background: color-mix(in srgb, var(--chip-color) 22%, transparent);
}

.chip-icon {
  font-size: 10px;
  opacity: 0.8;
}

.chip-label {
  font-weight: 500;
}

.chip-count {
  font-size: 9px;
  font-weight: 700;
  background: color-mix(in srgb, var(--chip-color) 25%, transparent);
  padding: 0 4px;
  border-radius: 4px;
  min-width: 14px;
  text-align: center;
}

/* Reduce gap between consecutive tool rows */
.tool-chips-row + .tool-chips-row {
  margin-top: -2px;
}

/* Inline tool chips for mixed messages (text + tool calls) */
.inline-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}

.tool-chip-mini {
  font-size: 9px;
  font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
  color: var(--chip-color);
  opacity: 0.7;
  white-space: nowrap;
}

/* Mobile */
@media (max-width: 768px) {
  .prompt-bar-panel {
    left: 8px !important;
    right: 8px;
    width: auto !important;
  }
}
</style>
