import { writable } from 'svelte/store'
import { sseUrl } from '$lib/config'

export interface OikosEvent {
  id: number
  ts: string
  type: string
  entity_id?: string | null
  severity: 'info' | 'warning' | 'critical'
  source: string
  data?: unknown
  correlation_id?: string | null
}

const MAX_BUFFERED = 200

export const liveEvents = writable<OikosEvent[]>([])

let source: EventSource | null = null
let subscriberCount = 0
let retry: ReturnType<typeof setTimeout> | null = null
let backoff = 0

/** True while the stream is live. Every live surface is only as fresh as this. */
export const eventsConnected = writable(true)

const RETRY_BASE_MS = 1000
const RETRY_MAX_MS = 30000

async function connect() {
  if (source) return
  // The browser's EventSource sends Last-event-ID automatically on reconnect,
  // so a reconnect replays whatever was missed rather than leaving a hole.
  // sseUrl is async so the OIDC access token is refreshed if expired.
  const es = new EventSource(await sseUrl('/api/v1/events/stream'))
  source = es

  es.onopen = () => {
    backoff = 0
    eventsConnected.set(true)
  }

  es.onmessage = (ev) => {
    try {
      const parsed: OikosEvent = JSON.parse(ev.data)
      liveEvents.update((events) => [parsed, ...events].slice(0, MAX_BUFFERED))
    } catch {
      // skip malformed
    }
  }

  // EventSource only auto-reconnects from a *transient* failure. Once it
  // reaches CLOSED — which is what an HTTP error on (re)connect produces, e.g.
  // the API restarting during a deploy — it stays closed and never retries.
  // Leaving that to the browser meant a single blip silently froze every live
  // surface in the app: health, signals and executions all just stopped
  // updating, with nothing on screen to say so. That is precisely the
  // stale-UI failure this whole change set exists to remove.
  es.onerror = () => {
    if (es.readyState !== EventSource.CLOSED) return // transient; browser retries
    eventsConnected.set(false)
    if (source === es) source = null
    es.close()
    if (subscriberCount === 0 || retry) return
    backoff = backoff ? Math.min(backoff * 2, RETRY_MAX_MS) : RETRY_BASE_MS
    retry = setTimeout(() => {
      retry = null
      if (subscriberCount > 0) connect()
    }, backoff)
  }
}

function disconnect() {
  if (retry) {
    clearTimeout(retry)
    retry = null
  }
  backoff = 0
  source?.close()
  source = null
}

// Reference-counted: the stream stays open as long as at least one page subscribes.
export function subscribeEvents(): () => void {
  subscriberCount++
  if (subscriberCount === 1) connect()
  return () => {
    subscriberCount--
    if (subscriberCount === 0) disconnect()
  }
}
