package lobby import ( "context" "fmt" "net/http" "strconv" "time" "github.com/ryanhamamura/games/connect4" "github.com/ryanhamamura/games/db/repository" lobbycomponents "github.com/ryanhamamura/games/features/lobby/components" "github.com/ryanhamamura/games/features/lobby/pages" appsessions "github.com/ryanhamamura/games/sessions" "github.com/ryanhamamura/games/snake" "github.com/alexedwards/scs/v2" "github.com/go-chi/chi/v5" "github.com/starfederation/datastar-go/datastar" ) // HandleLobbyPage renders the main lobby page with active games for logged-in users. func HandleLobbyPage(queries *repository.Queries, sessions *scs.SessionManager, snakeStore *snake.SnakeStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID := sessions.GetString(r.Context(), appsessions.KeyUserID) username := sessions.GetString(r.Context(), "username") isLoggedIn := userID != "" var userGames []lobbycomponents.GameListItem if isLoggedIn { ctx := context.Background() games, err := queries.GetUserActiveGames(ctx, &userID) if err == nil { for _, g := range games { isMyTurn := g.Status == 1 && g.CurrentTurn == g.MyColor opponentName := "" if g.OpponentNickname != nil { opponentName = *g.OpponentNickname } var lastPlayed time.Time if g.UpdatedAt != nil { lastPlayed = *g.UpdatedAt } userGames = append(userGames, lobbycomponents.GameListItem{ ID: g.ID, Status: int(g.Status), OpponentName: opponentName, IsMyTurn: isMyTurn, LastPlayed: lastPlayed, }) } } } var activeSnakeGames []pages.SnakeGameListItem for _, g := range snakeStore.ActiveGames() { statusLabel := "Waiting" if g.Status == snake.StatusCountdown { statusLabel = "Starting soon" } activeSnakeGames = append(activeSnakeGames, pages.SnakeGameListItem{ ID: g.ID, Width: g.State.Width, Height: g.State.Height, PlayerCount: g.PlayerCount(), StatusLabel: statusLabel, }) } data := pages.LobbyData{ IsLoggedIn: isLoggedIn, Username: username, UserGames: userGames, ActiveSnakeGames: activeSnakeGames, } if err := pages.LobbyPage(data).Render(r.Context(), w); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } } } // HandleCreateGame reads the nickname signal, creates a connect4 game, and redirects via SSE. func HandleCreateGame(store *connect4.Store, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { type Signals struct { Nickname string `json:"nickname"` } signals := &Signals{} if err := datastar.ReadSignals(r, signals); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if signals.Nickname == "" { return } sessions.Put(r.Context(), appsessions.KeyNickname, signals.Nickname) gi := store.Create() sse := datastar.NewSSE(w, r) sse.ExecuteScript(fmt.Sprintf("window.location.href='/games/%s'", gi.ID())) //nolint:errcheck } } // HandleDeleteGame deletes a connect4 game and redirects to the lobby. func HandleDeleteGame(store *connect4.Store, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { gameID := chi.URLParam(r, "id") if gameID == "" { http.Error(w, "missing game id", http.StatusBadRequest) return } store.Delete(gameID) //nolint:errcheck sse := datastar.NewSSE(w, r) sse.ExecuteScript("window.location.href='/'") //nolint:errcheck } } // HandleCreateSnakeGame reads nickname, grid preset, speed, and mode from the request, // creates a snake game, and redirects via SSE. func HandleCreateSnakeGame(snakeStore *snake.SnakeStore, sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { type Signals struct { Nickname string `json:"nickname"` SelectedSpeed int `json:"selectedSpeed"` } signals := &Signals{} if err := datastar.ReadSignals(r, signals); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if signals.Nickname == "" { return } sessions.Put(r.Context(), appsessions.KeyNickname, signals.Nickname) mode := snake.ModeMultiplayer if r.URL.Query().Get("mode") == "solo" { mode = snake.ModeSinglePlayer } presetIdx, _ := strconv.Atoi(r.URL.Query().Get("preset")) if presetIdx < 0 || presetIdx >= len(snake.GridPresets) { presetIdx = 0 } preset := snake.GridPresets[presetIdx] speed := snake.DefaultSpeed if signals.SelectedSpeed >= 0 && signals.SelectedSpeed < len(snake.SpeedPresets) { speed = snake.SpeedPresets[signals.SelectedSpeed].Speed } si := snakeStore.Create(preset.Width, preset.Height, mode, speed) sse := datastar.NewSSE(w, r) sse.ExecuteScript(fmt.Sprintf("window.location.href='/snake/%s'", si.ID())) //nolint:errcheck } } // HandleLogout clears the session and redirects to the lobby. func HandleLogout(sessions *scs.SessionManager) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if err := sessions.Destroy(r.Context()); err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } http.Redirect(w, r, "/", http.StatusSeeOther) } }