import { ref, shallowRef, computed, onUnmounted } from 'vue'
import { resolveEndpoints, terminalApiUrl } from '@/config/endpoints'
import { apiFetch } from '@/lib/tauri'
import { useEphemeralTerminal, type EphemeralTerminal } from '../useEphemeralTerminal'
import { useSessionState } from '@/stores/session-state'
import type {
  AgentName,
  SessionInfo,
  ParsedConversation,
  ConversationMessage,
  ParsedUserMessage,
  ParsedAssistantMessage,
  ParsedProgressGroup,
  ParsedSystemMessage,
  ParsedToolCall,
  ParsedToolResult,
  ParsedProgressEvent,
  TranscriptEntry,
  UserEntry,
  AssistantEntry,
  ProgressEntry,
  ContentBlock,
  ToolUseBlock,
  ToolResultBlock,
  TerminalSlot
} from '@/types/transcript-debug'

export function useTranscriptDebug() {
  // ── Centralized session state ──
  const sessionStore = useSessionState()

  const selectedAgent = ref<AgentName>('ejecutor')
  const sessions = ref<SessionInfo[]>([])
  const selectedSessionId = ref<string | null>(null)
  const rawContent = ref<string>('')
  const conversation = ref<ParsedConversation | null>(null)
  const loading = ref(false)
  const transitioning = ref(false)
  const transitionError = ref<string | null>(null)
  const error = ref<string | null>(null)
  const isRealtime = ref(false)

  // ── Hook metadata (derived from centralized session state) ──
  const hookMeta = computed(() => ({
    permissionMode: sessionStore.agents[selectedAgent.value]?.permissionMode || '',
    cwd: sessionStore.agents[selectedAgent.value]?.cwd || ''
  }))

  // ── Terminal registry (server-backed) ──

  const AGENT_CMD: Record<AgentName, string> = {
    ejecutor: 'ejecutor',
    nucleo000: 'nucleo000',
    claude: 'claude'
  }

  // Server registry data (from centralized session state, broadcast via WS)
  const serverRegistry = computed(() => sessionStore.terminalRegistry)

  // Local terminal instances (this client's active WS connections)
  const localTerminals = new Map<string, EphemeralTerminal>()

  // Which terminal is currently active for THIS client
  const activeTerminalSessionId = ref<string | null>(null)

  // Computed: current active terminal
  const ephemeral = computed<EphemeralTerminal | null>(() => {
    if (!activeTerminalSessionId.value) return null
    return localTerminals.get(activeTerminalSessionId.value) ?? null
  })

  // null = no terminal exists, false = terminal connecting/starting, true = running
  const terminalReady = computed<boolean | null>(() => {
    if (!ephemeral.value) return null
    return ephemeral.value.state.value === 'running'
  })

  // Computed: terminal list for AgentBadge dropdown (from server registry)
  const openTerminals = computed<TerminalSlot[]>(() =>
    serverRegistry.value.map(entry => ({
      sessionId: entry.transcriptSessionId,
      ephemeralSessionId: entry.ephemeralSessionId,
      agent: entry.agent as AgentName,
      label: entry.label,
      command: entry.command,
      active: entry.transcriptSessionId === activeTerminalSessionId.value,
      alive: entry.alive,
      clients: entry.clients
    }))
  )

  const awaitingNewSession = ref(false)
  const pendingPrompt = ref<string | null>(null)
  const lastStartError = ref<string | null>(null)

  // Guard: when a FAB click triggers switchToTerminal before init(), init() must not overwrite
  const pendingSwitchTarget = ref<string | null>(null)

  // ── Server registry HTTP helpers ──

  async function updateTerminalOnServer(
    ephemeralSessionId: string,
    updates: { transcriptSessionId?: string; label?: string }
  ) {
    try {
      await apiFetch(terminalApiUrl('/update-terminal'), {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ephemeralSessionId, ...updates })
      })
    } catch { /* best effort */ }
  }

  async function unregisterTerminalOnServer(ephemeralSessionId: string) {
    try {
      await apiFetch(terminalApiUrl('/unregister-terminal'), {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ephemeralSessionId })
      })
    } catch { /* best effort */ }
  }

  // ── Terminal lifecycle ──

  function getSessionLabel(sessionId: string): string {
    const session = sessions.value.find(s => s.id === sessionId)
    if (session?.firstUserMessage) {
      const msg = session.firstUserMessage.trim()
      return msg.length > 30 ? msg.slice(0, 30) + '...' : msg
    }
    return sessionId.slice(0, 12) + '...'
  }

  async function startTerminal(sessionId?: string) {
    const key = sessionId || '__new__'
    const cmd = sessionId
      ? `${AGENT_CMD[selectedAgent.value]} --resume "${sessionId}"`
      : AGENT_CMD[selectedAgent.value]

    console.log(`[TranscriptDebug] startTerminal called — key=${key} cmd=${cmd} url=${terminalApiUrl('/create-terminal')}`)

    try {
      // Server creates PTY, runs command, registers in registry, broadcasts
      const res = await apiFetch(terminalApiUrl('/create-terminal'), {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          agent: selectedAgent.value,
          transcriptSessionId: key,
          label: sessionId ? getSessionLabel(sessionId) : 'New session',
          command: cmd
        })
      })

      if (!res.ok) {
        const err = await res.json().catch(() => ({ error: 'Unknown error' }))
        console.error(`[TranscriptDebug] Failed to create terminal: ${err.error}`)
        lastStartError.value = err.error || 'Failed to create terminal'
        return
      }
      lastStartError.value = null

      const { ephemeralSessionId } = await res.json()

      // Connect to the existing PTY in reconnect mode
      const term = useEphemeralTerminal(cmd, ephemeralSessionId)
      localTerminals.set(key, term)
      activeTerminalSessionId.value = key

      term.start()
    } catch (e: any) {
      console.error('[TranscriptDebug] Terminal creation failed:', e.message)
    }
  }

  function parkCurrentTerminal() {
    const currentId = activeTerminalSessionId.value
    if (!currentId) return

    const term = localTerminals.get(currentId)
    if (term) {
      term.park()
      localTerminals.delete(currentId)
    }
    activeTerminalSessionId.value = null
  }

  function connectToTerminal(transcriptSessionId: string) {
    // Find the server registry entry
    const entry = serverRegistry.value.find(
      e => e.transcriptSessionId === transcriptSessionId
    )
    if (!entry) return

    // Create local EphemeralTerminal reusing the existing PTY session
    const term = useEphemeralTerminal(entry.command, entry.ephemeralSessionId)
    localTerminals.set(transcriptSessionId, term)
    activeTerminalSessionId.value = transcriptSessionId

    term.start()
  }

  async function closeTerminal(transcriptSessionId: string) {
    // Find the ephemeralSessionId from registry
    const entry = serverRegistry.value.find(
      e => e.transcriptSessionId === transcriptSessionId
    )
    const ephSid = entry?.ephemeralSessionId

    // Dispose local terminal if we have one
    const term = localTerminals.get(transcriptSessionId)
    if (term) {
      await term.dispose()
      localTerminals.delete(transcriptSessionId)
    } else if (ephSid) {
      // No local terminal — kill the PTY directly on server
      try {
        await apiFetch(terminalApiUrl('/kill-session'), {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ sessionId: ephSid })
        })
      } catch { /* best effort */ }
    }

    // Unregister if not auto-removed by kill
    if (ephSid) {
      await unregisterTerminalOnServer(ephSid)
    }

    // If it was active, switch to another or clear
    if (activeTerminalSessionId.value === transcriptSessionId) {
      activeTerminalSessionId.value = null
      const remaining = openTerminals.value.filter(t => t.sessionId !== transcriptSessionId)
      if (remaining.length > 0) {
        await switchToTerminal(remaining[remaining.length - 1].sessionId)
      }
    }
  }

  async function switchToTerminal(transcriptSessionId: string) {
    if (transcriptSessionId === activeTerminalSessionId.value) return

    pendingSwitchTarget.value = transcriptSessionId
    transitioning.value = true
    transitionError.value = null

    // Park current
    parkCurrentTerminal()

    // Small delay so the fade-out is visible before swapping content
    await new Promise(r => setTimeout(r, 150))

    // Find the entry — might be from another agent
    const entry = serverRegistry.value.find(
      e => e.transcriptSessionId === transcriptSessionId
    )
    if (entry && entry.agent !== selectedAgent.value) {
      // Switch agent context without disposing terminals
      selectedAgent.value = entry.agent as AgentName
      await fetchSessions()
    }

    // Refresh agent session state (hookHistory, status, etc.) from server
    const targetAgent = entry?.agent || selectedAgent.value
    if (targetAgent) {
      await sessionStore.refreshAgentState(targetAgent)
    }

    // Load the target session's transcript (skip for __new__ — no transcript yet)
    selectedSessionId.value = transcriptSessionId
    if (transcriptSessionId === '__new__') {
      // Brand-new session: no transcript to load, just clear conversation
      rawContent.value = ''
      conversation.value = null
      error.value = null
    } else {
      const ok = await fetchSessionContent(transcriptSessionId)
      if (!ok) {
        transitionError.value = error.value || `Failed to load session "${transcriptSessionId}"`
        transitioning.value = false
        // Still connect to the terminal so the user can interact
        connectToTerminal(transcriptSessionId)
        return
      }
    }

    // Connect to the terminal
    connectToTerminal(transcriptSessionId)

    pendingSwitchTarget.value = null
    transitioning.value = false
  }

  async function disposeAllLocalTerminals() {
    for (const [, term] of localTerminals) {
      await term.dispose()
    }
    localTerminals.clear()
    activeTerminalSessionId.value = null
  }

  async function createNewSession(initialPrompt?: string) {
    parkCurrentTerminal()

    selectedSessionId.value = null
    rawContent.value = ''
    conversation.value = null
    error.value = null
    optimisticProcessing.value = false
    optimisticMessage.value = null
    pendingPrompt.value = initialPrompt?.trim() || null

    awaitingNewSession.value = true
    await startTerminal() // no sessionId → brand new session
  }

  // ── WebSocket realtime ──

  let socket: WebSocket | null = null
  let reconnectTimer: ReturnType<typeof setTimeout> | null = null

  function connectRealtime() {
    if (socket?.readyState === WebSocket.OPEN) return

    // Same sync server as git (port 4105)
    socket = new WebSocket(resolveEndpoints().git)

    socket.onopen = () => {
      isRealtime.value = true
      console.log('[TranscriptDebug] Realtime connected')
    }

    socket.onmessage = (event) => {
      try {
        const msg = JSON.parse(event.data)
        if (msg.type === 'transcript-debug-change') {
          handleRealtimeChange(msg.sessionId)
        } else if (msg.type === 'transcript-debug-done') {
          handleRealtimeDone(msg.sessionId)
        }
      } catch {
        // Ignore non-JSON
      }
    }

    socket.onclose = () => {
      isRealtime.value = false
      console.log('[TranscriptDebug] Realtime disconnected, reconnecting...')
      reconnectTimer = setTimeout(connectRealtime, 2000)
    }

    socket.onerror = () => {
      isRealtime.value = false
    }
  }

  function disconnectRealtime() {
    if (reconnectTimer) {
      clearTimeout(reconnectTimer)
      reconnectTimer = null
    }
    if (socket) {
      socket.close()
      socket = null
    }
    isRealtime.value = false
  }

  async function handleRealtimeChange(changedSessionId: string) {
    // Refresh session list (new sessions or size changes)
    await fetchSessions()

    // Terminal registry is now updated via WS broadcast (no polling needed)

    // New session just appeared — lock onto it, re-key from __new__
    if (awaitingNewSession.value) {
      awaitingNewSession.value = false

      // Re-key the __new__ local terminal
      const newTerm = localTerminals.get('__new__')
      if (newTerm) {
        localTerminals.delete('__new__')
        localTerminals.set(changedSessionId, newTerm)

        // Update server registry with real sessionId
        await updateTerminalOnServer(newTerm.ephemeralSessionId, {
          transcriptSessionId: changedSessionId,
          label: getSessionLabel(changedSessionId)
        })

        if (activeTerminalSessionId.value === '__new__') {
          activeTerminalSessionId.value = changedSessionId
        }
      }

      selectedSessionId.value = changedSessionId
      await fetchSessionContent(changedSessionId)

      // Auto-send queued initial prompt
      if (pendingPrompt.value) {
        const prompt = pendingPrompt.value
        pendingPrompt.value = null
        // Small delay to let terminal fully settle
        setTimeout(() => sendPrompt(prompt), 300)
      }
      return
    }

    // If the changed session is the one we're viewing, re-fetch it
    if (selectedSessionId.value && selectedSessionId.value === changedSessionId) {
      await reloadCurrentSession()
    } else if (!selectedSessionId.value && sessions.value.length > 0) {
      // No session selected yet — auto-select the newest
      await selectSession(sessions.value[0].id)
    }

    // Update label for any terminal in the registry matching this session
    const entry = serverRegistry.value.find(e => e.transcriptSessionId === changedSessionId)
    if (entry) {
      const newLabel = getSessionLabel(changedSessionId)
      if (entry.label !== newLabel) {
        updateTerminalOnServer(entry.ephemeralSessionId, { label: newLabel })
      }
    }
  }

  function handleRealtimeDone(changedSessionId: string) {
    // Process exited — clear optimistic flag (server state will also be idle)
    if (selectedSessionId.value === changedSessionId) {
      optimisticProcessing.value = false
    }
  }

  async function reloadCurrentSession() {
    if (!selectedSessionId.value) return

    try {
      const res = await apiFetch(`/api/transcript-debug/${selectedSessionId.value}/raw?agent=${selectedAgent.value}`)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      rawContent.value = await res.text()
      const parsed = parseJsonl(rawContent.value, selectedSessionId.value)

      // Check if the optimistic user message now exists in the real JSONL
      if (optimisticMessage.value) {
        const optimisticText = optimisticMessage.value.content
        const found = parsed.messages.some(
          m => m.kind === 'user' && (m as ParsedUserMessage).content.includes(optimisticText)
        )
        if (found) {
          optimisticMessage.value = null
        } else {
          parsed.messages.push(optimisticMessage.value)
        }
      }

      // Clear optimistic processing if server state is now idle
      if (optimisticProcessing.value) {
        const agentState = sessionStore.agents[selectedAgent.value]
        if (agentState && ['idle', 'sessionStart', 'sessionEnd'].includes(agentState.status)) {
          optimisticProcessing.value = false
        }
      }

      conversation.value = parsed
    } catch (e: any) {
      console.error('[TranscriptDebug] Reload failed:', e.message)
    }
  }

  // Park local terminals on page close (don't kill — they persist for other clients)
  function onBeforeUnload() {
    for (const [, term] of localTerminals) {
      // Just park: close WS without killing server PTY
      try {
        term.park()
      } catch { /* best effort */ }
    }
  }

  if (typeof window !== 'undefined') {
    window.addEventListener('beforeunload', onBeforeUnload)
  }

  // Auto-cleanup on unmount
  onUnmounted(() => {
    // Park local terminals (don't kill server PTYs — they're global)
    for (const [, term] of localTerminals) {
      try { term.park() } catch {}
    }
    localTerminals.clear()
    activeTerminalSessionId.value = null

    disconnectRealtime()
    window.removeEventListener('beforeunload', onBeforeUnload)
  })

  // ── Send prompt ──

  const sending = ref(false)
  const optimisticProcessing = ref(false) // Set true on sendPrompt, auto-resets when server state catches up
  const optimisticMessage = ref<ParsedUserMessage | null>(null)

  // processing is derived from centralized state with optimistic override
  const processing = computed(() => {
    if (optimisticProcessing.value) return true
    const agentState = sessionStore.agents[selectedAgent.value]
    if (!agentState) return false
    return !['idle', 'sessionStart', 'sessionEnd'].includes(agentState.status)
  })

  async function sendPrompt(text: string) {
    if (!text.trim() || !selectedSessionId.value) return

    if (!ephemeral.value) {
      error.value = 'No terminal — use + to create a new session'
      return
    }
    if (ephemeral.value.state.value !== 'running') {
      error.value = 'Terminal not ready — wait for it to start'
      return
    }

    error.value = null
    ephemeral.value.sendInput(text)
    optimisticProcessing.value = true

    optimisticMessage.value = {
      kind: 'user',
      uuid: `optimistic-${Date.now()}`,
      timestamp: new Date().toISOString(),
      content: text,
      isMeta: false,
      isToolResult: false,
      toolResults: []
    } as ParsedUserMessage

    if (conversation.value) {
      conversation.value.messages.push(optimisticMessage.value)
    }
  }

  // ── API ──

  async function fetchSessions() {
    try {
      const res = await apiFetch(`/api/transcript-debug/sessions?agent=${selectedAgent.value}`)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      sessions.value = await res.json()
    } catch (e: any) {
      error.value = `Failed to load sessions: ${e.message}`
    }
  }

  async function fetchSessionContent(sessionId: string): Promise<boolean> {
    try {
      // Determine agent from registry if different from selected
      const entry = serverRegistry.value.find(e => e.transcriptSessionId === sessionId)
      const agent = entry?.agent || selectedAgent.value
      const res = await apiFetch(`/api/transcript-debug/${sessionId}/raw?agent=${agent}`)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      rawContent.value = await res.text()
      conversation.value = parseJsonl(rawContent.value, sessionId)
      return true
    } catch (e: any) {
      error.value = `Failed to load session: ${e.message}`
      return false
    }
  }

  async function init() {
    // If a FAB click already triggered switchToTerminal(), let it handle the connection.
    // Only load sessions and connect realtime — don't auto-connect to a terminal.
    if (pendingSwitchTarget.value) {
      await fetchSessions()
      connectRealtime()
      return
    }

    // Derive initial state from server registry: if there's an alive terminal, sync to it
    const aliveEntry = serverRegistry.value.find(e => e.alive)
    if (aliveEntry) {
      selectedAgent.value = (aliveEntry.agent as AgentName) || 'ejecutor'
    }

    await fetchSessions()

    // Re-check after async gap — switchToTerminal() may have started during fetchSessions
    if (pendingSwitchTarget.value) {
      connectRealtime()
      return
    }

    let targetSession: string | null = null

    if (aliveEntry) {
      // Sync to the alive terminal's transcript session
      targetSession = aliveEntry.transcriptSessionId
      // Validate it exists in sessions list
      if (targetSession && !sessions.value.some(s => s.id === targetSession)) {
        targetSession = null
      }
    }

    // Fall back to newest session
    if (!targetSession && sessions.value.length > 0) {
      targetSession = sessions.value[0].id
    }

    if (targetSession) {
      selectedSessionId.value = targetSession
      await fetchSessionContent(targetSession)

      // Final check before connecting — bail if user clicked a FAB during content fetch
      if (pendingSwitchTarget.value) {
        connectRealtime()
        return
      }

      // Connect to existing terminal if one exists (clients never create terminals — only the server does)
      const existing = serverRegistry.value.find(
        e => e.transcriptSessionId === targetSession && e.alive
      )
      if (existing) {
        connectToTerminal(targetSession)
      }
    } else {
      selectedSessionId.value = null
    }

    transitioning.value = false
    connectRealtime()
  }

  async function switchAgent(agent: AgentName) {
    if (agent === selectedAgent.value) return

    // Park local terminals (don't kill — they're global)
    parkCurrentTerminal()

    selectedAgent.value = agent
    error.value = null
    transitionError.value = null
    loading.value = true
    transitioning.value = true

    await new Promise(r => setTimeout(r, 150))

    rawContent.value = ''
    conversation.value = null

    await fetchSessions()

    if (sessions.value.length > 0) {
      selectedSessionId.value = sessions.value[0].id
      await fetchSessionContent(sessions.value[0].id)

      // Connect to existing terminal if one exists (clients never create terminals)
      const existing = serverRegistry.value.find(
        e => e.transcriptSessionId === sessions.value[0].id && e.alive
      )
      if (existing) {
        connectToTerminal(sessions.value[0].id)
      }
    } else {
      selectedSessionId.value = null
    }

    transitioning.value = false
    loading.value = false
  }

  async function selectSession(sessionId: string) {
    if (sessionId === selectedSessionId.value) return

    parkCurrentTerminal()

    error.value = null
    transitionError.value = null
    loading.value = true
    transitioning.value = true

    await new Promise(r => setTimeout(r, 150))

    selectedSessionId.value = sessionId
    await fetchSessionContent(sessionId)

    transitioning.value = false
    loading.value = false

    // Connect to existing terminal if one exists (clients never create terminals)
    const existing = serverRegistry.value.find(
      e => e.transcriptSessionId === sessionId && e.alive
    )
    if (existing) {
      connectToTerminal(sessionId)
    }
  }

  // ── JSONL Parser ──

  function parseJsonl(text: string, sessionId: string): ParsedConversation {
    const lines = text.split('\n').filter(l => l.trim())
    const entries: TranscriptEntry[] = []

    for (const line of lines) {
      try {
        entries.push(JSON.parse(line))
      } catch {
        // Skip malformed lines
      }
    }

    // Extract metadata from first meaningful entry
    let model = ''
    let version = ''
    let cwd = ''
    let gitBranch = ''
    let startTime = ''
    let endTime = ''

    for (const e of entries) {
      if ('sessionId' in e && e.sessionId) {
        if (!version && 'version' in e && e.version) version = e.version
        if (!cwd && 'cwd' in e && e.cwd) cwd = e.cwd
        if (!gitBranch && 'gitBranch' in e && e.gitBranch) gitBranch = e.gitBranch
      }
      if (e.type === 'assistant') {
        const ae = e as AssistantEntry
        if (!model && ae.message?.model) model = ae.message.model
      }
      if ('timestamp' in e && e.timestamp) {
        if (!startTime) startTime = e.timestamp
        endTime = e.timestamp
      }
    }

    const messages: ConversationMessage[] = []
    const assistantGroups = new Map<string, AssistantEntry[]>()
    const toolResultMap = new Map<string, ParsedToolResult>()
    const progressByTool = new Map<string, ParsedProgressEvent[]>()

    function buildProgressEvent(pe: ProgressEntry): ParsedProgressEvent {
      return {
        uuid: pe.uuid,
        timestamp: pe.timestamp,
        dataType: pe.data?.type || 'unknown',
        hookEvent: pe.data?.hookEvent as string | undefined,
        hookName: pe.data?.hookName as string | undefined,
        command: pe.data?.command as string | undefined,
        mcpStatus: pe.data?.status as string | undefined,
        mcpServerName: pe.data?.serverName as string | undefined,
        mcpToolName: pe.data?.toolName as string | undefined,
        mcpElapsedMs: pe.data?.elapsedTimeMs as number | undefined,
        data: pe.data as Record<string, unknown>
      }
    }

    // First pass: categorize all entries
    for (const entry of entries) {
      if (entry.type === 'file-history-snapshot') continue

      if (entry.type === 'assistant') {
        const ae = entry as AssistantEntry
        const msgId = ae.message?.id
        if (msgId) {
          if (!assistantGroups.has(msgId)) assistantGroups.set(msgId, [])
          assistantGroups.get(msgId)!.push(ae)
        }
        continue
      }

      if (entry.type === 'user') {
        const ue = entry as UserEntry
        const content = ue.message?.content
        if (Array.isArray(content)) {
          for (const block of content) {
            if (typeof block === 'object' && block.type === 'tool_result') {
              const tr = block as ToolResultBlock
              const resultContent = typeof tr.content === 'string'
                ? tr.content
                : Array.isArray(tr.content)
                  ? tr.content.map(b => 'text' in b ? b.text : JSON.stringify(b)).join('\n')
                  : ''
              toolResultMap.set(tr.tool_use_id, {
                toolUseId: tr.tool_use_id,
                content: resultContent,
                isError: !!tr.is_error
              })
            }
          }
        }
        continue
      }

      if (entry.type === 'progress') {
        const pe = entry as ProgressEntry
        if (pe.toolUseID) {
          if (!progressByTool.has(pe.toolUseID)) progressByTool.set(pe.toolUseID, [])
          progressByTool.get(pe.toolUseID)!.push(buildProgressEvent(pe))
        }
        continue
      }
    }

    // Second pass: build linear conversation
    let currentProgressBatch: ParsedProgressEvent[] = []

    for (const entry of entries) {
      if (entry.type === 'file-history-snapshot') continue

      if (entry.type === 'progress') {
        const pe = entry as ProgressEntry
        if (!pe.toolUseID) {
          currentProgressBatch.push(buildProgressEvent(pe))
        }
        continue
      }

      if (currentProgressBatch.length > 0) {
        messages.push({
          kind: 'progress',
          uuid: currentProgressBatch[0].uuid,
          timestamp: currentProgressBatch[0].timestamp,
          events: [...currentProgressBatch]
        } as ParsedProgressGroup)
        currentProgressBatch = []
      }

      if (entry.type === 'user') {
        const ue = entry as UserEntry
        const content = ue.message?.content

        const hasToolResult = Array.isArray(content) &&
          content.some(b => typeof b === 'object' && b.type === 'tool_result')
        const hasText = Array.isArray(content)
          ? content.some(b => typeof b === 'object' && b.type === 'text')
          : typeof content === 'string' && content.length > 0

        if (hasToolResult && !hasText) continue

        const textContent = typeof content === 'string'
          ? content
          : Array.isArray(content)
            ? content.filter(b => typeof b === 'object' && b.type === 'text').map(b => (b as any).text).join('\n')
            : ''

        messages.push({
          kind: 'user',
          uuid: ue.uuid,
          timestamp: ue.timestamp || '',
          content: textContent,
          isMeta: !!ue.isMeta,
          isToolResult: false,
          toolResults: []
        } as ParsedUserMessage)
        continue
      }

      if (entry.type === 'assistant') {
        const ae = entry as AssistantEntry
        const msgId = ae.message?.id
        if (!msgId) continue

        const group = assistantGroups.get(msgId)
        if (!group) continue

        if (group[0].uuid !== ae.uuid) continue

        const textBlocks: string[] = []
        const thinkingBlocks: string[] = []
        const toolCalls: ParsedToolCall[] = []

        for (const chunk of group) {
          if (!chunk.message?.content) continue
          for (const block of chunk.message.content) {
            if (block.type === 'text') {
              textBlocks.push(block.text)
            } else if (block.type === 'thinking') {
              thinkingBlocks.push(block.thinking)
            } else if (block.type === 'tool_use') {
              const tu = block as ToolUseBlock
              toolCalls.push({
                id: tu.id,
                name: tu.name,
                input: tu.input,
                result: toolResultMap.get(tu.id),
                progressEvents: progressByTool.get(tu.id) || []
              })
            }
          }
        }

        const lastChunk = group[group.length - 1]

        messages.push({
          kind: 'assistant',
          uuid: ae.uuid,
          messageId: msgId,
          timestamp: ae.timestamp || '',
          model: ae.message?.model || model,
          textBlocks,
          thinkingBlocks,
          toolCalls,
          usage: lastChunk.message?.usage,
          stopReason: lastChunk.message?.stop_reason
        } as ParsedAssistantMessage)
        continue
      }

      if (entry.type === 'system') {
        const se = entry as any
        const isTurnEnd = se.subtype === 'stop_hook_summary' || se.subtype === 'turn_duration'

        if (isTurnEnd) {
          // Merge consecutive turn-end messages into one divider
          const prev = messages[messages.length - 1]
          if (prev?.kind === 'system' && (prev as ParsedSystemMessage).subtype === 'turn_end') {
            // Merge into existing turn_end
            if (se.subtype === 'turn_duration' && se.durationMs != null) {
              (prev as ParsedSystemMessage).durationMs = se.durationMs
            }
          } else {
            messages.push({
              kind: 'system',
              uuid: se.uuid || crypto.randomUUID(),
              timestamp: se.timestamp || '',
              content: '',
              subtype: 'turn_end',
              durationMs: se.subtype === 'turn_duration' ? se.durationMs : undefined
            } as ParsedSystemMessage)
          }
        } else {
          const content = se.message?.content
          const textContent = typeof content === 'string'
            ? content
            : Array.isArray(content)
              ? content.filter((b: any) => b.type === 'text').map((b: any) => b.text).join('\n')
              : JSON.stringify(se.data || se.message || '')

          messages.push({
            kind: 'system',
            uuid: se.uuid || crypto.randomUUID(),
            timestamp: se.timestamp || '',
            content: textContent,
            subtype: se.subtype
          } as ParsedSystemMessage)
        }
      }
    }

    if (currentProgressBatch.length > 0) {
      messages.push({
        kind: 'progress',
        uuid: currentProgressBatch[0].uuid,
        timestamp: currentProgressBatch[0].timestamp,
        events: [...currentProgressBatch]
      } as ParsedProgressGroup)
    }

    return {
      sessionId,
      model,
      version,
      messages,
      metadata: {
        cwd,
        gitBranch,
        startTime,
        endTime,
        totalEntries: entries.length
      }
    }
  }

  const lineCount = computed(() => {
    if (!rawContent.value) return 0
    return rawContent.value.split('\n').filter(l => l.trim()).length
  })

  return {
    selectedAgent,
    sessions,
    selectedSessionId,
    rawContent,
    conversation,
    loading,
    transitioning,
    transitionError,
    error,
    lineCount,
    isRealtime,
    sending,
    processing,
    ephemeral,
    terminalReady,
    hookMeta,
    awaitingNewSession,
    lastStartError,
    openTerminals,
    activeTerminalSessionId,
    init,
    fetchSessions,
    switchAgent,
    selectSession,
    createNewSession,
    startTerminal,
    parkCurrentTerminal,
    fetchSessionContent,
    switchToTerminal,
    closeTerminal,
    connectRealtime,
    disconnectRealtime,
    sendPrompt
  }
}
