import type { Router } from 'vue-router'
import { queryClient, queryKeys } from '../api/queryClient'
import type { DomainEventDto, EntityType } from '../api/contracts'
import {
  reconcileTaskBoardCard,
  removeTaskBoardCardDelta,
} from '../api/taskBoard'
import { sseHub, type ParsedSseEvent, type SseConnectionState } from './sseHub'
export type { SseConnectionState } from './sseHub'

// The outbox sequence is global. Consuming the complete content-minimized
// stream prevents legitimate events from filtered-out channels looking like
// sequence gaps in the browser.
const STREAM_URL = '/api/v1/events'
let unsubscribe: (() => void) | null = null
let lastSequence = 0
let state: SseConnectionState = 'closed'
const listeners = new Set<(next: SseConnectionState) => void>()
const pendingInvalidations = new Set<string>()
let flushScheduled = false

function scheduleInvalidation(domain: string): void {
  pendingInvalidations.add(domain)
  if (flushScheduled) return
  flushScheduled = true
  window.setTimeout(async () => {
    flushScheduled = false
    const domains = [...pendingInvalidations]
    pendingInvalidations.clear()
    await Promise.all(domains.map(domain =>
      queryClient.invalidateQueries({
        queryKey: domain === 'openclaw-overview'
          ? queryKeys.openClawOverview()
          : [domain],
      }),
    ))
  }, 80)
}

function scheduleFullResync(): void {
  for (const domain of ['tasks', 'openclaw', 'projects', 'activity', 'notifications', 'incidents']) {
    scheduleInvalidation(domain)
  }
}

export function invalidateOpenClawRunEventQueries(entityId?: string): Promise<void> {
  return queryClient.invalidateQueries({
    predicate: query => {
      const key = query.queryKey
      if (key[0] !== 'openclaw' || key[1] !== 'runs') return false
      if (!entityId) return true
      const isList = key.length === 2
        || (typeof key[2] === 'object' && key[2] !== null)
      if (isList) return true
      return (key[2] === 'detail' || key[2] === 'history')
        && key[3] === entityId
    },
  })
}

export function invalidateOpenClawCronEventQueries(entityId?: string): Promise<void> {
  return queryClient.invalidateQueries({
    predicate: query => {
      const key = query.queryKey
      if (key[0] !== 'openclaw' || key[1] !== 'cron') return false
      if (!entityId) return true
      const isList = key.length === 2
        || (typeof key[2] === 'object' && key[2] !== null)
      if (isList) return true
      return (key[2] === 'detail' && key[3] === entityId)
        || (key[2] === 'runs' && key[3] === entityId)
    },
  })
}

function applyEvent(raw: ParsedSseEvent): void {
  if (raw.event === 'heartbeat') return
  const event = raw.data as Partial<DomainEventDto>
  if (!event || typeof event !== 'object') return
  window.dispatchEvent(new CustomEvent('nexus:domain-event', { detail: event }))

  if (event.eventType === 'resync_required') {
    if (typeof event.sequence === 'number') lastSequence = event.sequence
    scheduleFullResync()
    return
  }

  if (typeof event.sequence === 'number') {
    if (lastSequence > 0 && event.sequence > lastSequence + 1) {
      // The outbox sequence is global, so the missing event may belong to any
      // domain rather than to the entity carried by this first later event.
      scheduleFullResync()
    }
    lastSequence = Math.max(lastSequence, event.sequence)
  }

  if (!event.entity) return
  const entityType = event.entity.type as EntityType
  switch (entityType) {
    case 'task':
      if (event.entity.id && event.entity.id !== '*') {
        if (event.eventType === 'task.deleted') {
          removeTaskBoardCardDelta(event.entity.id)
        } else {
          void reconcileTaskBoardCard(event.entity.id)
        }
        void queryClient.invalidateQueries({
          queryKey: queryKeys.task(event.entity.id),
        })
      } else {
        scheduleInvalidation('tasks')
      }
      scheduleInvalidation('openclaw-overview')
      break
    case 'agent':
    case 'agent-proposal':
      scheduleInvalidation('openclaw')
      break
    case 'run':
      void invalidateOpenClawRunEventQueries(
        event.entity.id && event.entity.id !== '*' ? event.entity.id : undefined,
      )
      scheduleInvalidation('openclaw-overview')
      break
    case 'cron':
      void invalidateOpenClawCronEventQueries(
        event.entity.id && event.entity.id !== '*' ? event.entity.id : undefined,
      )
      scheduleInvalidation('openclaw-overview')
      break
    case 'project':
      scheduleInvalidation('projects')
      break
    case 'notification':
      scheduleInvalidation('notifications')
      break
    case 'activity':
      scheduleInvalidation('activity')
      break
    case 'incident':
      scheduleInvalidation('incidents')
      break
  }
}

function setState(next: SseConnectionState): void {
  state = next
  for (const listener of listeners) listener(next)
}

function start(): void {
  if (unsubscribe) return
  unsubscribe = sseHub.subscribe(STREAM_URL, applyEvent, {
    lastEventId: lastSequence > 0 ? String(lastSequence) : null,
    onStateChange: setState,
  })
}

function stop(): void {
  unsubscribe?.()
  unsubscribe = null
  setState('closed')
}

export function startDomainEventSync(router: Router): void {
  const syncForRoute = () => {
    if (router.currentRoute.value.meta.public) stop()
    else start()
  }
  void router.isReady().then(syncForRoute)
  router.afterEach(syncForRoute)
}

export function subscribeDomainEventState(listener: (next: SseConnectionState) => void): () => void {
  listeners.add(listener)
  listener(state)
  return () => listeners.delete(listener)
}

export function invalidateTaskBoard(): Promise<void> {
  return queryClient.invalidateQueries({ queryKey: queryKeys.taskBoard(50) })
}
