"""Serveur asyncio : sert le HUD (HTTP statique) et diffuse les états (websocket).

Le moteur vocal tourne dans un thread (audio bloquant) et pousse ses événements via
`emit()`, relayés sur la boucle asyncio puis diffusés à tous les clients HUD connectés.
"""

from __future__ import annotations

import asyncio
import json
import threading
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any

# Le HUD est embarqué DANS le package (stt/hud) pour être livré par pipx.
HUD_DIR = Path(__file__).resolve().parent.parent / "hud"


def _start_http(host: str, port: int) -> ThreadingHTTPServer:
    handler = partial(SimpleHTTPRequestHandler, directory=str(HUD_DIR))
    httpd = ThreadingHTTPServer((host, port), handler)
    threading.Thread(target=httpd.serve_forever, daemon=True).start()
    return httpd


async def serve(cfg: dict[str, Any], make_engine) -> None:
    """make_engine(emit) -> VoiceEngine. emit est thread-safe (depuis le thread voix)."""
    import websockets

    ui = cfg["ui"]
    loop = asyncio.get_running_loop()
    clients: set = set()
    last_state = {"type": "state", "state": "idle"}

    def broadcast(event: dict) -> None:
        nonlocal last_state
        if event.get("type") == "state":
            last_state = event
        data = json.dumps(event, ensure_ascii=False)
        for ws in list(clients):
            asyncio.create_task(_safe_send(ws, data))

    async def _safe_send(ws, data: str) -> None:
        try:
            await ws.send(data)
        except Exception:
            clients.discard(ws)

    # emit appelé depuis le thread voix → on repasse sur la boucle asyncio
    def emit(event: dict) -> None:
        loop.call_soon_threadsafe(broadcast, event)

    async def handler(ws):
        clients.add(ws)
        try:
            await ws.send(json.dumps(last_state, ensure_ascii=False))
            async for _ in ws:  # le HUD n'envoie rien pour l'instant (phase 5 : réglages)
                pass
        finally:
            clients.discard(ws)

    _start_http(ui["ws_host"], ui["http_port"])

    engine = make_engine(emit)
    # chargement Whisper + boucle audio dans un thread dédié
    threading.Thread(target=_run_engine, args=(engine, emit), daemon=True).start()

    async with websockets.serve(handler, ui["ws_host"], ui["ws_port"]):
        url = f"http://{ui['ws_host']}:{ui['http_port']}/"
        print(f"  HUD : {url}")
        if ui.get("kiosk"):
            _open_browser(url, kiosk=True)
        await asyncio.Future()  # tourne indéfiniment


def _run_engine(engine, emit) -> None:
    try:
        engine.load()
        engine.run()
    except Exception as e:  # noqa: BLE001
        emit({"type": "error", "text": f"moteur vocal : {e}"})


def _open_browser(url: str, kiosk: bool) -> None:
    import shutil
    import subprocess
    import webbrowser

    for browser in ("chromium", "google-chrome", "chromium-browser"):
        if shutil.which(browser):
            flag = "--kiosk" if kiosk else "--new-window"
            subprocess.Popen([browser, flag, url])
            return
    webbrowser.open(url)
