import type { SteamCollection, SteamCollectionItem, Submission, PuzzleResponse, SubmissionFile, UserInfo, TournamentSubmissions, TournamentPuzzleResults } from '../types' // API Configuration const API_BASE_URL = '/api' // API Response Types interface ApiResponse { data?: T error?: string status: number } interface PaginatedResponse { items: T[] count: number } interface SubmissionStats { total_submissions: number total_responses: number needs_validation: number validated_submissions: number validation_rate: number } // API Service Class export class ApiService { private async request( endpoint: string, options: RequestInit = {} ): Promise> { try { const response = await fetch(`${API_BASE_URL}${endpoint}`, { headers: { 'Content-Type': 'application/json', ...options.headers, }, ...options, }) const data = await response.json() if (!response.ok) { return { error: data.detail || `HTTP ${response.status}`, status: response.status } } return { data, status: response.status } } catch (error) { return { error: error instanceof Error ? error.message : 'Network error', status: 0 } } } private async uploadRequest( endpoint: string, formData: FormData ): Promise> { try { const response = await fetch(`${API_BASE_URL}${endpoint}`, { method: 'POST', body: formData, }) const data = await response.json() if (!response.ok) { return { error: data.detail || `HTTP ${response.status}`, status: response.status } } return { data, status: response.status } } catch (error) { return { error: error instanceof Error ? error.message : 'Network error', status: 0 } } } // Puzzle endpoints async getPuzzles(): Promise> { return this.request('/submissions/puzzles') } async getCollection(): Promise> { return this.request('/submissions/collection') } async getTopSubmissions(limit = 5): Promise> { return this.request(`/results/top-submissions?limit=${limit}`) } async getPuzzleResults(limit = 5): Promise> { return this.request(`/results/puzzle-results?limit=${limit}`) } // Submission endpoints async getSubmissions(limit = 20, offset = 0): Promise>> { return this.request>( `/submissions/submissions?limit=${limit}&offset=${offset}` ) } async getSubmission(id: string): Promise> { return this.request(`/submissions/submissions/${id}`) } async createSubmission( submissionData: { notes?: string manual_validation_requested?: boolean responses: Array<{ puzzle_id: number puzzle_name: string cost?: number cycles?: number area?: number needs_manual_validation?: boolean ocr_confidence_cost?: number ocr_confidence_cycles?: number ocr_confidence_area?: number }> }, files: File[] ): Promise> { const formData = new FormData() // Add JSON data formData.append('data', JSON.stringify(submissionData)) // Add files files.forEach((file) => { formData.append('files', file) }) return this.uploadRequest('/submissions/submissions', formData) } // Admin endpoints (require staff permissions) async validateResponse( responseId: number, validationData: { validated_cost?: number validated_cycles?: number validated_area?: number } ): Promise> { return this.request(`/submissions/responses/${responseId}/validate`, { method: 'PUT', body: JSON.stringify(validationData), }) } async autoValidateResponses(responseId: number): Promise> { return this.request(`/submissions/responses/${responseId}/validate/auto`, { method: 'PUT', }) } async getResponsesNeedingValidation(): Promise> { return this.request('/submissions/responses/needs-validation') } async validateSubmission(submissionId: string): Promise> { return this.request(`/submissions/submissions/${submissionId}/validate`, { method: 'POST', }) } async deleteSubmission(submissionId: string): Promise> { return this.request<{ detail: string }>(`/submissions/submissions/${submissionId}`, { method: 'DELETE', }) } // Statistics endpoint async getStats(): Promise> { return this.request('/submissions/stats') } // Health check async healthCheck(): Promise> { return this.request<{ status: string; service: string }>('/health') } // User info async getUserInfo(): Promise> { return this.request('/user') } } // Singleton instance export const apiService = new ApiService() // Helper functions for common operations export const puzzleHelpers = { async loadPuzzles(): Promise { const response = await apiService.getPuzzles() if (response.error) { console.error('Failed to load puzzles:', response.error) return [] } return response.data || [] }, findPuzzleByName(puzzles: SteamCollectionItem[], name: string): SteamCollectionItem | null { if (!name) return null // Try exact match first let match = puzzles.find(p => p.title.toLowerCase() === name.toLowerCase() ) if (!match) { // Try partial match match = puzzles.find(p => p.title.toLowerCase().includes(name.toLowerCase()) || name.toLowerCase().includes(p.title.toLowerCase()) ) } return match || null } } export const submissionHelpers = { async createFromFiles( files: SubmissionFile[], puzzles: SteamCollectionItem[], notes?: string, manualValidationRequested?: boolean ): Promise> { const responses = files.map(item => { const puzzle = puzzleHelpers.findPuzzleByName(puzzles, item.ocrData?.puzzle || '') if (!puzzle) { return } return { puzzle_id: puzzle.id, puzzle_name: item.ocrData?.puzzle || '', cost: item.ocrData?.cost, cycles: item.ocrData?.cycles, area: item.ocrData?.area, needs_manual_validation: (item.ocrData?.confidence.overall ?? 0) <= 0.8, ocr_confidence_cost: item.ocrData?.confidence?.cost || 0.0, ocr_confidence_cycles: item.ocrData?.confidence?.cycles || 0.0, ocr_confidence_area: item.ocrData?.confidence?.area || 0.0 } }).filter(item => item !== undefined) // Extract actual File objects for upload const fileObjects = files.map(f => f.file) return apiService.createSubmission({ notes, manual_validation_requested: manualValidationRequested, responses }, fileObjects) }, async loadSubmissions(limit = 20, offset = 0): Promise { const response = await apiService.getSubmissions(limit, offset) if (response.error) { console.error('Failed to load submissions:', response.error) return [] } return response.data?.items || [] } } // Error handling utilities export const errorHelpers = { getErrorMessage(error: unknown): string { if (typeof error === 'string') return error if (error instanceof Error) return error.message if (typeof error === 'object' && error !== null && 'detail' in error) { return String((error as any).detail) } return 'An unknown error occurred' }, isNetworkError(error: unknown): boolean { return typeof error === 'string' && error.includes('Network') }, isValidationError(status: number): boolean { return status === 400 }, isAuthError(status: number): boolean { return status === 401 || status === 403 } }