import { useEffect, useCallback } from 'react' import { useGameStore, saveSession, clearSession } from '../stores/gameStore' import { soundPlayer } from './useSound' import { useThemeStore } from '../stores/themeStore' import { useSoundStore } from '../stores/soundStore' import { socketService } from '../services/socket' import type { GameRoom, ChatMessage, AnswerResult, Achievement } from '../types' import type { Reaction } from '../stores/gameStore' // Team message type export interface TeamMessage { player_name: string team: 'A' | 'B' message: string timestamp: string } export function useSocket() { const { setRoom, addMessage, setShowStealPrompt, setCurrentQuestion, setGameResult, addReaction, addTeamMessage } = useGameStore() // Initialize sound player with current theme const currentTheme = useThemeStore.getState().currentTheme soundPlayer.loadTheme(currentTheme) useEffect(() => { // Get singleton socket connection const socket = socketService.connect() // Only set up listeners once globally if (socketService.isInitialized) { return // No cleanup - socket persists } socketService.setInitialized() // Error handler socket.on('error', (data: { message: string }) => { console.error('Socket error:', data.message) }) // Room events socket.on('room_created', (data: { room: GameRoom }) => { setRoom(data.room) }) socket.on('player_joined', (data: { room: GameRoom }) => { setRoom(data.room) // Play sound when a player joins const volume = useSoundStore.getState().volume soundPlayer.play('player_join', volume) }) socket.on('player_left', (data: { room: GameRoom }) => { setRoom(data.room) }) socket.on('team_changed', (data: { room: GameRoom }) => { setRoom(data.room) }) // Reconnection events socket.on('rejoin_success', (data: { room: GameRoom; player_name: string; team: 'A' | 'B' }) => { console.log('Rejoin successful:', data.player_name) setRoom(data.room) useGameStore.getState().setPlayerName(data.player_name) // Update saved session with possibly new team saveSession(data.room.code, data.player_name, data.team) }) socket.on('rejoin_failed', (data: { message: string }) => { console.log('Rejoin failed:', data.message) clearSession() }) socket.on('player_reconnected', (data: { player_name: string; team: string; room: GameRoom }) => { console.log('Player reconnected:', data.player_name) setRoom(data.room) }) // Game events socket.on('game_started', (data: { room: GameRoom }) => { setRoom(data.room) // Play game start sound const volume = useSoundStore.getState().volume soundPlayer.play('game_start', volume) }) socket.on('question_selected', (data: { room: GameRoom; question_id: number }) => { setRoom(data.room) // Find the question in the board and set it as current const questionId = data.question_id for (const categoryQuestions of Object.values(data.room.board || {})) { const question = (categoryQuestions as Array<{ id: number }>).find(q => q.id === questionId) if (question) { setCurrentQuestion(question as unknown as import('../types').Question) break } } }) socket.on('answer_result', (data: AnswerResult) => { setRoom(data.room) // Play appropriate sound based on answer result const volume = useSoundStore.getState().volume if (data.valid) { soundPlayer.play('correct', volume) // Clear current question after correct answer setCurrentQuestion(null) } else { soundPlayer.play('incorrect', volume) } if (!data.valid && !data.was_steal && data.room.can_steal) { setShowStealPrompt(true) } else if (data.was_steal) { // Clear question after steal attempt (success or fail) setCurrentQuestion(null) } }) socket.on('steal_attempted', (data: { room: GameRoom; success?: boolean }) => { setRoom(data.room) setShowStealPrompt(false) // Play steal sound when a steal is attempted const volume = useSoundStore.getState().volume soundPlayer.play('steal', volume) }) socket.on('steal_passed', (data: { room: GameRoom }) => { setRoom(data.room) setShowStealPrompt(false) setCurrentQuestion(null) }) socket.on('time_up', (data: { room: GameRoom; was_steal: boolean }) => { setRoom(data.room) if (!data.was_steal && data.room.can_steal) { setShowStealPrompt(true) } else { setShowStealPrompt(false) setCurrentQuestion(null) } }) // Chat events socket.on('chat_message', (data: ChatMessage) => { addMessage(data) }) socket.on('emoji_reaction', (data: { player_name: string; team: string; emoji: string }) => { // Legacy handler - redirect to new reaction system addReaction({ player_name: data.player_name, team: data.team as 'A' | 'B', emoji: data.emoji, timestamp: new Date().toISOString(), }) }) socket.on('receive_reaction', (data: Omit) => { // Add reaction to the store for display in overlay addReaction(data) }) // Team chat events socket.on('receive_team_message', (data: TeamMessage) => { addTeamMessage(data) }) socket.on('game_finished', (data: { room: GameRoom winner: 'A' | 'B' | null final_scores: { A: number; B: number } replay_code: string | null achievements_unlocked: Array<{ player_name: string team: 'A' | 'B' achievement: unknown }> }) => { setRoom(data.room) // Determine if current player is on the winning team const currentPlayerName = useGameStore.getState().playerName const myTeam = data.room.teams.A.find(p => p.name === currentPlayerName) ? 'A' : 'B' const volume = useSoundStore.getState().volume if (data.winner === myTeam) { soundPlayer.play('victory', volume) } else if (data.winner !== null) { soundPlayer.play('defeat', volume) } setGameResult({ winner: data.winner, finalScores: data.final_scores, replayCode: data.replay_code, achievementsUnlocked: data.achievements_unlocked.map(a => ({ player_name: a.player_name, team: a.team, achievement: a.achievement as Achievement })) }) }) // No cleanup - socket connection persists across components }, []) // Socket methods - use singleton service const createRoom = useCallback((playerName: string) => { socketService.emit('create_room', { player_name: playerName }) }, []) const joinRoom = useCallback((roomCode: string, playerName: string, team: 'A' | 'B') => { socketService.emit('join_room', { room_code: roomCode, player_name: playerName, team, }) }, []) const changeTeam = useCallback((team: 'A' | 'B') => { socketService.emit('change_team', { team }) }, []) const startGame = useCallback((board: Record) => { socketService.emit('start_game', { board }) }, []) const selectQuestion = useCallback((questionId: number, categoryId: number) => { socketService.emit('select_question', { question_id: questionId, category_id: categoryId, }) }, []) const submitAnswer = useCallback( (answer: string, question: Record, isSteal: boolean = false) => { socketService.emit('submit_answer', { answer, question, is_steal: isSteal, }) }, [] ) const stealDecision = useCallback((attempt: boolean, questionId: number, answer?: string) => { socketService.emit('steal_decision', { attempt, question_id: questionId, answer, }) }, []) const sendChatMessage = useCallback((message: string) => { socketService.emit('chat_message', { message }) }, []) const sendEmojiReaction = useCallback((emoji: string) => { socketService.emit('emoji_reaction', { emoji }) }, []) const sendReaction = useCallback((emoji: string, roomCode?: string, playerName?: string) => { socketService.emit('send_reaction', { emoji, room_code: roomCode, player_name: playerName, }) }, []) const sendTeamMessage = useCallback( (message: string, roomCode: string, team: 'A' | 'B', playerName: string) => { socketService.emit('team_message', { room_code: roomCode, team, player_name: playerName, message, }) }, [] ) const notifyTimerExpired = useCallback(() => { socketService.emit('timer_expired', {}) }, []) const rejoinRoom = useCallback((roomCode: string, playerName: string, team: 'A' | 'B') => { socketService.emit('rejoin_room', { room_code: roomCode, player_name: playerName, team, }) }, []) return { socket: socketService.connect(), createRoom, joinRoom, rejoinRoom, changeTeam, startGame, selectQuestion, submitAnswer, stealDecision, sendChatMessage, sendEmojiReaction, sendReaction, sendTeamMessage, notifyTimerExpired, } }