/**
 * In-memory presence + peer registry for the staff live-chat module.
 *
 * Presence = *connected* AND *recently active*. A peer reports its own activity
 * (active/idle) over the socket; a user is:
 *   - online  → ≥1 connected peer that is currently active
 *   - away    → ≥1 connected peer but all are idle (no interaction ≥ idle window)
 *   - offline → no connected peers
 * This is what makes a "logged in but away for an hour" user show as away, not
 * online.
 *
 * ⚠️ PER-PROCESS (same limitation as notificationBus). Correct ONLY while PM2
 * runs a single instance (ecosystem.config.cjs → instances: 1). At 2+ instances
 * presence is split-brain (instance A can't see instance B's peers) and the
 * offline-vs-deliver decision is wrong for ~half of users. Before scaling, move
 * presence + fan-out onto a Redis backplane (publish presence/message events;
 * each instance re-emits locally; presence as a Redis hash with heartbeat TTL).
 *
 * Stashed on globalThis so HMR / multiple imports share one registry.
 */

const STALE_MS = 35000 // drop a peer we haven't heard a ping from in this long
const SWEEP_MS = 30000

export interface PeerMeta {
  peer: any
  peerId: string
  userId: number
  roleId: number
  token: string
  active: boolean
  lastPong: number
}

interface Store {
  peers: Map<string, PeerMeta>
  byUser: Map<number, Set<string>>
  sweepTimer: any
}

const g = globalThis as any
const store: Store = g.__chatPresence ?? (g.__chatPresence = {
  peers: new Map<string, PeerMeta>(),
  byUser: new Map<number, Set<string>>(),
  sweepTimer: null
})

const now = () => Date.now()

const send = (peer: any, frame: any) => {
  try { peer.send(JSON.stringify(frame)) } catch { /* peer gone */ }
}

export const statusOf = (userId: number): 'online' | 'away' | 'offline' => {
  const set = store.byUser.get(userId)
  if (!set || set.size === 0) return 'offline'
  for (const peerId of set) {
    const m = store.peers.get(peerId)
    if (m && m.active) return 'online'
  }
  return 'away'
}

const snapshot = () => {
  const online: number[] = []
  const away: number[] = []
  for (const userId of store.byUser.keys()) {
    const s = statusOf(userId)
    if (s === 'online') online.push(userId)
    else if (s === 'away') away.push(userId)
  }
  return { online, away }
}

/** Broadcast a frame to every connected staff peer on this instance. */
const broadcastAll = (frame: any) => {
  store.peers.forEach((m) => send(m.peer, frame))
}

const broadcastPresence = (userId: number) => {
  broadcastAll({ type: 'presence', userId, status: statusOf(userId) })
}

/** Send a frame to every peer (tab) belonging to one user. */
export const sendToUser = (userId: number, frame: any) => {
  const set = store.byUser.get(userId)
  if (!set) return
  for (const peerId of set) {
    const m = store.peers.get(peerId)
    if (m) send(m.peer, frame)
  }
}

export const isConnected = (userId: number) => {
  const set = store.byUser.get(userId)
  return !!set && set.size > 0
}

export const getPeer = (peerId: string) => store.peers.get(peerId)

const startSweep = () => {
  if (store.sweepTimer) return
  store.sweepTimer = setInterval(() => {
    const t = now()
    for (const [peerId, m] of store.peers) {
      if (t - m.lastPong > STALE_MS) {
        try { m.peer.terminate?.() } catch {}
        removePeer(peerId)
      }
    }
  }, SWEEP_MS)
}

const stopSweep = () => {
  if (store.sweepTimer) {
    clearInterval(store.sweepTimer)
    store.sweepTimer = null
  }
}

export const addPeer = (meta: Omit<PeerMeta, 'active' | 'lastPong'>) => {
  const prev = statusOf(meta.userId)
  const full: PeerMeta = { ...meta, active: true, lastPong: now() }
  store.peers.set(meta.peerId, full)
  let set = store.byUser.get(meta.userId)
  if (!set) { set = new Set(); store.byUser.set(meta.userId, set) }
  set.add(meta.peerId)

  startSweep()

  // Tell the new peer it's connected, and hand it the current presence snapshot.
  send(full.peer, { type: 'connected', userId: meta.userId })
  send(full.peer, { type: 'presence-snapshot', ...snapshot() })

  // If this user just transitioned (offline→online), let everyone know.
  if (statusOf(meta.userId) !== prev) broadcastPresence(meta.userId)
}

export const removePeer = (peerId: string) => {
  const m = store.peers.get(peerId)
  if (!m) return
  const prev = statusOf(m.userId)
  store.peers.delete(peerId)
  const set = store.byUser.get(m.userId)
  if (set) {
    set.delete(peerId)
    if (set.size === 0) store.byUser.delete(m.userId)
  }
  if (store.peers.size === 0) stopSweep()
  if (statusOf(m.userId) !== prev) broadcastPresence(m.userId)
}

export const setActive = (peerId: string, active: boolean) => {
  const m = store.peers.get(peerId)
  if (!m) return
  const prev = statusOf(m.userId)
  m.active = active
  m.lastPong = now()
  if (statusOf(m.userId) !== prev) broadcastPresence(m.userId)
}

export const touch = (peerId: string) => {
  const m = store.peers.get(peerId)
  if (m) m.lastPong = now()
}
