package infra

import "sync/atomic"

// WSHub gestiona el ciclo de vida de conexiones WebSocket.
// Mantiene un mapa de clientes activos y canales para registro,
// desregistro y broadcast. Se ejecuta como goroutine via su Run().
type WSHub struct {
	Clients    map[*WSClient]bool
	Broadcast  chan []byte
	Register   chan *WSClient
	Unregister chan *WSClient
	done       chan struct{}
	count      atomic.Int64
}

// ClientCount retorna el numero de clientes registrados de forma thread-safe.
// Util para metricas, dashboards y tests.
func (h *WSHub) ClientCount() int {
	return int(h.count.Load())
}

// NewWSHub crea un WSHub vacio con canales inicializados.
// Ejecutar Run() en una goroutine para activar el loop de eventos.
func NewWSHub() *WSHub {
	return &WSHub{
		Clients:    make(map[*WSClient]bool),
		Broadcast:  make(chan []byte, 256),
		Register:   make(chan *WSClient),
		Unregister: make(chan *WSClient),
		done:       make(chan struct{}),
	}
}

// Run ejecuta el loop principal del hub. Atiende registros, desregistros
// y broadcasts en un select. Bloqueante: lanzar como goroutine.
// Para parar, llamar a Stop().
func (h *WSHub) Run() {
	for {
		select {
		case <-h.done:
			// Cerrar todos los Send y vaciar el mapa
			for client := range h.Clients {
				delete(h.Clients, client)
				close(client.Send)
			}
			h.count.Store(0)
			return
		case client := <-h.Register:
			h.Clients[client] = true
			h.count.Store(int64(len(h.Clients)))
		case client := <-h.Unregister:
			if _, ok := h.Clients[client]; ok {
				delete(h.Clients, client)
				close(client.Send)
				h.count.Store(int64(len(h.Clients)))
			}
		case msg := <-h.Broadcast:
			for client := range h.Clients {
				select {
				case client.Send <- msg:
				default:
					// Cliente lento: desconectar
					delete(h.Clients, client)
					close(client.Send)
				}
			}
			h.count.Store(int64(len(h.Clients)))
		}
	}
}

// Stop cierra el loop de Run() y limpia todos los clientes.
// Idempotente — llamar mas de una vez no panica.
func (h *WSHub) Stop() {
	select {
	case <-h.done:
		// Ya cerrado
	default:
		close(h.done)
	}
}
