import type { WebSocket } from 'ws'
import type { ClientMessage, ServerMessage } from './types.js'
import { enqueue } from '../utils/messageQueueManager.js'
import { getSessions, updateSessionState, updatePendingApprovals } from './sessionRegistry.js'

type PendingApproval = {
  requestId: string
  resolve: (decision: 'allow' | 'deny') => void
}

const connectedClients = new Set<WebSocket>()
const pendingApprovals = new Map<string, PendingApproval>()

export function addClient(ws: WebSocket): void {
  connectedClients.add(ws)
  // Send current sessions on connect
  broadcastSessions()

  ws.on('message', (raw) => {
    try {
      const msg = JSON.parse(raw.toString()) as ClientMessage
      handleClientMessage(ws, msg)
    } catch {
      sendTo(ws, { type: 'error', message: 'Invalid message format' })
    }
  })

  ws.on('close', () => {
    connectedClients.delete(ws)
  })
}

function handleClientMessage(ws: WebSocket, msg: ClientMessage): void {
  switch (msg.type) {
    case 'user_message':
      enqueue({
        value: msg.text,
        mode: 'prompt',
        origin: { kind: 'channel' },
        skipSlashCommands: false,
      })
      break

    case 'tool_decision': {
      const pending = pendingApprovals.get(msg.requestId)
      if (pending) {
        pending.resolve(msg.decision)
        pendingApprovals.delete(msg.requestId)
        // Update count
        const sessionApprovals = Array.from(pendingApprovals.values()).length
        updatePendingApprovals(msg.sessionId, sessionApprovals)
        broadcastSessions()
      }
      break
    }

    case 'list_sessions':
      sendTo(ws, { type: 'sessions_update', sessions: getSessions() })
      break

    case 'cancel':
      enqueue({
        value: '',
        mode: 'prompt',
        priority: 'now',
        origin: { kind: 'channel' },
      })
      break
  }
}

/** Called by permission system when a tool call needs approval */
export function requestApproval(
  sessionId: string,
  requestId: string,
  toolName: string,
  input: Record<string, unknown>,
  description?: string,
): Promise<'allow' | 'deny'> {
  return new Promise((resolve) => {
    pendingApprovals.set(requestId, { requestId, resolve })
    updatePendingApprovals(sessionId, pendingApprovals.size)
    broadcast({
      type: 'tool_call',
      sessionId,
      requestId,
      toolName,
      input,
      description,
    })
    broadcastSessions()
  })
}

/** Called when assistant streams text */
export function sendAssistantMessage(sessionId: string, text: string, done: boolean): void {
  broadcast({ type: 'assistant_message', sessionId, text, done })
  if (done) {
    updateSessionState(sessionId, 'idle')
  }
}

/** Called when a tool produces output */
export function sendToolResult(sessionId: string, requestId: string, output: string): void {
  broadcast({ type: 'tool_result', sessionId, requestId, output })
}

/** Called when session state changes */
export function sendSessionState(sessionId: string, state: import('./types.js').SessionState): void {
  updateSessionState(sessionId, state)
  broadcast({ type: 'session_state', sessionId, state })
}

export function broadcastSessions(): void {
  broadcast({ type: 'sessions_update', sessions: getSessions() })
}

function broadcast(msg: ServerMessage): void {
  const data = JSON.stringify(msg)
  for (const client of connectedClients) {
    if (client.readyState === 1) { // WebSocket.OPEN
      client.send(data)
    }
  }
}

function sendTo(ws: WebSocket, msg: ServerMessage): void {
  if (ws.readyState === 1) {
    ws.send(JSON.stringify(msg))
  }
}

export function getConnectedClientCount(): number {
  return connectedClients.size
}

/** Returns true when at least one RC mobile client is connected. */
export function isRCActive(): boolean {
  return connectedClients.size > 0
}
