import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../data/models/exam_model.dart'; import '../../data/repositories/app_repositories.dart'; abstract class ExamState {} class ExamInitial extends ExamState {} class ExamLoading extends ExamState { final String message; ExamLoading({this.message = 'جاري تحضير الامتحان والأسئلة التكيفية...'}); } class ExamLoaded extends ExamState { final ExamModel exam; final int currentQuestionIndex; final Map selectedAnswers; // questionId -> optionId final int remainingSeconds; final bool isSubmitting; ExamLoaded({ required this.exam, this.currentQuestionIndex = 0, this.selectedAnswers = const {}, required this.remainingSeconds, this.isSubmitting = false, }); QuestionModel? get currentQuestion { if (exam.questions.isEmpty || currentQuestionIndex >= exam.questions.length) return null; return exam.questions[currentQuestionIndex]; } bool get isLastQuestion => currentQuestionIndex == exam.questions.length - 1; int get answeredCount => selectedAnswers.length; ExamLoaded copyWith({ ExamModel? exam, int? currentQuestionIndex, Map? selectedAnswers, int? remainingSeconds, bool? isSubmitting, }) { return ExamLoaded( exam: exam ?? this.exam, currentQuestionIndex: currentQuestionIndex ?? this.currentQuestionIndex, selectedAnswers: selectedAnswers ?? this.selectedAnswers, remainingSeconds: remainingSeconds ?? this.remainingSeconds, isSubmitting: isSubmitting ?? this.isSubmitting, ); } } class ExamCompleted extends ExamState { final ExamModel exam; final ExamSubmissionResultModel result; final int timeSpentSeconds; ExamCompleted({ required this.exam, required this.result, required this.timeSpentSeconds, }); } class ExamError extends ExamState { final String message; ExamError(this.message); } class ExamCubit extends Cubit { final ExamRepository _repository; Timer? _countdownTimer; int _initialSeconds = 0; ExamCubit({ExamRepository? repository}) : _repository = repository ?? ExamRepository(), super(ExamInitial()); @override Future close() { _countdownTimer?.cancel(); return super.close(); } Future loadExam({int examId = 1, ExamModel? initialExam}) async { emit(ExamLoading()); try { ExamModel loadedExam; if (initialExam != null && initialExam.questions.isNotEmpty) { loadedExam = initialExam; } else { loadedExam = await _repository.getExamDetails(examId); } if (loadedExam.questions.isEmpty) { emit(ExamError('لم يتم العثور على أسئلة لهذا الامتحان في السيرفر. يرجى توليد بنك الأسئلة من لوحة المناهج.')); return; } _initialSeconds = loadedExam.durationMinutes * 60; emit(ExamLoaded( exam: loadedExam, remainingSeconds: _initialSeconds, )); _startTimer(); } catch (e) { emit(ExamError('تعذر جلب الامتحان من السيرفر: ${e.toString()}')); } } void _startTimer() { _countdownTimer?.cancel(); _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { final s = state; if (s is ExamLoaded) { if (s.remainingSeconds <= 1) { timer.cancel(); submitCurrentExam(); } else { emit(s.copyWith(remainingSeconds: s.remainingSeconds - 1)); } } else { timer.cancel(); } }); } void selectOption(int questionId, int optionId) { final s = state; if (s is ExamLoaded) { final updatedAnswers = Map.from(s.selectedAnswers); updatedAnswers[questionId] = optionId; emit(s.copyWith(selectedAnswers: updatedAnswers)); } } void goToQuestion(int index) { final s = state; if (s is ExamLoaded) { if (index >= 0 && index < s.exam.questions.length) { emit(s.copyWith(currentQuestionIndex: index)); } } } void nextQuestion() { final s = state; if (s is ExamLoaded && !s.isLastQuestion) { emit(s.copyWith(currentQuestionIndex: s.currentQuestionIndex + 1)); } } void previousQuestion() { final s = state; if (s is ExamLoaded && s.currentQuestionIndex > 0) { emit(s.copyWith(currentQuestionIndex: s.currentQuestionIndex - 1)); } } Future submitCurrentExam() async { final s = state; if (s is! ExamLoaded) return; _countdownTimer?.cancel(); emit(s.copyWith(isSubmitting: true)); final timeSpent = _initialSeconds - s.remainingSeconds; final formattedAnswers = s.selectedAnswers.entries.map((e) { return { 'question_id': e.key, 'selected_option_id': e.value, }; }).toList(); try { ExamSubmissionResultModel result; try { result = await _repository.submitExam( s.exam.id, answers: formattedAnswers, timeSpentSeconds: timeSpent, ); } catch (_) { // Safe offline evaluation fallback if API token not present result = _evaluateLocally(s.exam, s.selectedAnswers); } emit(ExamCompleted( exam: s.exam, result: result, timeSpentSeconds: timeSpent, )); } catch (e) { emit(ExamError('فشل تقديم الامتحان: ${e.toString()}')); } } ExamSubmissionResultModel _evaluateLocally(ExamModel exam, Map selectedAnswers) { int earnedScore = 0; int totalScore = 0; List weakTopics = []; List detailedAnswers = []; for (var q in exam.questions) { totalScore += q.points; final selectedOptId = selectedAnswers[q.id]; QuestionOptionModel? correctOption; try { correctOption = q.options.firstWhere((opt) => opt.isCorrect); } catch (_) { correctOption = q.options.isNotEmpty ? q.options.first : null; } final isCorrect = selectedOptId != null && correctOption != null && selectedOptId == correctOption.id; final points = isCorrect ? q.points : 0; earnedScore += points; if (!isCorrect) { if (!weakTopics.contains(q.topicTag)) { weakTopics.add(q.topicTag); } } detailedAnswers.add(DetailedAnswerModel( questionId: q.id, selectedOptionId: selectedOptId ?? 0, isCorrect: isCorrect, pointsAwarded: points, explanation: q.explanationText ?? 'راجع نص القاعدة في كتاب الوزارة', aiHint: q.aiHint, )); } final pct = totalScore > 0 ? (earnedScore / totalScore) * 100 : 0.0; final passed = pct >= exam.passingPercentage; final report = passed ? 'أداء ممتاز! حققت نسبة إتقان ${pct.toStringAsFixed(1)}%. لديك استيعاب عميق للمفاهيم الأساسية.' : 'تم رصد تعثر في مفاهيم: ${weakTopics.join('، ')}. ننصح بمشاهدة مقاطع الشرح المركزة لمعالجة الثغرات.'; return ExamSubmissionResultModel( attemptId: DateTime.now().millisecondsSinceEpoch ~/ 1000, score: earnedScore, totalScore: totalScore, percentage: pct, passed: passed, rewindSeconds: passed ? 0 : 45, aiDiagnosticReport: report, weakTopics: weakTopics, tawjihiReadinessScore: (pct * 0.7) + (passed ? 25.0 : 10.0), detailedAnswers: detailedAnswers, ); } }