import { mapCalculateScoresResponse, mapFinishGameResponse, mapHealthResponse, mapJoinSessionResponse, mapMixAnswersResponse, mapScoreboardResponse, mapSessionDetailResponse, mapShowQuestionResponse, mapStartNextRoundResponse, mapStartRoundResponse, mapSubmitGuessResponse, mapSubmitLieResponse } from './mappers'; import type { ApiResult, CalculateScoresResponse, FinishGameResponse, HealthResponse, JoinSessionRequest, JoinSessionResponse, MixAnswersResponse, ScoreboardResponse, SessionDetailResponse, ShowQuestionResponse, StartNextRoundResponse, StartRoundRequest, StartRoundResponse, SubmitGuessRequest, SubmitGuessResponse, SubmitLieRequest, SubmitLieResponse } from './types'; export interface ApiClient { health(): Promise>; getSession(code: string): Promise>; joinSession(payload: JoinSessionRequest): Promise>; startRound(code: string, payload: StartRoundRequest): Promise>; showQuestion(code: string): Promise>; mixAnswers(code: string, roundQuestionId: number): Promise>; calculateScores(code: string, roundQuestionId: number): Promise>; getScoreboard(code: string): Promise>; startNextRound(code: string): Promise>; finishGame(code: string): Promise>; submitLie(code: string, roundQuestionId: number, payload: SubmitLieRequest): Promise>; submitGuess(code: string, roundQuestionId: number, payload: SubmitGuessRequest): Promise>; } export function createApiClient(baseUrl = '', fetchImpl: typeof fetch = fetch): ApiClient { async function request( path: string, method: 'GET' | 'POST', mapper: (payload: unknown) => T, payload?: unknown ): Promise> { let response: Response; try { response = await fetchImpl(`${baseUrl}${path}`, { method, headers: { Accept: 'application/json', ...(payload === undefined ? {} : { 'Content-Type': 'application/json' }) }, ...(payload === undefined ? {} : { body: JSON.stringify(payload) }) }); } catch { return { ok: false, status: 0, error: { kind: 'network', status: 0, message: 'Network error while contacting API' } }; } let responsePayload: unknown; try { responsePayload = await response.json(); } catch { return { ok: false, status: response.status, error: { kind: 'parse', status: response.status, message: 'Invalid JSON response from API' } }; } if (!response.ok) { return { ok: false, status: response.status, error: { kind: 'http', status: response.status, message: `HTTP ${response.status}`, payload: responsePayload } }; } try { return { ok: true, status: response.status, data: mapper(responsePayload) }; } catch (error) { return { ok: false, status: response.status, error: { kind: 'parse', status: response.status, message: error instanceof Error ? error.message : 'Invalid API response contract', payload: responsePayload } }; } } const normalizeCode = (value: string): string => value.trim().toUpperCase(); return { health: () => request('/healthz', 'GET', mapHealthResponse), getSession: (code: string) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}`, 'GET', mapSessionDetailResponse ), joinSession: (payload: JoinSessionRequest) => request( '/lobby/sessions/join', 'POST', mapJoinSessionResponse, { code: normalizeCode(payload.code), nickname: payload.nickname.trim() } ), startRound: (code: string, payload: StartRoundRequest) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/rounds/start`, 'POST', mapStartRoundResponse, payload ), showQuestion: (code: string) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/show`, 'POST', mapShowQuestionResponse, {} ), mixAnswers: (code: string, roundQuestionId: number) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/answers/mix`, 'POST', mapMixAnswersResponse, {} ), calculateScores: (code: string, roundQuestionId: number) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/scores/calculate`, 'POST', mapCalculateScoresResponse, {} ), getScoreboard: (code: string) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/scoreboard`, 'GET', mapScoreboardResponse ), startNextRound: (code: string) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/rounds/next`, 'POST', mapStartNextRoundResponse, {} ), finishGame: (code: string) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/finish`, 'POST', mapFinishGameResponse, {} ), submitLie: (code: string, roundQuestionId: number, payload: SubmitLieRequest) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/lies/submit`, 'POST', mapSubmitLieResponse, payload ), submitGuess: (code: string, roundQuestionId: number, payload: SubmitGuessRequest) => request( `/lobby/sessions/${encodeURIComponent(normalizeCode(code))}/questions/${roundQuestionId}/guesses/submit`, 'POST', mapSubmitGuessResponse, payload ) }; }