// Live command output for a chat session's currently-running execution.
//
// The chat renders a `run` tool call as "running" from the moment the tool_use
// arrives until its tool_result comes back — and for an auto-run that gap IS
// the command's runtime. Until now that window showed only the arguments; the
// output appeared all at once at the end.
//
// Correlation works because MCP-initiated executions now carry the chat
// session id as their correlation_id, and every execution.output event carries
// that through. So an event can be matched to the session on screen without a
// lookup. Nomos runs tools sequentially, so at most one execution is in flight
// per session — no ambiguity about which entry the output belongs to.
//
// Only auto-runs stream. A gated run returns "execution queued" immediately,
// so its tool entry is already `done` and the command executes minutes later
// after approval — the entity detail window is where that one is watched.

import { readable, type Readable } from 'svelte/store'
import { liveEvents, subscribeEvents } from './events'
import { fetchExecutionLogs } from '$lib/api'

export interface LiveExecutionOutput {
  executionId: string
  output: string
}

const cache = new Map<string, Readable<LiveExecutionOutput | null>>()

/**
 * Live output for whichever execution this session is currently running.
 * Resets when a different execution starts, so output from a previous command
 * never bleeds into the next one's entry.
 */
export function liveExecutionOutputFor(sessionId: string): Readable<LiveExecutionOutput | null> {
  const existing = cache.get(sessionId)
  if (existing) return existing

  const store = readable<LiveExecutionOutput | null>(null, (set) => {
    let currentId: string | null = null
    let inFlight = false
    // Coalesce: a refetch already running means the next event's data will be
    // covered by a single follow-up, rather than queueing a request per event.
    let queued = false

    async function refresh(executionId: string) {
      if (inFlight) {
        queued = true
        return
      }
      inFlight = true
      try {
        const logs = await fetchExecutionLogs(executionId)
        // Drop a response for an execution that is no longer current.
        if (currentId === executionId) set({ executionId, output: logs.combined })
      } finally {
        inFlight = false
        if (queued) {
          queued = false
          if (currentId) refresh(currentId)
        }
      }
    }

    const unsubscribeSSE = subscribeEvents()
    const unsubscribeEvents = liveEvents.subscribe((events) => {
      const ev = events[0]
      if (!ev) return

      // Lifecycle end: clear so the finished command's output stops being
      // shown against a new "running" entry.
      if (
        ev.correlation_id === sessionId &&
        (ev.type === 'execution.completed' ||
          ev.type === 'execution.failed' ||
          ev.type === 'execution.cancelled')
      ) {
        currentId = null
        set(null)
        return
      }

      if (ev.type !== 'execution.output') return
      if (ev.correlation_id !== sessionId) return
      if (!ev.entity_id) return

      if (ev.entity_id !== currentId) {
        currentId = ev.entity_id
        set({ executionId: currentId, output: '' })
      }
      refresh(ev.entity_id)
    })

    return () => {
      unsubscribeEvents()
      unsubscribeSSE()
      cache.delete(sessionId)
    }
  })

  cache.set(sessionId, store)
  return store
}
