import type {
  BotStatusEvent,
  ChatMessage,
  ConversationActivityEvent,
  ConversationListItem,
  ConversationRenamedEvent,
  ConversationStatusEvent,
  ConversationStatusRecord,
  MessageCreatedEvent,
  MessageUpdatedEvent,
  TypingEvent,
} from "./types"
import type {
  ActiveConversationListener,
  ConversationStatusListener,
  ConversationsListener,
  TypingMembersListener,
  TypingTracker,
} from "./client-helpers"
import { emitSet, toChatMessageFromCreated, toChatMessageFromUpdated } from "./client-helpers"
import type { EventServiceClient } from "./event-service-client"

type EventDeps = {
  events: EventServiceClient
  sandboxCtx: string
  sandboxId: string
  currentUserId: string
  getActiveCtx: () => string | null
  getActiveId: () => string | null
  senderCache: Map<string, string>
  lastSeenByConv: Map<string, string>
  getConversations: () => ConversationListItem[]
  setConversations: (c: ConversationListItem[]) => void
  setActiveStatus: (s: ConversationStatusRecord | null) => void
  loadConversations: () => Promise<ConversationListItem[]>
  setActive: (id: string | null) => void
  typing: TypingTracker
  messageListeners: Set<(msg: ChatMessage) => void>
  messageUpdatedListeners: Set<(msg: ChatMessage) => void>
  presenceListeners: Set<(online: boolean) => void>
  conversationsListeners: Set<ConversationsListener>
  statusListeners: Set<ConversationStatusListener>
}

export function setupEvents(d: EventDeps): void {
  d.events.on("conversation.created", (ctx) => {
    if (ctx !== d.sandboxCtx) return
    void d.loadConversations()
  })

  d.events.on("conversation.renamed", (ctx, e: ConversationRenamedEvent) => {
    if (ctx !== d.sandboxCtx) return
    d.setConversations(
      d.getConversations().map((c) =>
        c.conversationId === e.conversationId ? { ...c, title: e.title } : c,
      ),
    )
    emitSet(d.conversationsListeners, d.getConversations())
  })

  d.events.on("conversation.left", (ctx, e) => {
    if (ctx !== d.sandboxCtx) return
    d.setConversations(d.getConversations().filter((c) => c.conversationId !== e.conversationId))
    emitSet(d.conversationsListeners, d.getConversations())
    if (d.getActiveId() === e.conversationId) d.setActive(null)
  })

  d.events.on("conversation.activity", (ctx, e: ConversationActivityEvent) => {
    if (ctx !== d.sandboxCtx) return
    d.setConversations(
      d.getConversations().map((c) =>
        c.conversationId === e.conversationId ? { ...c, lastActivityAt: e.lastActivityAt } : c,
      ),
    )
    emitSet(d.conversationsListeners, d.getConversations())
  })

  d.events.on("bot.status", (ctx, e: BotStatusEvent) => {
    if (ctx !== d.sandboxCtx) return
    if (e.sandboxId !== d.sandboxId) return
    emitSet(d.presenceListeners, e.online)
  })

  d.events.on("message.created", (ctx, e: MessageCreatedEvent) => {
    if (ctx !== d.getActiveCtx()) return
    d.senderCache.set(e.messageId, e.senderId)
    const activeId = d.getActiveId()
    if (activeId) trackLastSeen(d, activeId, e.messageId)
    if (e.senderId !== d.currentUserId) d.typing.stop(e.senderId)
    emitSet(d.messageListeners, toChatMessageFromCreated(e, d.currentUserId))
  })

  d.events.on("message.updated", (ctx, e: MessageUpdatedEvent) => {
    if (ctx !== d.getActiveCtx()) return
    const sender = d.senderCache.get(e.messageId) ?? null
    emitSet(d.messageUpdatedListeners, toChatMessageFromUpdated(e, d.currentUserId, sender))
  })

  d.events.on("conversation.status", (ctx, e: ConversationStatusEvent) => {
    if (ctx !== d.getActiveCtx()) return
    if (e.conversationId !== d.getActiveId()) return
    const status: ConversationStatusRecord = {
      conversationId: e.conversationId,
      contextTokens: e.contextTokens,
      contextWindow: e.contextWindow,
      model: e.model,
      provider: e.provider,
      at: e.at,
      updatedAt: Date.now(),
    }
    d.setActiveStatus(status)
    emitSet(d.statusListeners, status)
  })

  d.events.on("typing", (ctx, e: TypingEvent) => {
    if (ctx !== d.getActiveCtx()) return
    if (e.memberId === d.currentUserId) return
    d.typing.start(e.memberId)
  })

  d.events.on("typing.stop", (ctx, e: TypingEvent) => {
    if (ctx !== d.getActiveCtx()) return
    if (e.memberId === d.currentUserId) return
    d.typing.stop(e.memberId)
  })
}

export function trackLastSeen(
  d: { lastSeenByConv: Map<string, string> },
  conversationId: string,
  messageId: string,
): void {
  if (!messageId || messageId.startsWith("pending-")) return
  const prev = d.lastSeenByConv.get(conversationId)
  if (!prev || prev < messageId) d.lastSeenByConv.set(conversationId, messageId)
}
