import type {
  ChatMessage,
  ChatToken,
  ContentBlock,
  ConversationListItem,
  ConversationStatusRecord,
  Message,
  MessageCreatedEvent,
  MessageUpdatedEvent,
  TypingMember,
} from "./types"

export type ConversationStatusListener = (status: ConversationStatusRecord | null) => void
export type ConversationsListener = (conversations: ConversationListItem[]) => void
export type ActiveConversationListener = (id: string | null) => void
export type TypingMembersListener = (members: TypingMember[]) => void

export type ClawChatClient = {
  disconnect: () => Promise<void>
  listConversations: () => ConversationListItem[]
  activeConversationId: () => string | null
  selectConversation: (
    conversationId: string,
  ) => Promise<{ messages: ChatMessage[]; status: ConversationStatusRecord | null }>
  createConversation: (title?: string) => Promise<string>
  renameConversation: (conversationId: string, title: string) => Promise<void>
  deleteConversation: (conversationId: string) => Promise<void>
  refreshConversations: () => Promise<ConversationListItem[]>
  send: (text: string) => Promise<void>
  loadHistory: (conversationId?: string) => Promise<ChatMessage[]>
  initialBotOnline: () => boolean
  conversationStatus: () => ConversationStatusRecord | null
  typingMembers: () => TypingMember[]
  onMessage: (cb: (msg: ChatMessage) => void) => () => void
  onMessageUpdated: (cb: (msg: ChatMessage) => void) => () => void
  onPresence: (cb: (online: boolean) => void) => () => void
  onConversations: (cb: ConversationsListener) => () => void
  onActiveConversation: (cb: ActiveConversationListener) => () => void
  onConversationStatus: (cb: ConversationStatusListener) => () => void
  onTypingMembers: (cb: TypingMembersListener) => () => void
}

export type ConnectInput = {
  envelope: ChatToken
  sandboxId: string
  currentUserId: string
}

export function emitSet<T>(set: Set<(v: T) => void>, value: T): void {
  for (const cb of set) cb(value)
}

export function onAdd<T>(set: Set<(v: T) => void>, cb: (v: T) => void): () => void {
  set.add(cb)
  return () => set.delete(cb)
}

export function blocksToText(content: ContentBlock[]): string {
  let out = ""
  for (const b of content) {
    if (b.type === "text") out += b.text
  }
  return out
}

const ULID_TIME_LEN = 10
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"

export function ulidToTimestamp(id: string): number {
  if (!id || id.length < ULID_TIME_LEN) return Date.now()
  const time = id.slice(0, ULID_TIME_LEN).toUpperCase()
  let ts = 0
  for (const ch of time) {
    const idx = ENCODING.indexOf(ch)
    if (idx === -1) return Date.now()
    ts = ts * ENCODING.length + idx
  }
  return ts
}

export function toChatMessage(m: Message, currentUserId: string): ChatMessage {
  return {
    id: m.id,
    text: m.deleted ? "[deleted message]" : blocksToText(m.content),
    user: m.senderId,
    created: new Date(ulidToTimestamp(m.id)),
    bot: m.senderId.startsWith("bot:") || m.senderId !== currentUserId,
  }
}

export function toChatMessageFromCreated(e: MessageCreatedEvent, currentUserId: string): ChatMessage {
  return {
    id: e.messageId,
    text: blocksToText(e.content),
    user: e.senderId,
    created: new Date(ulidToTimestamp(e.messageId)),
    bot: e.senderId.startsWith("bot:") || e.senderId !== currentUserId,
  }
}

export function toChatMessageFromUpdated(
  e: MessageUpdatedEvent,
  currentUserId: string,
  senderHint: string | null,
): ChatMessage {
  const sender = senderHint ?? "bot"
  return {
    id: e.messageId,
    text: blocksToText(e.content),
    user: sender,
    created: new Date(ulidToTimestamp(e.messageId)),
    bot: sender.startsWith("bot:") || sender !== currentUserId,
  }
}

const TYPING_TIMEOUT_MS = 5_000

export type TypingTracker = {
  start: (memberId: string) => void
  stop: (memberId: string) => void
  clear: () => void
  snapshot: () => TypingMember[]
  dispose: () => void
}

export function createTypingTracker(onChange: (members: TypingMember[]) => void): TypingTracker {
  const entries = new Map<string, number>()
  let timer: ReturnType<typeof setInterval> | null = null

  function snapshot(): TypingMember[] {
    const out: TypingMember[] = []
    for (const [memberId, expiresAt] of entries) {
      out.push({ memberId, at: expiresAt - TYPING_TIMEOUT_MS })
    }
    return out
  }

  function prune(): void {
    const now = Date.now()
    let removed = false
    for (const [memberId, expiresAt] of entries) {
      if (expiresAt <= now) {
        entries.delete(memberId)
        removed = true
      }
    }
    if (removed) onChange(snapshot())
    if (entries.size === 0 && timer !== null) {
      clearInterval(timer)
      timer = null
    }
  }

  return {
    start(memberId: string): void {
      entries.set(memberId, Date.now() + TYPING_TIMEOUT_MS)
      if (timer === null) timer = setInterval(prune, 1_000)
      onChange(snapshot())
    },
    stop(memberId: string): void {
      if (!entries.delete(memberId)) return
      onChange(snapshot())
      if (entries.size === 0 && timer !== null) {
        clearInterval(timer)
        timer = null
      }
    },
    clear(): void {
      if (entries.size === 0) return
      entries.clear()
      onChange(snapshot())
    },
    snapshot,
    dispose(): void {
      if (timer !== null) {
        clearInterval(timer)
        timer = null
      }
      entries.clear()
    },
  }
}
