import { create } from "zustand" import type { ErrorPayload, PlayerScore, RoomCreatedPayload, RoomSnapshot, RoundStartPayload, VoteAckPayload, } from "@nerdware/shared" import { socket } from "@/lib/socket" /** Manche en cours côté client (depuis round:start). */ export interface ActiveRound { type: RoundStartPayload["type"] djId?: string startsAt: number endsAt: number payload: unknown } /** Reveal reçu (round:reveal), payloads typés par mode au moment de l'affichage. */ export interface RoundReveal { truth: unknown perPlayerResult: unknown } interface RoomState { connected: boolean /** Identité locale, pour reconnaître "moi" dans le snapshot. */ playerId: string | null roomCode: string | null snapshot: RoomSnapshot | null lastError: ErrorPayload | null // État de jeu (poussé par le moteur serveur). round: ActiveRound | null voteProgress: VoteAckPayload | null reveal: RoundReveal | null myChoiceIndex: number | null finalScores: PlayerScore[] | null /** Compteur d'explosions : incrémenté à chaque fin de manche au timer (boom à 0). */ boomKey: number /** Compteur de manches : incrémenté à chaque round:start (transition WarioWare). */ roundKey: number createRoom: (playerName: string) => Promise joinRoom: (roomCode: string, playerName: string) => Promise startGame: (questionCount: number) => Promise vote: (choiceIndex: number) => void boom: () => void clearError: () => void reset: () => void } export const useRoomStore = create((set, get) => ({ connected: socket.connected, playerId: null, roomCode: null, snapshot: null, lastError: null, round: null, voteProgress: null, reveal: null, myChoiceIndex: null, finalScores: null, boomKey: 0, roundKey: 0, createRoom: (playerName) => new Promise((resolve, reject) => { socket.emit("room:create", { playerName }, (res) => { if (res.ok) { set({ playerId: res.data.playerId, roomCode: res.data.roomCode }) resolve(res.data) } else { set({ lastError: res.error }) reject(res.error) } }) }), joinRoom: (roomCode, playerName) => new Promise((resolve, reject) => { socket.emit("room:join", { roomCode, playerName }, (res) => { if (res.ok) { set({ playerId: res.data.playerId, roomCode: res.data.roomCode }) resolve(res.data) } else { set({ lastError: res.error }) reject(res.error) } }) }), startGame: (questionCount) => new Promise((resolve, reject) => { const rounds = Array.from({ length: questionCount }, () => ({ type: "quiz" as const, })) // On pousse la séquence de manches, puis on lance. socket.emit("lobby:updateSettings", { blindtestMode: get().snapshot?.settings.blindtestMode ?? "title_artist", roundDuration: 20, rounds, }) socket.emit("game:start", (res) => { if (res.ok) { resolve() } else { set({ lastError: res.error }) reject(res.error) } }) }), vote: (choiceIndex) => { if (get().myChoiceIndex !== null) { return // vote déjà émis pour cette manche } set({ myChoiceIndex: choiceIndex }) socket.emit("round:vote", { answer: { choiceIndex } }) }, boom: () => set((s) => ({ boomKey: s.boomKey + 1 })), clearError: () => set({ lastError: null }), reset: () => set({ roomCode: null, snapshot: null, lastError: null, round: null, voteProgress: null, reveal: null, myChoiceIndex: null, finalScores: null, boomKey: 0, roundKey: 0, }), })) // Listeners socket → on pousse l'état serveur dans le store (source de vérité). socket.on("connect", () => useRoomStore.setState({ connected: true })) socket.on("disconnect", () => useRoomStore.setState({ connected: false })) socket.on("room:state", (snapshot) => useRoomStore.setState({ snapshot })) socket.on("error", (error) => useRoomStore.setState({ lastError: error })) socket.on("round:start", (payload) => useRoomStore.setState((s) => ({ round: { type: payload.type, djId: payload.djId, startsAt: payload.startsAt, endsAt: payload.endsAt, payload: payload.payload, }, voteProgress: null, reveal: null, myChoiceIndex: null, roundKey: s.roundKey + 1, })) ) socket.on("round:voteAck", (voteProgress) => useRoomStore.setState({ voteProgress }) ) socket.on("round:reveal", (reveal) => useRoomStore.setState({ reveal })) socket.on("game:end", ({ finalScores }) => useRoomStore.setState({ finalScores }) )