import { create } from 'zustand' import type { BetSelection, Chip, GameBootstrapSnapshot, GameCell, HistoryEntry, RoundPhase, RoundSnapshot, TrendEntry, } from '@/features/game/shared' import { buildGameCellViewModels, createEmptyGameBootstrapSnapshot, DEFAULT_ACTIVE_CHIP_ID, getChipById, getRecentWinningCellIds, getSelectionTotal, groupSelectionsByCell, } from '@/features/game/shared' type GameRoundSlice = Pick< GameBootstrapSnapshot, | 'cells' | 'chips' | 'history' | 'maxSelectionCount' | 'round' | 'selections' | 'trends' > function resolveRecentActiveChipId( chips: Chip[], selections: BetSelection[], fallbackChipId: string, ) { for (let index = selections.length - 1; index >= 0; index -= 1) { const chipId = selections[index]?.chipId if (chipId && getChipById(chips, chipId)) { return chipId } } return getChipById(chips, fallbackChipId) ? fallbackChipId : (chips.find((chip) => chip.isDefault)?.id ?? chips[0]?.id ?? DEFAULT_ACTIVE_CHIP_ID) } export interface GameRoundStoreState extends GameRoundSlice { activeChipId: string clearSelections: () => void hydrateRound: (snapshot: GameRoundSlice) => void placeBet: (cellId: number) => void recentSuccessfulSelections: BetSelection[] removeSelectionsForCell: (cellId: number) => void restoreRecentSuccessfulSelections: () => boolean setRecentSuccessfulSelections: (selections: BetSelection[]) => void selectChip: (chipId: string) => void setPhase: (phase: RoundPhase) => void syncRound: (round: Partial) => void upsertSelections: (selections: BetSelection[]) => void } function createInitialRoundState(): GameRoundSlice & { activeChipId: string recentSuccessfulSelections: BetSelection[] } { const snapshot = createEmptyGameBootstrapSnapshot() return { activeChipId: DEFAULT_ACTIVE_CHIP_ID, cells: snapshot.cells, chips: snapshot.chips, history: snapshot.history, maxSelectionCount: snapshot.maxSelectionCount, recentSuccessfulSelections: [], round: snapshot.round, selections: snapshot.selections, trends: snapshot.trends, } } export const useGameRoundStore = create()((set) => ({ ...createInitialRoundState(), clearSelections: () => { set({ selections: [] }) }, hydrateRound: (snapshot) => { set((state) => ({ activeChipId: getChipById(snapshot.chips, state.activeChipId) ? state.activeChipId : (snapshot.chips.find((chip) => chip.isDefault)?.id ?? snapshot.chips[0]?.id ?? DEFAULT_ACTIVE_CHIP_ID), cells: snapshot.cells, chips: snapshot.chips, history: snapshot.history, maxSelectionCount: snapshot.maxSelectionCount, round: snapshot.round, selections: snapshot.selections, trends: snapshot.trends, })) }, placeBet: (cellId) => { set((state) => { const activeChip = getChipById(state.chips, state.activeChipId) ?? state.chips.find((chip) => chip.isDefault) ?? state.chips[0] const hasExistingSelection = state.selections.some( (selection) => selection.cellId === cellId, ) const selectedCellCount = new Set( state.selections.map((selection) => selection.cellId), ).size if ( !activeChip || state.round.phase !== 'betting' || hasExistingSelection || selectedCellCount >= state.maxSelectionCount ) { return state } return { selections: [ ...state.selections, { amount: activeChip.amount, cellId, chipId: activeChip.id, id: `bet-${cellId}-${state.selections.length + 1}-${Date.now()}`, placedAt: new Date().toISOString(), source: 'local', }, ], } }) }, recentSuccessfulSelections: [], removeSelectionsForCell: (cellId) => { set((state) => ({ selections: state.selections.filter( (selection) => selection.cellId !== cellId, ), })) }, restoreRecentSuccessfulSelections: () => { const state = useGameRoundStore.getState() if ( state.round.phase !== 'betting' || state.recentSuccessfulSelections.length === 0 ) { return false } const nextSelections = state.recentSuccessfulSelections .filter((selection) => getChipById(state.chips, selection.chipId)) .slice(0, state.maxSelectionCount) .map((selection, index) => ({ ...selection, id: `bet-repeat-${selection.cellId}-${index + 1}-${Date.now()}`, placedAt: new Date().toISOString(), source: 'local' as const, })) if (nextSelections.length === 0) { return false } set({ activeChipId: resolveRecentActiveChipId( state.chips, nextSelections, state.activeChipId, ), selections: nextSelections, }) return true }, setRecentSuccessfulSelections: (selections) => { set({ recentSuccessfulSelections: selections.map((selection) => ({ ...selection, })), }) }, selectChip: (chipId) => { set((state) => { if (!getChipById(state.chips, chipId)) { return state } return { activeChipId: chipId } }) }, setPhase: (phase) => { set((state) => ({ round: { ...state.round, phase, }, })) }, syncRound: (round) => { set((state) => ({ round: { ...state.round, ...round, }, })) }, upsertSelections: (selections) => { set({ selections }) }, })) export const selectActiveChip = (state: GameRoundStoreState): Chip | null => getChipById(state.chips, state.activeChipId) ?? state.chips.find((chip) => chip.isDefault) ?? state.chips[0] ?? null export const selectBoardCells = (state: GameRoundStoreState) => buildGameCellViewModels({ cells: state.cells, round: state.round, selections: state.selections, trends: state.trends, }) export const selectCanPlaceBets = (state: GameRoundStoreState) => state.round.phase === 'betting' export const selectRecentResults = (state: GameRoundStoreState) => getRecentWinningCellIds(state.history) export const selectSelectionTotal = (state: GameRoundStoreState) => getSelectionTotal(state.selections) export const selectSelectionsByCell = (state: GameRoundStoreState) => groupSelectionsByCell(state.selections) export type GameRoundStore = typeof useGameRoundStore export type GameRoundStoreData = Pick< GameRoundStoreState, | 'cells' | 'chips' | 'history' | 'maxSelectionCount' | 'round' | 'selections' | 'trends' > export type { BetSelection, GameCell, HistoryEntry, RoundSnapshot, TrendEntry }