import type { RealtimeEvent } from './event-types.js'; import type { DashboardEventPublisher } from './event-publisher.js'; type Published = RealtimeEvent; type SyncInput = | { phase: 'started'; id: string; kind: 'delta' | 'full' | 'media' } | { phase: 'progress'; id: string; kind: 'delta' | 'full' | 'media'; current: number; total?: number; label?: string } | { phase: 'completed'; id: string; kind: 'delta' | 'full' | 'media'; processed: number } | { phase: 'failed'; id: string; kind: 'delta' | 'full' | 'media'; error: string; code?: string }; const syncKind = (kind: SyncInput['kind']) => kind === 'delta' ? 'conversations' as const : kind; const errorReason = (error: unknown) => error instanceof Error ? error.message : String(error); export function createDashboardEventAdapters(hub: DashboardEventPublisher) { return { bot(input: { running: boolean; dryRun: boolean; transitioning?: 'starting' | 'stopping'; reason?: string }): Published { return hub.publish('bot.status.changed', { status: input.transitioning ?? (input.running ? 'running' : 'stopped'), dryRun: input.dryRun, reason: input.reason }); }, messageSent(input: { id: string; username: string; conversationId?: string }): Published { return hub.publish('message.sent', { messageId: input.id, recipient: input.username, conversationId: input.conversationId }); }, messageFailed(input: { username: string; error: unknown; messageId?: string; conversationId?: string; code?: string; retryable?: boolean }): Published { return hub.publish('message.failed', { recipient: input.username, reason: errorReason(input.error), messageId: input.messageId, conversationId: input.conversationId, code: input.code, retryable: input.retryable }); }, conversation(input: { id: string; state: 'new' | 'awaiting_reply' | 'replied' | 'closed'; unread?: number }): Published { return hub.publish('conversation.updated', { conversationId: input.id, status: input.state === 'awaiting_reply' ? 'awaiting-reply' : input.state, unreadCount: input.unread }); }, reply(input: { conversationId: string; id: string; username: string; body?: string }): Published { return hub.publish('reply.received', { conversationId: input.conversationId, replyId: input.id, sender: input.username, preview: input.body }); }, mediaReceived(input: { conversationId: string; username: string; count: number }): Published { return hub.publish('media.received', { conversationId: input.conversationId, sender: input.username, count: input.count }); }, sync(input: SyncInput): Published { const kind = syncKind(input.kind); switch (input.phase) { case 'started': return hub.publish('sync.started', { syncId: input.id, kind }); case 'progress': return hub.publish('sync.progress', { syncId: input.id, kind, completed: input.current, total: input.total, message: input.label }); case 'completed': return hub.publish('sync.completed', { syncId: input.id, kind, processed: input.processed }); case 'failed': return hub.publish('sync.failed', { syncId: input.id, kind, reason: input.error, code: input.code }); } }, rateLimit(input: { blocked: boolean; remaining?: number; resetAt?: number; reason?: string }): Published { return hub.publish('rate-limit.changed', { limited: input.blocked, remaining: input.remaining, resetsAt: input.resetAt === undefined ? undefined : new Date(input.resetAt).toISOString(), reason: input.reason }); }, redditSession(input: { state: 'unknown' | 'authenticating' | 'authenticated' | 'expired' | 'login-required' | 'unreachable'; username?: string; reason?: string }): Published { const status = input.state === 'login-required' ? 'logged-out' : input.state === 'unreachable' ? 'error' : input.state; return hub.publish('reddit.session.changed', { status, username: input.username, reason: input.reason }); }, log(input: { level: 'debug' | 'info' | 'warn' | 'error'; message: string; data?: unknown }): Published { return hub.publish('log.created', input); }, }; }