"""
SSE Event Bus — in-memory broadcaster for Server-Sent Events.

All routers import `broadcast_sync()` to push events from sync routes.
The SSE endpoint imports `subscribe()` / `unsubscribe()` to manage per-client queues.

Event shape (JSON-serialisable dict):
  { "type": "<event_type>", "data": { ... } }

Supported event types:
  order_updated          — order created / item added / transferred / merged
  order_paid             — items paid on an order
  order_closed           — order closed or cancelled
  table_list_changed     — table added/removed
  table_flags_changed    — flags set/cleared on a table
  message_sent           — new staff message (targeted or broadcast)
  shift_changed          — shift started / ended by manager
  business_day_changed   — business day opened / closed
"""

import asyncio
import json
from typing import Dict, Set

# Captured once at startup by init_loop() called from lifespan.
# Sync route threads use this to schedule coroutines safely.
_main_loop: asyncio.AbstractEventLoop | None = None

# waiter_id → set of asyncio.Queue (one per SSE connection for that user)
_queues: Dict[int, Set[asyncio.Queue]] = {}


def init_loop(loop: asyncio.AbstractEventLoop) -> None:
    """Call once from the FastAPI lifespan (async context) to capture the event loop."""
    global _main_loop
    _main_loop = loop


async def subscribe(user_id: int) -> asyncio.Queue:
    q: asyncio.Queue = asyncio.Queue(maxsize=256)
    if user_id not in _queues:
        _queues[user_id] = set()
    _queues[user_id].add(q)
    return q


async def unsubscribe(user_id: int, q: asyncio.Queue) -> None:
    if user_id in _queues:
        _queues[user_id].discard(q)
        if not _queues[user_id]:
            del _queues[user_id]


def broadcast_sync(event_type: str, data: dict, *, user_ids: list[int] | None = None) -> None:
    """
    Fire-and-forget broadcast from a synchronous FastAPI route (thread-pool worker).
    Uses call_soon_threadsafe so the coroutine runs on the main event loop, not the thread.
    """
    if _main_loop is None:
        return
    _main_loop.call_soon_threadsafe(
        _main_loop.create_task,
        broadcast(event_type, data, user_ids=user_ids),
    )


async def broadcast(event_type: str, data: dict, *, user_ids: list[int] | None = None) -> None:
    """
    Push an event to connected clients.
    user_ids=None  → broadcast to ALL connected users
    user_ids=[...]  → send only to those specific user IDs
    """
    payload = json.dumps({"type": event_type, "data": data})
    targets = (
        {uid: qs for uid, qs in _queues.items() if uid in user_ids}
        if user_ids is not None
        else dict(_queues)
    )
    for qs in targets.values():
        for q in list(qs):
            try:
                q.put_nowait(payload)
            except asyncio.QueueFull:
                pass  # slow client — drop rather than block
