import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../config/api_config.dart'; import '../models/guess_score_reset_result.dart'; import '../models/guess_score_summary.dart'; import '../models/incoming_question.dart'; import '../models/question_defer_result.dart'; import '../models/question_submit_result.dart'; import 'auth_service.dart'; /// HTTP client for question queue, answers, and deferrals. class QuestionsApiService { QuestionsApiService({http.Client? client}) : _client = client ?? http.Client(); final http.Client _client; /// Ensures login question state is initialized for the signed-in user. /// /// Returns the first unanswered question when present and always attempts to /// return persisted [GuessScoreSummary] for this Firebase user when the API /// includes it in the bootstrap payload. Future<({IncomingQuestion? question, GuessScoreSummary? score})> bootstrapOnLogin() async { final String? token = await AuthService.instance.getIdToken(); if (token == null) { return (question: null, score: null); } final http.Response response = await _client.post( Uri.parse('$apiBaseUrl/v1/me/questions/bootstrap'), headers: _authHeaders(token), ); if (response.statusCode != 200) { debugPrint( 'bootstrapOnLogin failed: ${response.statusCode} ${response.body}', ); return (question: null, score: null); } final Map body = jsonDecode(response.body) as Map; final Map? questionJson = body['question'] as Map?; final Map? scoreJson = body['score'] as Map?; return ( question: questionJson == null ? null : IncomingQuestion.fromJson(questionJson), score: scoreJson == null ? null : GuessScoreSummary.fromJson(scoreJson), ); } Future> fetchUnanswered() async { final String? token = await AuthService.instance.getIdToken(); if (token == null) { return []; } final http.Response response = await _client.get( Uri.parse('$apiBaseUrl/v1/me/questions'), headers: _authHeaders(token), ); if (response.statusCode != 200) { debugPrint( 'fetchUnanswered failed: ${response.statusCode} ${response.body}', ); return []; } final Map body = jsonDecode(response.body) as Map; final List raw = body['questions'] as List? ?? []; return raw .map( (dynamic item) => IncomingQuestion.fromJson(item as Map), ) .toList(); } Future submitAnswer({ required String questionId, num answer = 0, }) async { final String? token = await AuthService.instance.getIdToken(); if (token == null) { return null; } final http.Response response = await _client.post( Uri.parse('$apiBaseUrl/v1/me/questions/$questionId/answer'), headers: _authHeaders(token), body: jsonEncode({'answer': answer}), ); if (response.statusCode != 200) { debugPrint( 'submitAnswer failed: ${response.statusCode} ${response.body}', ); return null; } final Map body = jsonDecode(response.body) as Map; final Map? scoreJson = body['score'] as Map?; final Map? nextQuestionJson = body['nextQuestion'] as Map?; return QuestionSubmitResult( unansweredCount: (body['unansweredCount'] as num?)?.toInt() ?? 0, score: scoreJson == null ? GuessScoreSummary.empty : GuessScoreSummary.fromJson(scoreJson), nextQuestion: nextQuestionJson == null ? null : IncomingQuestion.fromJson(nextQuestionJson), ); } Future deferQuestion({required String questionId}) async { final String? token = await AuthService.instance.getIdToken(); if (token == null) { return null; } final http.Response response = await _client.post( Uri.parse('$apiBaseUrl/v1/me/questions/$questionId/defer'), headers: _authHeaders(token), ); if (response.statusCode != 200) { debugPrint( 'deferQuestion failed: ${response.statusCode} ${response.body}', ); return null; } final Map body = jsonDecode(response.body) as Map; final Map? nextQuestionJson = body['nextQuestion'] as Map?; return QuestionDeferResult( unansweredCount: (body['unansweredCount'] as num?)?.toInt() ?? 0, nextQuestion: nextQuestionJson == null ? null : IncomingQuestion.fromJson(nextQuestionJson), ); } Future fetchGuessScoreSummary() async { final String? token = await AuthService.instance.getIdToken(); if (token == null) { return null; } final http.Response response = await _client.get( Uri.parse('$apiBaseUrl/v1/me/questions/score'), headers: _authHeaders(token), ); if (response.statusCode != 200) { debugPrint( 'fetchGuessScoreSummary failed: ${response.statusCode} ${response.body}', ); return null; } final Map body = jsonDecode(response.body) as Map; final Map? scoreJson = body['score'] as Map?; if (scoreJson == null) { return GuessScoreSummary.empty; } return GuessScoreSummary.fromJson(scoreJson); } Future resetGuessScoreSummary() async { final String? token = await AuthService.instance.getIdToken(); if (token == null) { return null; } final http.Response response = await _client.post( Uri.parse('$apiBaseUrl/v1/me/questions/score/reset'), headers: _authHeaders(token), ); if (response.statusCode != 200) { debugPrint( 'resetGuessScoreSummary failed: ${response.statusCode} ${response.body}', ); return null; } final Map body = jsonDecode(response.body) as Map; final Map? scoreJson = body['score'] as Map?; final Map? questionJson = body['question'] as Map?; return GuessScoreResetResult( score: scoreJson == null ? GuessScoreSummary.empty : GuessScoreSummary.fromJson(scoreJson), unansweredCount: (body['unansweredCount'] as num?)?.toInt() ?? 0, question: questionJson == null ? null : IncomingQuestion.fromJson(questionJson), ); } Map _authHeaders(String token) { return { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'Accept': 'application/json', }; } }