<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import Toolbar from './components/Toolbar.vue'
import TorchButton from './components/TorchButton.vue'
import FloatingResponse from './components/FloatingResponse.vue'
import { initWhisperSocket } from './services/whisperSocket'
import FloatingVoice from './components/FloatingVoice.vue'
import FloatingTranscriptDebug from './components/FloatingTranscriptDebug.vue'
import TerminalFabStack from './components/transcript-debug/TerminalFabStack.vue'
import PwaInstallBanner from './components/PwaInstallBanner.vue'
import HooksApprovalModal from './components/HooksApprovalModal.vue'
import ServerConfigDialog from './components/ServerConfigDialog.vue'
import WindowControls from './components/WindowControls.vue'
import { useGlobalApproval } from './composables/useGlobalApproval'
import { initWebMCP, getWebMCP } from './services/webmcp'
import { initTorch, destroyTorch } from './services/torch'
import { initSessionStateWS, destroySessionStateWS } from './services/session-state-ws'
import { initToolRegistry, activatePageTools, initToolsOnRefresh } from './services/toolRegistry'
import { setResponseControls } from './services/tools/handlers/responseHandlers'
import { useCanvasStore } from './stores/canvas'
import { useProjectCanvasStore } from './stores/projectCanvas'
import { useSessionState } from './stores/session-state'
import { isTauri, isMobileTauri, getTauriNotification } from './lib/tauri'
import { useServerConfig } from './stores/server-config'

const route = useRoute()
const router = useRouter()

// Tauri server config
const serverConfig = isTauri ? useServerConfig() : null
const showServerConfig = ref(false)
const needsServerConfig = computed(() => isTauri && serverConfig && !serverConfig.isConfigured)

const isPipWindow = computed(() => route.query.pip === '1')
const showVoice = ref(false)
const showTranscriptDebug = ref(false)
const showDebugConsole = ref(false)
const toolbarVisible = ref(true)
const forceWco = ref(isTauri && !isMobileTauri())
const debugLogs = ref<Array<{ type: string; message: string; time: string }>>([])

// Intercept console.log for debug panel
const originalConsoleLog = console.log
const originalConsoleWarn = console.warn
const originalConsoleError = console.error

function addDebugLog(type: string, args: any[]) {
  const message = args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ')
  const time = new Date().toLocaleTimeString()
  debugLogs.value.push({ type, message, time })
  // Keep only last 100 logs
  if (debugLogs.value.length > 100) {
    debugLogs.value.shift()
  }
}

console.log = (...args) => {
  originalConsoleLog.apply(console, args)
  addDebugLog('log', args)
}
console.warn = (...args) => {
  originalConsoleWarn.apply(console, args)
  addDebugLog('warn', args)
}
console.error = (...args) => {
  originalConsoleError.apply(console, args)
  addDebugLog('error', args)
}

function copyDebugLogs() {
  const text = debugLogs.value.map(l => `[${l.time}] [${l.type}] ${l.message}`).join('\n')
  navigator.clipboard.writeText(text)
  canvasStore.showNotification('Logs copied!', 'success')
}

function clearDebugLogs() {
  debugLogs.value = []
}
const responseRef = ref<InstanceType<typeof FloatingResponse> | null>(null)
const voiceRef = ref<InstanceType<typeof FloatingVoice> | null>(null)
const transcriptDebugRef = ref<InstanceType<typeof FloatingTranscriptDebug> | null>(null)
const mousePos = ref({ x: 0, y: 0 })
const canvasStore = useCanvasStore()
const projectCanvasStore = useProjectCanvasStore()
const sessionState = useSessionState()
const { totalPending, modalVisible, connect: connectApproval, disconnect: disconnectApproval, fetchPending: fetchApprovalPending } = useGlobalApproval()
// Voice FAB push-to-talk state
const voicePTTActive = ref(false)
let voiceTouchStarted = false
let voicePTTTimeout: number | null = null

const keyboardVisible = ref(false)    // Virtual keyboard visible

// Whether any terminal exists (T1+)
const hasTerminals = computed(() => sessionState.terminalRegistry.length > 0)

// Whether terminal 1 is the currently active terminal
const isT1Active = computed(() => {
  const reg = sessionState.terminalRegistry
  if (!reg.length) return false
  return reg[0].transcriptSessionId === transcriptDebugRef.value?.activeTerminalSessionId
})

// Extra terminals (T2-T5) from Pinia store — fully reactive, no template ref dependency
const extraTerminals = computed(() => {
  const reg = sessionState.terminalRegistry
  if (reg.length <= 1) return []
  return reg.slice(1).map(entry => ({
    sessionId: entry.transcriptSessionId,
    ephemeralSessionId: entry.ephemeralSessionId,
    agent: entry.agent,
    label: entry.label,
    command: entry.command,
    active: entry.transcriptSessionId === transcriptDebugRef.value?.activeTerminalSessionId,
    alive: entry.alive,
    clients: entry.clients
  }))
})

function hardRefresh() {
  location.reload()
}

function trackMouse(e: MouseEvent) {
  mousePos.value = { x: e.clientX, y: e.clientY }
}

function handleFabTerminalSelect(sessionId: string) {
  const isActive = transcriptDebugRef.value?.activeTerminalSessionId === sessionId
  if (showTranscriptDebug.value && isActive) {
    // Already showing this terminal — close
    showTranscriptDebug.value = false
    return
  }
  if (transcriptDebugRef.value) {
    transcriptDebugRef.value.switchToTerminal(sessionId)
  }
  showTranscriptDebug.value = true
}

function handleFabCreateSession() {
  showTranscriptDebug.value = true
  nextTick(() => {
    transcriptDebugRef.value?.handleCreateSession()
  })
}

function handleMainFabClick() {
  const terminals = transcriptDebugRef.value?.openTerminals
  if (!terminals?.length) return

  const firstSessionId = terminals[0].sessionId
  const isActive = transcriptDebugRef.value?.activeTerminalSessionId === firstSessionId

  if (showTranscriptDebug.value && isActive) {
    // Already showing terminal 1 — close
    showTranscriptDebug.value = false
    return
  }

  // Switch to terminal 1 and open
  transcriptDebugRef.value?.switchToTerminal(firstSessionId)
  showTranscriptDebug.value = true
}

function handleGlobalKeydown(e: KeyboardEvent) {
  if (e.ctrlKey && e.key === 'e') {
    e.preventDefault()
    if (transcriptDebugRef.value) {
      transcriptDebugRef.value.openAtCursor(mousePos.value.x, mousePos.value.y)
    } else {
      showTranscriptDebug.value = !showTranscriptDebug.value
    }
  }
}

// Voice FAB push-to-talk handlers
function handleVoiceFabClick() {
  // If touch just ended, ignore click
  if (voiceTouchStarted) {
    voiceTouchStarted = false
    return
  }
  // Normal click: toggle panel
  showVoice.value = !showVoice.value
}

function handleVoiceFabTouchStart(e: TouchEvent) {
  e.preventDefault()
  voiceTouchStarted = true
  voicePTTActive.value = true

  // Open panel and start recording
  showVoice.value = true

  // Wait a moment for panel to open, then start recording
  setTimeout(() => {
    voiceRef.value?.startRecording()
  }, 100)
}

function handleVoiceFabTouchEnd(e: TouchEvent) {
  e.preventDefault()

  if (!voicePTTActive.value) return

  // Add buffer before stopping
  voicePTTTimeout = window.setTimeout(() => {
    voiceRef.value?.stopRecordingAndSend()
    voicePTTActive.value = false
  }, 1000)

  setTimeout(() => { voiceTouchStarted = false }, 100)
}

type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'project-canvas' | 'database' | 'source' | 'tools' | 'agents'

function syncThemeColor() {
  const bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-primary').trim()
  if (bg) {
    document.querySelector('meta[name="theme-color"]')?.setAttribute('content', bg)
  }
}

onMounted(async () => {
  // Bridge for Android widget navigation (called from MainActivity via evaluateJavascript)
  ;(window as any).__WIDGET_NAVIGATE__ = (route: string) => {
    router.push(route)
    return true
  }

  // Bridge for Android voice assistant — navigates to transcript-debug page on the target terminal
  ;(window as any).__VOICE_OPEN_TERMINAL__ = (ephemeralSessionId: string) => {
    const entry = sessionState.terminalRegistry.find(
      t => t.ephemeralSessionId === ephemeralSessionId
    )
    const idx = entry
      ? sessionState.terminalRegistry.indexOf(entry) + 1
      : 1
    router.push(`/transcript-debug/${idx}`)
    return true
  }

  // Sync Windows titlebar color with CSS variable
  syncThemeColor()

  // Connect global hooks approval WS
  connectApproval()
  fetchApprovalPending()

  // Connect centralized session state WS
  initSessionStateWS()

  // Initialize Whisper WebSocket connection early
  initWhisperSocket()

  // Fire torch connection early (don't await yet)
  const torchReady = initTorch()

  // Initialize WebMCP connection
  await initWebMCP()

  // Initialize tool registry with router
  initToolRegistry(router)

  // Initialize tools for current page (handles refresh)
  const currentPage = (route.name as string) || 'canvas'
  initToolsOnRefresh(currentPage as PageName)

  // Setup response controls for MCP tools
  setResponseControls({
    addMessage: (message: string, type?: 'info' | 'success' | 'warning' | 'error') => {
      if (responseRef.value) {
        return responseRef.value.addMessage(message, type)
      }
      return ''
    },
    removeMessage: (id: string) => {
      responseRef.value?.removeMessage(id)
    },
    clearAll: () => {
      responseRef.value?.clearAll()
    },
    getMessages: () => {
      return responseRef.value?.getMessages() || []
    }
  })

  // Track mouse for Ctrl+E cursor-based opening
  document.addEventListener('mousemove', trackMouse)

  // Global keyboard shortcut: Ctrl+E toggles Transcript Debug
  document.addEventListener('keydown', handleGlobalKeydown)

  // Detect virtual keyboard on mobile
  if (window.visualViewport) {
    const initialHeight = window.visualViewport.height
    window.visualViewport.addEventListener('resize', () => {
      const currentHeight = window.visualViewport!.height
      // If viewport shrinks significantly, keyboard is likely open
      keyboardVisible.value = currentHeight < initialHeight * 0.75
    })
  }

  // Ensure torch connection is established
  await torchReady
})

async function sendTestNotification() {
  const title = 'Agent UI'
  const body = 'Test notification from Agent UI — all platforms!'

  if (isTauri) {
    try {
      const { isPermissionGranted, requestPermission, sendNotification } = await getTauriNotification()
      let granted = await isPermissionGranted()
      if (!granted) {
        const perm = await requestPermission()
        granted = perm === 'granted'
      }
      if (granted) {
        sendNotification({ title, body })
      }
    } catch (e) {
      console.warn('[Notification] Tauri plugin failed:', e)
    }
  } else if ('Notification' in window) {
    if (Notification.permission === 'granted') {
      new Notification(title, { body })
    } else if (Notification.permission !== 'denied') {
      const perm = await Notification.requestPermission()
      if (perm === 'granted') new Notification(title, { body })
    }
  }
}

onUnmounted(() => {
  document.removeEventListener('mousemove', trackMouse)
  document.removeEventListener('keydown', handleGlobalKeydown)
  destroyTorch()
  disconnectApproval()
  destroySessionStateWS()
})

// Watch for route changes and update tools
watch(() => route.name, (newPage) => {
  if (newPage) {
    activatePageTools(newPage as PageName)
  }
})

// Watch for Tauri server config changes — re-init services when server is configured
if (serverConfig) {
  watch(() => serverConfig!.isConfigured, async (configured) => {
    if (configured) {
      showServerConfig.value = false
      // Re-initialize all services with the new server URL
      initSessionStateWS()
      initWhisperSocket()
      await initWebMCP()
      await initTorch()
    }
  })
}
</script>

<template>
  <div class="app-container" :class="{ wco: forceWco, 'pip-window': isPipWindow }">
    <header v-if="!isPipWindow" class="app-header" :class="{ 'wco-header': forceWco }">
      <div class="header-left">
        <button
          class="toolbar-toggle"
          :class="{ collapsed: !toolbarVisible }"
          @click="toolbarVisible = !toolbarVisible"
          :title="toolbarVisible ? 'Ocultar toolbar' : 'Mostrar toolbar'"
        >
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <rect x="3" y="3" width="18" height="18" rx="2"/>
            <line x1="9" y1="3" x2="9" y2="21"/>
          </svg>
        </button>
        <template v-if="projectCanvasStore.activeCanvas && route.name === 'project-canvas'">
          <span class="header-sep">/</span>
          <span class="header-canvas-name">{{ projectCanvasStore.activeCanvas.name }}</span>
          <span v-if="projectCanvasStore.activeCanvas.is_system" class="header-canvas-badge">Sistema</span>
        </template>
        <template v-if="canvasStore.isAnonymousCanvas && route.name === 'canvas'">
          <span class="header-sep">/</span>
          <span class="header-canvas-name">anonimo</span>
        </template>
        <button class="debug-btn" :class="{ active: showDebugConsole }" @click="showDebugConsole = !showDebugConsole" title="Debug Console">
          <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
          </svg>
          <span v-if="debugLogs.length" class="log-count">{{ debugLogs.length }}</span>
        </button>
        <PwaInstallBanner v-if="!isTauri" />
        <button v-if="isTauri" class="server-config-btn" @click="showServerConfig = true" title="Server settings">
          <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/>
          </svg>
        </button>
      </div>
      <div class="header-right">
        <button
          v-if="totalPending > 0 || modalVisible"
          class="approval-badge-btn"
          :class="{ active: modalVisible, pulse: totalPending > 0 }"
          @click="modalVisible = !modalVisible"
          title="Hooks Approval"
        >
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
          </svg>
          <span v-if="totalPending > 0" class="approval-count">{{ totalPending }}</span>
        </button>
        <button class="refresh-btn" @click="sendTestNotification" title="Test notification">
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
            <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
          </svg>
        </button>
        <button class="refresh-btn" @click="hardRefresh" title="Hard refresh (Ctrl+F5)">
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
            <path d="M21 3v5h-5"/>
          </svg>
        </button>
        <span class="wco-dot" :class="{ on: forceWco }" @click="forceWco = !forceWco"></span>
        <TorchButton />
        <WindowControls />
      </div>
    </header>
    <main class="app-main">
      <Toolbar v-if="!isPipWindow" :collapsed="!toolbarVisible" />
      <RouterView v-slot="{ Component }">
        <Transition name="page" mode="out-in">
          <component :is="Component" />
        </Transition>
      </RouterView>
    </main>

    <!-- Transcript Debug FAB Button (pixel art ocean) — hidden in PiP -->
    <div v-if="!isPipWindow" class="transcript-fab-wrap" :class="{ 'sheet-open': showVoice || showTranscriptDebug, 'keyboard-visible': keyboardVisible }" @contextmenu.prevent>
      <TerminalFabStack
        :terminals="extraTerminals"
        :active-session-id="transcriptDebugRef?.activeTerminalSessionId ?? null"
        @select="handleFabTerminalSelect"
        @create-session="handleFabCreateSession"
      />
      <div v-if="hasTerminals" class="fab-button-area">
        <span class="fab-bubble b1"></span>
        <span class="fab-bubble b2"></span>
        <span class="fab-bubble b3"></span>
        <button
          class="transcript-fab"
          :class="{ active: showTranscriptDebug, 't1-active': isT1Active }"
          @click="handleMainFabClick"
          @contextmenu.prevent
          title="Transcript Debug"
        >
          <!-- Pixel art chat bubble icon -->
          <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" shape-rendering="crispEdges">
            <rect x="4" y="2" width="12" height="2" fill="currentColor"/>
            <rect x="2" y="4" width="2" height="8" fill="currentColor"/>
            <rect x="16" y="4" width="2" height="8" fill="currentColor"/>
            <rect x="4" y="12" width="12" height="2" fill="currentColor"/>
            <rect x="4" y="14" width="2" height="2" fill="currentColor"/>
            <rect x="2" y="16" width="2" height="2" fill="currentColor"/>
            <!-- Dots inside -->
            <rect x="6" y="7" width="2" height="2" fill="currentColor" opacity="0.5"/>
            <rect x="9" y="7" width="2" height="2" fill="currentColor" opacity="0.5"/>
            <rect x="12" y="7" width="2" height="2" fill="currentColor" opacity="0.5"/>
          </svg>
        </button>
      </div>
    </div>

    <!-- Voice FAB Button (hidden) -->
    <button
      v-show="false"
      class="voice-fab"
      :class="{ active: showVoice, 'sheet-open': showVoice || showTranscriptDebug, 'ptt-active': voicePTTActive, 'keyboard-visible': keyboardVisible }"
      @click="handleVoiceFabClick"
      @touchstart="handleVoiceFabTouchStart"
      @touchend="handleVoiceFabTouchEnd"
      @touchcancel="handleVoiceFabTouchEnd"
      title="Voice Input (mantén presionado para PTT)"
    >
      <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
        <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
        <line x1="12" y1="19" x2="12" y2="23"/>
        <line x1="8" y1="23" x2="16" y2="23"/>
      </svg>
    </button>

    <!-- Floating Response (Agent UI messages) -->
    <FloatingResponse ref="responseRef" />


    <!-- Floating Voice Input -->
    <FloatingVoice ref="voiceRef" v-model="showVoice" />

    <!-- Floating Transcript Debug -->
    <FloatingTranscriptDebug ref="transcriptDebugRef" v-model="showTranscriptDebug" />

    <!-- Global Hooks Approval Modal -->
    <HooksApprovalModal />

    <!-- Tauri Server Config Dialog -->
    <ServerConfigDialog v-if="needsServerConfig || showServerConfig" />

    <!-- Debug Console Panel -->
    <Teleport to="body">
      <Transition name="debug-slide">
        <div v-if="showDebugConsole" class="debug-console">
          <div class="debug-header">
            <span>Debug Console ({{ debugLogs.length }})</span>
            <div class="debug-actions">
              <button @click="copyDebugLogs" title="Copy all">Copy</button>
              <button @click="clearDebugLogs" title="Clear">Clear</button>
              <button @click="showDebugConsole = false" title="Close">×</button>
            </div>
          </div>
          <div class="debug-logs">
            <div
              v-for="(log, i) in debugLogs"
              :key="i"
              class="debug-log"
              :class="log.type"
              @click="navigator.clipboard.writeText(log.message)"
            >
              <span class="log-time">{{ log.time }}</span>
              <span class="log-type">{{ log.type }}</span>
              <span class="log-msg">{{ log.message }}</span>
            </div>
            <div v-if="!debugLogs.length" class="debug-empty">No logs yet</div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<style scoped>
.app-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  background: var(--bg-primary);
}

/* PiP window: no header, no chrome, full bleed */
.app-container.pip-window {
  border-radius: 8px;
  overflow: hidden;
}

.app-container.pip-window .app-main {
  border-radius: 8px;
}

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 1rem;
  padding-top: calc(0.5rem + var(--sat, env(safe-area-inset-top, 0px)));
  padding-left: calc(1rem + var(--sal, env(safe-area-inset-left, 0px)));
  padding-right: calc(1rem + var(--sar, env(safe-area-inset-right, 0px)));
  background: var(--bg-primary);
  border-bottom: 1px solid var(--border-color);
  flex-shrink: 0;
  min-height: 40px;
  -webkit-app-region: drag;
  app-region: drag;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  -webkit-app-region: no-drag;
  app-region: no-drag;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  -webkit-app-region: no-drag;
  app-region: no-drag;
}

/* ── Compact header (WCO + manual toggle) ── */
.wco-header,
.wco-header.app-header {
  height: 32px;
  min-height: 32px;
  max-height: 32px;
  padding: 0 0.5rem;
  padding-right: 0;
  border-bottom: none;
  overflow: visible;
}

.wco-header .header-left { gap: 0.35rem; overflow: visible; }
.wco-header .header-right { gap: 0.2rem; }

.wco-header .toolbar-toggle {
  width: 22px;
  height: 22px;
  border-radius: 4px;
}
.wco-header .toolbar-toggle svg { width: 12px; height: 12px; }

.wco-header .header-sep { font-size: 0.65rem; }
.wco-header .header-canvas-name { font-size: 0.65rem; max-width: 100px; }
.wco-header .header-canvas-badge { font-size: 0.5rem; padding: 0 0.2rem; }
.wco-header :deep(.pwa-banner) { display: none; }

.wco-header .debug-btn {
  padding: 1px 4px;
  font-size: 8px;
  border-radius: 3px;
  gap: 2px;
}
.wco-header .debug-btn svg { width: 10px; height: 10px; }
.wco-header .log-count { font-size: 7px; padding: 0 2px; min-width: 10px; }

.wco-header .refresh-btn {
  width: 22px;
  height: 22px;
  border-radius: 4px;
}
.wco-header .refresh-btn svg { width: 12px; height: 12px; }

/* TorchButton compact via :deep */
.wco-header :deep(.trigger-split) { border-radius: 4px; font-size: 0.65rem; }
.wco-header :deep(.trigger-main) { padding: 0.1rem 0.25rem 0.1rem 0.35rem; gap: 0.25rem; }
.wco-header :deep(.trigger-chevron) { padding: 0.1rem 0.25rem; }
.wco-header :deep(.status-dot) { width: 5px; height: 5px; }
.wco-header :deep(.chevron) { width: 10px; height: 10px; }
.wco-header :deep(.trigger-name) { max-width: 60px; font-size: 0.65rem; }

/* Window Controls Overlay — real PWA titlebar */
@media (display-mode: window-controls-overlay) {
  .app-header {
    position: fixed;
    top: env(titlebar-area-y, 0);
    left: env(titlebar-area-x, 0);
    width: env(titlebar-area-width, 100%);
    height: env(titlebar-area-height, 32px);
    min-height: unset;
    max-height: env(titlebar-area-height, 32px);
    padding: 0 0.5rem;
    z-index: 10000;
    border-bottom: none;
    box-sizing: border-box;
    background: var(--bg-primary);
  }

  .app-container {
    padding-top: env(titlebar-area-height, 32px);
  }

  /* All the same compacting as .wco-header */
  .header-left { gap: 0.35rem; }
  .header-right { gap: 0.2rem; }
  .toolbar-toggle { width: 22px; height: 22px; border-radius: 4px; }
  .toolbar-toggle svg { width: 12px; height: 12px; }
  .header-sep { font-size: 0.65rem; }
  .header-canvas-name { font-size: 0.65rem; max-width: 100px; }
  .header-canvas-badge { font-size: 0.5rem; padding: 0 0.2rem; }
  .debug-btn { padding: 1px 4px; font-size: 8px; border-radius: 3px; gap: 2px; }
  .debug-btn svg { width: 10px; height: 10px; }
  .log-count { font-size: 7px; padding: 0 2px; min-width: 10px; }
  .refresh-btn { width: 22px; height: 22px; border-radius: 4px; }
  .refresh-btn svg { width: 12px; height: 12px; }
}

/* Tiny hidden toggle dot */
.wco-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--border-color);
  opacity: 0.18;
  cursor: pointer;
  transition: all 0.3s ease;
  flex-shrink: 0;
}

.wco-dot:hover {
  opacity: 0.5;
  transform: scale(1.8);
}

.wco-dot.on {
  background: #6366f1;
  opacity: 0.6;
  box-shadow: 0 0 4px rgba(99, 102, 241, 0.5);
}

.toolbar-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--border-color);
  border-radius: 5px;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  flex-shrink: 0;
}

.toolbar-toggle:hover {
  background: var(--bg-hover);
  color: var(--accent, #6366f1);
  border-color: var(--accent, #6366f1);
}

.toolbar-toggle svg {
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.toolbar-toggle.collapsed svg {
  transform: scaleX(-1);
}

.toolbar-toggle.collapsed {
  color: var(--accent, #6366f1);
  border-color: rgba(99, 102, 241, 0.3);
  background: rgba(99, 102, 241, 0.08);
}

.header-sep {
  color: var(--text-muted);
  font-size: 0.85rem;
  opacity: 0.4;
}

.header-canvas-name {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 160px;
}

.header-canvas-badge {
  padding: 0.0625rem 0.375rem;
  background: rgba(99, 102, 241, 0.15);
  color: #6366f1;
  font-size: 0.625rem;
  font-weight: 500;
  border-radius: 999px;
}

.refresh-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--border-color);
  border-radius: 5px;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.15s ease;
}

.refresh-btn:hover {
  background: var(--bg-hover);
  color: var(--accent);
  border-color: var(--accent);
}

.refresh-btn:active {
  transform: rotate(180deg);
}

/* Approval badge button */
.approval-badge-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: auto;
  min-width: 28px;
  height: 28px;
  padding: 0 6px;
  background: transparent;
  border: 1px solid var(--border-color);
  border-radius: 5px;
  color: #f59e0b;
  cursor: pointer;
  transition: all 0.15s ease;
  position: relative;
}

.approval-badge-btn:hover {
  background: var(--bg-hover);
  border-color: #f59e0b;
}

.approval-badge-btn.active {
  background: rgba(245, 158, 11, 0.15);
  border-color: #f59e0b;
}

.approval-badge-btn.pulse {
  animation: approval-badge-pulse 2s ease-in-out infinite;
}

.approval-count {
  background: #ef4444;
  color: white;
  font-size: 9px;
  font-weight: 700;
  padding: 0 4px;
  border-radius: 8px;
  min-width: 14px;
  text-align: center;
  line-height: 1.4;
}

@keyframes approval-badge-pulse {
  0%, 100% { box-shadow: none; }
  50% { box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); }
}

.app-main {
  display: flex;
  flex: 1;
  overflow: hidden;
}

/* Page transitions */
.page-enter-active,
.page-leave-active {
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.page-enter-from {
  opacity: 0;
  transform: translateX(20px);
}

.page-leave-to {
  opacity: 0;
  transform: translateX(-20px);
}

/* Voice FAB */
.voice-fab {
  position: fixed;
  bottom: 20px;
  left: 20px;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  color: white;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  z-index: 9998;
  /* Prevent text selection and touch gestures */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
}

.voice-fab:hover {
  transform: scale(1.08);
  box-shadow: 0 8px 24px rgba(16, 185, 129, 0.5);
}

.voice-fab.active {
  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
  box-shadow: 0 6px 20px rgba(239, 68, 68, 0.4);
}

.voice-fab.active:hover {
  box-shadow: 0 8px 24px rgba(239, 68, 68, 0.5);
}

/* Voice FAB PTT active - recording in progress */
.voice-fab.ptt-active {
  background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
  box-shadow: 0 0 30px rgba(249, 115, 22, 0.7);
  transform: scale(1.15);
  animation: ptt-pulse 0.5s ease-in-out infinite;
}

@keyframes ptt-pulse {
  0%, 100% { box-shadow: 0 0 30px rgba(249, 115, 22, 0.7); }
  50% { box-shadow: 0 0 50px rgba(249, 115, 22, 0.9); }
}

/* Transcript Debug FAB wrapper — holds button + terminal stack */
.transcript-fab-wrap {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 10000;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

/* Transcript Debug FAB — pixel art night ocean, elevated */
.transcript-fab {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 0;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44' viewBox='0 0 44 44' shape-rendering='crispEdges'%3E%3Crect width='44' height='8' fill='%23050a14'/%3E%3Crect y='8' width='44' height='4' fill='%23061020'/%3E%3Crect y='12' width='44' height='4' fill='%23071828'/%3E%3Crect y='16' width='44' height='4' fill='%23081e30'/%3E%3Crect y='20' width='44' height='4' fill='%23092438'/%3E%3Crect y='24' width='44' height='4' fill='%230a2a3e'/%3E%3Crect y='28' width='44' height='4' fill='%23082030'/%3E%3Crect y='32' width='44' height='4' fill='%23061828'/%3E%3Crect y='36' width='44' height='8' fill='%231a1810'/%3E%3Crect x='34' y='2' width='3' height='3' fill='%23e8e4c8' opacity='0.6'/%3E%3Crect x='35' y='1' width='2' height='1' fill='%23e8e4c8' opacity='0.35'/%3E%3Crect x='35' y='5' width='1' height='1' fill='%23c8c4a8' opacity='0.2'/%3E%3Crect x='6' y='2' width='1' height='1' fill='%23c8d8f0' opacity='0.5'/%3E%3Crect x='14' y='4' width='1' height='1' fill='%23c8d8f0' opacity='0.35'/%3E%3Crect x='24' y='1' width='1' height='1' fill='%23c8d8f0' opacity='0.4'/%3E%3Crect x='40' y='6' width='1' height='1' fill='%23c8d8f0' opacity='0.3'/%3E%3Crect x='18' y='7' width='1' height='1' fill='%23c8d8f0' opacity='0.25'/%3E%3Crect x='2' y='9' width='1' height='1' fill='%23c8d8f0' opacity='0.2'/%3E%3Crect x='30' y='3' width='1' height='1' fill='%23c8d8f0' opacity='0.3'/%3E%3Crect x='10' y='6' width='1' height='1' fill='%23fde68a' opacity='0.2'/%3E%3Crect x='3' y='10' width='6' height='2' fill='%230ea5e9' opacity='0.15'/%3E%3Crect x='9' y='11' width='8' height='2' fill='%230284c7' opacity='0.12'/%3E%3Crect x='17' y='10' width='4' height='2' fill='%230ea5e9' opacity='0.15'/%3E%3Crect x='25' y='11' width='10' height='2' fill='%230284c7' opacity='0.12'/%3E%3Crect x='35' y='10' width='6' height='2' fill='%230ea5e9' opacity='0.15'/%3E%3Crect x='3' y='10' width='2' height='1' fill='%23c8d8f0' opacity='0.1'/%3E%3Crect x='17' y='10' width='2' height='1' fill='%23c8d8f0' opacity='0.08'/%3E%3Crect x='35' y='10' width='2' height='1' fill='%23c8d8f0' opacity='0.1'/%3E%3Crect x='10' y='18' width='3' height='2' fill='%23f97316' opacity='0.35'/%3E%3Crect x='9' y='19' width='1' height='1' fill='%23fdba74' opacity='0.2'/%3E%3Crect x='30' y='24' width='2' height='1' fill='%23818cf8' opacity='0.3'/%3E%3Crect x='32' y='24' width='1' height='1' fill='%23a5b4fc' opacity='0.2'/%3E%3Crect x='20' y='30' width='1' height='1' fill='%23c8d8f0' opacity='0.1'/%3E%3Crect x='36' y='22' width='1' height='1' fill='%23c8d8f0' opacity='0.1'/%3E%3Crect x='6' y='26' width='1' height='1' fill='%23c8d8f0' opacity='0.08'/%3E%3Crect x='4' y='36' width='6' height='5' fill='%23052e1e' opacity='0.7'/%3E%3Crect x='5' y='32' width='2' height='4' fill='%23064e33' opacity='0.5'/%3E%3Crect x='8' y='34' width='2' height='2' fill='%23059669' opacity='0.35'/%3E%3Crect x='30' y='38' width='5' height='4' fill='%23052e1e' opacity='0.6'/%3E%3Crect x='32' y='35' width='2' height='3' fill='%23064e33' opacity='0.45'/%3E%3Crect x='18' y='39' width='4' height='3' fill='%23500e28' opacity='0.45'/%3E%3Crect x='19' y='37' width='2' height='2' fill='%23701838' opacity='0.35'/%3E%3Crect x='14' y='40' width='2' height='2' fill='%23052e1e' opacity='0.5'/%3E%3Crect x='38' y='40' width='3' height='2' fill='%231a1810' opacity='0.6'/%3E%3Crect x='24' y='42' width='2' height='1' fill='%23c8b060' opacity='0.15'/%3E%3Crect x='10' y='42' width='2' height='1' fill='%23c8b060' opacity='0.12'/%3E%3C/svg%3E");
  color: #0ea5e9;
  border: 1px solid rgba(14, 165, 233, 0.2);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow:
    0 2px 4px rgba(0, 0, 0, 0.5),
    0 6px 16px rgba(0, 0, 0, 0.6),
    0 12px 28px rgba(0, 0, 0, 0.4);
  filter: grayscale(1) brightness(0.6);
  transition: all 0.2s ease;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
  image-rendering: pixelated;
  pointer-events: auto;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.transcript-fab *,
.transcript-fab svg {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  pointer-events: none;
}

.transcript-fab:not(.t1-active):hover {
  transform: translateY(-2px);
  border-color: rgba(14, 165, 233, 0.35);
  filter: grayscale(0.5) brightness(0.8);
  box-shadow:
    0 4px 8px rgba(0, 0, 0, 0.5),
    0 10px 24px rgba(0, 0, 0, 0.6),
    0 0 14px rgba(14, 165, 233, 0.15);
  color: #38bdf8;
}

.transcript-fab.active {
  color: #67e8f9;
  border-color: rgba(14, 165, 233, 0.3);
}

.transcript-fab.active:hover {
  color: #a5f3fc;
}

.transcript-fab.t1-active {
  border: 2px solid;
  border-image: conic-gradient(
    from var(--border-angle, 0deg),
    rgba(34, 211, 238, 1),
    rgba(99, 102, 241, 0.7),
    rgba(34, 211, 238, 0.15),
    rgba(99, 102, 241, 0.7),
    rgba(34, 211, 238, 1)
  ) 1;
  filter: none;
  box-shadow: 0 0 12px rgba(34, 211, 238, 0.3);
  animation: border-spin 3s linear infinite;
}

@property --border-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@keyframes border-spin {
  to { --border-angle: 360deg; }
}

.fab-button-area {
  position: relative;
  width: 44px;
  height: 44px;
}

/* Bubble particles — very occasional */
.fab-bubble {
  position: absolute;
  width: 4px;
  height: 4px;
  background: rgba(14, 165, 233, 0.5);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
}

.fab-bubble.b1 {
  left: 10px;
  bottom: 36px;
  animation: fab-bubble-rise 18s ease-in 2s infinite;
}

.fab-bubble.b2 {
  left: 28px;
  bottom: 38px;
  animation: fab-bubble-rise 22s ease-in 9s infinite;
  width: 3px;
  height: 3px;
  background: rgba(34, 211, 238, 0.45);
}

.fab-bubble.b3 {
  left: 18px;
  bottom: 34px;
  animation: fab-bubble-rise 25s ease-in 16s infinite;
  width: 5px;
  height: 5px;
  background: rgba(14, 165, 233, 0.4);
}

@keyframes fab-bubble-rise {
  0%, 85%, 100% {
    opacity: 0;
    transform: translateY(0) translateX(0);
  }
  88% {
    opacity: 0.7;
    transform: translateY(-10px) translateX(2px);
  }
  92% {
    opacity: 0.5;
    transform: translateY(-24px) translateX(-1px);
  }
  96% {
    opacity: 0.2;
    transform: translateY(-40px) translateX(3px);
  }
  98% {
    opacity: 0;
    transform: translateY(-52px) translateX(1px);
  }
}

@media (max-width: 768px) {
  .voice-fab {
    bottom: 80px;
    left: 16px;
    width: 44px;
    height: 44px;
  }

  .transcript-fab-wrap {
    bottom: 80px;
    right: 16px;
  }

  .transcript-fab-wrap .fab-button-area {
    width: 40px;
    height: 40px;
  }

  .transcript-fab-wrap .transcript-fab {
    width: 40px;
    height: 40px;
  }
}

/* Mobile: FABs above bottom sheets */
@media (max-width: 1024px) and (pointer: coarse) {
  .voice-fab {
    z-index: 10001;
    transition: bottom 0.25s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s ease;
  }

  .transcript-fab-wrap {
    z-index: 10001;
  }

  .voice-fab.sheet-open {
    bottom: calc(15vh + 100px);
  }

  .voice-fab.keyboard-visible {
    bottom: 35vh;
  }

  .voice-fab.keyboard-visible.sheet-open {
    bottom: 45vh;
  }
}

/* Server Config Button (Tauri) */
.server-config-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--border-color);
  border-radius: 5px;
  color: var(--text-secondary);
  cursor: pointer;
  transition: all 0.15s ease;
}

.server-config-btn:hover {
  background: var(--bg-hover);
  color: var(--accent, #6366f1);
  border-color: var(--accent, #6366f1);
}

/* Debug Console Button */
.debug-btn {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 3px 6px;
  background: rgba(100, 100, 100, 0.2);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 5px;
  color: #888;
  font-size: 10px;
  cursor: pointer;
  transition: all 0.2s;
}

.debug-btn:hover {
  background: rgba(100, 100, 100, 0.3);
  color: #aaa;
}

.debug-btn.active {
  background: rgba(59, 130, 246, 0.3);
  border-color: rgba(59, 130, 246, 0.5);
  color: #60a5fa;
}

.log-count {
  background: #ef4444;
  color: white;
  font-size: 8px;
  padding: 1px 4px;
  border-radius: 10px;
  min-width: 14px;
  text-align: center;
  line-height: 1.2;
}

/* Debug Console Panel */
.debug-console {
  position: fixed;
  top: 50px;
  left: 10px;
  right: 10px;
  bottom: 80px;
  background: rgba(20, 20, 25, 0.98);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  z-index: 10002;
  display: flex;
  flex-direction: column;
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}

.debug-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 14px;
  background: rgba(255, 255, 255, 0.05);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px 12px 0 0;
  color: #ddd;
  font-size: 12px;
  font-weight: 600;
}

.debug-actions {
  display: flex;
  gap: 8px;
}

.debug-actions button {
  padding: 4px 10px;
  background: rgba(255, 255, 255, 0.1);
  border: none;
  border-radius: 4px;
  color: #aaa;
  font-size: 11px;
  cursor: pointer;
}

.debug-actions button:hover {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
}

.debug-logs {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
  font-family: 'Consolas', 'Monaco', monospace;
  font-size: 11px;
}

.debug-log {
  display: flex;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 4px;
  cursor: pointer;
  border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}

.debug-log:hover {
  background: rgba(255, 255, 255, 0.05);
}

.debug-log:active {
  background: rgba(59, 130, 246, 0.2);
}

.log-time {
  color: #666;
  flex-shrink: 0;
}

.log-type {
  width: 40px;
  flex-shrink: 0;
  font-weight: 600;
}

.debug-log.log .log-type { color: #888; }
.debug-log.warn .log-type { color: #f59e0b; }
.debug-log.error .log-type { color: #ef4444; }

.log-msg {
  color: #ccc;
  word-break: break-all;
}

.debug-log.warn .log-msg { color: #fcd34d; }
.debug-log.error .log-msg { color: #fca5a5; }

.debug-empty {
  color: #666;
  text-align: center;
  padding: 40px;
}

/* Debug slide animation */
.debug-slide-enter-active,
.debug-slide-leave-active {
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

.debug-slide-enter-from,
.debug-slide-leave-to {
  opacity: 0;
  transform: translateY(-20px);
}
</style>
