<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'
import { endpoints } from '../config/endpoints'
import type { Agent, AgentStatusState, ClaudeStatus } from '../types/agent'
import { useVoiceCapture } from '../composables/useVoiceCapture'
import { useCanvasStore } from '../stores/canvas'
import FloatBubble from './agent/FloatBubble.vue'
import PromptBar from './agent/PromptBar.vue'
import TranscriptCard from './agent/TranscriptCard.vue'

const agents = ref<Agent[]>([])
const loading = ref(true)

// Per-agent status tracking
const agentStatuses = reactive<Record<string, AgentStatusState>>({})
const agentTimers = new Map<string, Record<string, number>>()

// PromptBar state
const activeAgentId = ref<string | null>(null)
const activeAnchorRect = ref<DOMRect | null>(null)
const openInRecording = ref(false)
const promptBarRef = ref<InstanceType<typeof PromptBar> | null>(null)
const bubbleRefs = new Map<string, Element>()

function setBubbleRef(agentId: string, el: any) {
  if (el?.$el) bubbleRefs.set(agentId, el.$el)
  else if (el) bubbleRefs.set(agentId, el)
}

function autoOpenForAgent(agentId: string) {
  if (activeAgentId.value === agentId) return // Already open
  const bubbleEl = bubbleRefs.get(agentId)
  if (!bubbleEl) return
  activeAnchorRect.value = bubbleEl.getBoundingClientRect()
  openInRecording.value = false
  activeAgentId.value = agentId
}

const isRecordingActive = computed(() =>
  promptBarRef.value?.isRecording ?? false
)

// Floating transcript state (long-press recording)
const canvasStore = useCanvasStore()
const floatingVoice = useVoiceCapture({
  onNotification: (msg, type, dur) => canvasStore.showNotification(msg, type, dur)
})
const floatingAgentId = ref<string | null>(null)
const floatingAnchorRect = ref<DOMRect | null>(null)

const floatingAgent = computed(() =>
  enabledAgents.value.find(a => a.id === floatingAgentId.value) || null
)

const isFloatingRecording = computed(() =>
  !!floatingAgentId.value && floatingVoice.isRecording.value
)

const floatingStyle = computed(() => {
  if (!floatingAnchorRect.value) return {}
  const rect = floatingAnchorRect.value
  const bubbleCenterX = rect.left + rect.width / 2
  const bottomOffset = window.innerHeight - rect.top + 12
  const panelWidth = 320
  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`,
    zIndex: 10000
  }
})

const enabledAgents = computed(() =>
  agents.value.filter(a => a.uiConfig?.enabled)
)

const activeAgent = computed(() =>
  enabledAgents.value.find(a => a.id === activeAgentId.value) || null
)

// --- Status tracking helpers ---

function getAgentStatus(agentId: string): AgentStatusState {
  if (!agentStatuses[agentId]) {
    agentStatuses[agentId] = {
      isProcessing: false,
      isReading: false,
      isWriting: false,
      awaitingPermission: false,
      showToolFlash: false,
      showNotification: false,
      currentTool: null
    }
  }
  return agentStatuses[agentId]
}

function getTimers(agentId: string): Record<string, number> {
  if (!agentTimers.has(agentId)) {
    agentTimers.set(agentId, {})
  }
  return agentTimers.get(agentId)!
}

function clearAgentTimer(agentId: string, key: string) {
  const timers = getTimers(agentId)
  if (timers[key]) {
    clearTimeout(timers[key])
    delete timers[key]
  }
}

function setAgentTimer(agentId: string, key: string, fn: () => void, ms: number) {
  clearAgentTimer(agentId, key)
  const timers = getTimers(agentId)
  timers[key] = window.setTimeout(fn, ms)
}

function triggerToolFlash(agentId: string) {
  const s = getAgentStatus(agentId)
  s.showToolFlash = true
  setAgentTimer(agentId, 'toolFlash', () => {
    s.showToolFlash = false
  }, 500)
}

// --- WebSocket ---

let statusWs: WebSocket | null = null
let reconnectTimeout: number | null = null

function connectStatusWs() {
  if (statusWs?.readyState === WebSocket.OPEN) return

  console.log('[AgentBar] Connecting to', endpoints.claudeStatus)
  statusWs = new WebSocket(endpoints.claudeStatus)

  statusWs.onopen = () => {
    console.log('[AgentBar] WebSocket OPEN')
  }

  statusWs.onerror = (err) => {
    console.error('[AgentBar] WebSocket ERROR', err)
  }

  statusWs.onmessage = (event) => {
    try {
      const msg = JSON.parse(event.data)
      console.log('[AgentBar] WS message:', msg.type, msg.status || '', msg.agent || '')

      if (msg.type !== 'claude-status') return

      const status = msg.status as ClaudeStatus
      const agentName = (msg.agent || 'main') as string
      const tool = msg.tool || null

      console.log(`[AgentBar] Status: ${status}, agent: ${agentName}, tool: ${tool}`)

      const agent = enabledAgents.value.find(a =>
        a.id.toLowerCase() === agentName.toLowerCase() ||
        a.name.toLowerCase() === agentName.toLowerCase() ||
        a.uiConfig?.label.toLowerCase() === agentName.toLowerCase()
      )
      if (!agent) {
        console.log(`[AgentBar] No matching enabled agent for "${agentName}", enabled:`, enabledAgents.value.map(a => a.id))
        return
      }

      console.log(`[AgentBar] Matched agent: ${agent.id}, applying status: ${status}`)
      const s = getAgentStatus(agent.id)
      s.currentTool = tool

      switch (status) {
        case 'processing':
        case 'thinking':
          s.isProcessing = true
          setAgentTimer(agent.id, 'processing', () => {
            s.isProcessing = false
          }, 120000)
          break

        case 'idle':
          s.isProcessing = false
          s.isReading = false
          s.isWriting = false
          s.awaitingPermission = false
          clearAgentTimer(agent.id, 'processing')
          break

        case 'permissionRequest':
          s.awaitingPermission = true
          // Auto-open PromptBar if not already open for this agent
          autoOpenForAgent(agent.id)
          break

        case 'reading':
          s.isReading = true
          triggerToolFlash(agent.id)
          break

        case 'writing':
          s.isWriting = true
          triggerToolFlash(agent.id)
          break

        case 'toolUse':
          triggerToolFlash(agent.id)
          break

        case 'toolDone':
          s.isReading = false
          s.isWriting = false
          s.awaitingPermission = false
          break

        case 'notification':
          s.showNotification = true
          setAgentTimer(agent.id, 'notification', () => {
            s.showNotification = false
          }, 2000)
          break
      }
    } catch { /* ignore */ }
  }

  statusWs.onclose = () => {
    reconnectTimeout = window.setTimeout(connectStatusWs, 2000)
  }
}

// --- Data fetching ---

async function fetchAgents() {
  try {
    const res = await fetch('/api/agents')
    if (!res.ok) return
    const data: Agent[] = await res.json()
    agents.value = data
  } catch {
    // silent
  } finally {
    loading.value = false
  }
}

// --- Bubble interaction ---

function openPromptBar(agent: Agent, el: HTMLElement, recording: boolean) {
  if (activeAgentId.value === agent.id && !recording) {
    activeAgentId.value = null
    activeAnchorRect.value = null
    openInRecording.value = false
    return
  }
  activeAnchorRect.value = el.getBoundingClientRect()
  openInRecording.value = recording
  activeAgentId.value = agent.id
}

function handleBubbleClick(agent: Agent, event: MouseEvent) {
  openPromptBar(agent, event.currentTarget as HTMLElement, false)
}

async function handleBubbleHold(agent: Agent, el: HTMLElement) {
  // Close PromptBar if open
  if (activeAgentId.value) {
    handlePromptClose()
  }
  // Open floating transcript
  floatingAnchorRect.value = el.getBoundingClientRect()
  floatingAgentId.value = agent.id
  await floatingVoice.init()
  floatingVoice.clearTranscript()
  floatingVoice.startRecording()
}

function handleBubbleHoldRelease() {
  if (!floatingAgentId.value || !floatingVoice.isRecording.value) return
  // Buffer 500ms for trailing words, then stop and emit done via TranscriptCard
  setTimeout(() => {
    if (floatingVoice.isRecording.value) {
      floatingVoice.stopRecording()
      // Wait for final Whisper result if needed
      const delay = floatingVoice.voiceMode.value === 'whisper' ? 800 : 200
      setTimeout(() => {
        const text = floatingVoice.transcript.value.trim()
        closeFloating()
        if (text) {
          console.log(`[AgentBar] Voice submit to ${floatingAgent.value?.id}:`, text)
        }
      }, delay)
    }
  }, 500)
}

function handleFloatingDone(text: string) {
  closeFloating()
  if (text.trim()) {
    console.log(`[AgentBar] Voice submit to ${floatingAgent.value?.id}:`, text)
  }
}

function closeFloating() {
  if (floatingVoice.isRecording.value) {
    floatingVoice.stopRecording()
  }
  floatingVoice.cleanup()
  floatingAgentId.value = null
  floatingAnchorRect.value = null
}

function handleFloatingClose() {
  closeFloating()
}

function handlePromptClose() {
  activeAgentId.value = null
  activeAnchorRect.value = null
  openInRecording.value = false
}

function handlePromptSubmit(text: string) {
  console.log(`[AgentBar] Submit to ${activeAgentId.value}:`, text)
}

// --- Lifecycle ---

onMounted(() => {
  fetchAgents()
  connectStatusWs()
})

onBeforeUnmount(() => {
  statusWs?.close()
  floatingVoice.cleanup()
  if (reconnectTimeout) clearTimeout(reconnectTimeout)
  for (const [, timers] of agentTimers) {
    for (const key of Object.keys(timers)) {
      clearTimeout(timers[key])
    }
  }
  agentTimers.clear()
})
</script>

<template>
  <div v-if="enabledAgents.length" class="agent-bubbles">
    <FloatBubble
      v-for="agent in enabledAgents"
      :key="agent.id"
      :ref="(el: any) => setBubbleRef(agent.id, el)"
      :agent="agent"
      :status="agentStatuses[agent.id]"
      :recording="(activeAgentId === agent.id && isRecordingActive) || (floatingAgentId === agent.id && isFloatingRecording)"
      @click="handleBubbleClick(agent, $event)"
      @hold="handleBubbleHold(agent, $event)"
      @holdrelease="handleBubbleHoldRelease"
    />
  </div>

  <PromptBar
    v-if="activeAgent"
    :key="activeAgent.id"
    ref="promptBarRef"
    :agent="activeAgent"
    :anchor-rect="activeAnchorRect"
    :visible="!!activeAgentId"
    :start-recording="openInRecording"
    @close="handlePromptClose"
    @submit="handlePromptSubmit"
  />

  <!-- Floating transcript (long-press recording) -->
  <Teleport to="body">
    <Transition name="float-transcript">
      <div v-if="floatingAgentId && floatingAnchorRect" class="float-transcript-backdrop" @click.self="handleFloatingClose">
        <div class="float-transcript-panel" :style="floatingStyle">
          <div class="ft-header">
            <div
              v-if="floatingAgent"
              class="ft-badge"
              :style="{ background: floatingAgent.uiConfig?.gradient || floatingAgent.uiConfig?.color }"
            >
              {{ floatingAgent.uiConfig?.shortLabel }}
            </div>
            <span class="ft-label">{{ floatingAgent?.uiConfig?.label || floatingAgent?.name }}</span>
          </div>
          <TranscriptCard :voice="floatingVoice" @done="handleFloatingDone" />
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<style scoped>
.agent-bubbles {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 14px;
  z-index: 9998;
  pointer-events: none;
}

/* Floating transcript */
.float-transcript-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9999;
}

.float-transcript-panel {
  background: rgba(15, 10, 26, 0.9);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 14px;
  padding: 10px 12px;
  box-shadow:
    0 8px 32px rgba(0, 0, 0, 0.4),
    0 0 0 1px rgba(255, 255, 255, 0.05) inset;
  transform-origin: bottom center;
}

.ft-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.ft-badge {
  width: 22px;
  height: 22px;
  border-radius: 7px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  flex-shrink: 0;
}

.ft-label {
  font-size: 12px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.5);
  font-family: system-ui, sans-serif;
}

/* Transition */
.float-transcript-enter-active {
  transition: opacity 0.2s ease;
}

.float-transcript-enter-active .float-transcript-panel {
  animation: ft-enter 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

.float-transcript-enter-from {
  opacity: 0;
}

.float-transcript-leave-active {
  transition: opacity 0.15s ease;
}

.float-transcript-leave-active .float-transcript-panel {
  animation: ft-leave 0.15s ease both;
}

.float-transcript-leave-to {
  opacity: 0;
}

@keyframes ft-enter {
  0% { opacity: 0; transform: translateY(10px) scale(0.9); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes ft-leave {
  0% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(8px) scale(0.95); }
}

@media (max-width: 768px) {
  .agent-bubbles {
    bottom: 80px;
    gap: 12px;
  }

  .float-transcript-panel {
    left: 8px !important;
    right: 8px;
    width: auto !important;
  }
}
</style>
