import 'package:drift/drift.dart'; import 'package:drift_postgres/drift_postgres.dart'; import 'package:injectable/injectable.dart'; import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_backend/repository/export.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; @lazySingleton class AchievementManager { final AchievementRepository _achievementRepository; final UserRepository _userRepository; AchievementManager(this._achievementRepository, this._userRepository); /// Get all available achievements with their definitions List get allAchievementDefinitions => AchievementDefinitions.allAchievements; /// Check and update achievements for a user Future> checkAndUnlockAchievements( String userId, UserDataModel userData, ) async { final unlockedAchievements = []; // Get existing user achievements final userAchievements = await getUserAchievements(userId); final existingIds = userAchievements.map((a) => a.id).toSet(); // Check each achievement for (final achievement in AchievementDefinitions.allAchievements) { if (existingIds.contains(achievement.id)) continue; final isUnlocked = await _checkAchievementCondition( userId, userData, achievement, ); if (isUnlocked) { final unlocked = await unlockAchievement(userId, achievement.id); if (unlocked != null) { unlockedAchievements.add(unlocked); } } else { // Update progress if applicable final progress = await _calculateAchievementProgress( userId, userData, achievement, ); if (progress > 0.0) { await _achievementRepository.updateAchievementProgress( userId, achievement.id, progress, ); } } } return unlockedAchievements; } /// Get achievements for a user Future> getUserAchievements(String userId) async { final userAchievements = await _achievementRepository.getUserAchievements( userId, ); final progressMap = await _achievementRepository.getAchievementProgress( userId, ); final achievements = []; for (final userAchievement in userAchievements) { final definition = AchievementDefinitions.getById( userAchievement.achievementId, ); if (definition != null) { achievements.add( definition.copyWith( unlockedAt: userAchievement.unlockedAt.dateTime, progress: userAchievement.progress, ), ); } } // Add locked achievements with progress for (final definition in AchievementDefinitions.allAchievements) { if (!achievements.any((a) => a.id == definition.id)) { final progress = progressMap[definition.id] ?? 0.0; achievements.add(definition.updateProgress(progress)); } } return achievements; } /// Check if user has specific achievement Future hasAchievement(String userId, String achievementId) async { return await _achievementRepository.hasAchievement(userId, achievementId); } /// Unlock achievement for user Future unlockAchievement( String userId, String achievementId, ) async { final definition = AchievementDefinitions.getById(achievementId); if (definition == null) return null; final companion = UserAchievementsCompanion.insert( userId: userId, achievementId: achievementId, unlockedAt: Value(PgDateTime(DateTime.now())), progress: Value(1.0), ); try { await _achievementRepository.unlockAchievement(companion); return definition.unlock(); } catch (e) { // Achievement might already exist, return existing final existing = await _achievementRepository.getUserAchievement( userId, achievementId, ); return existing != null ? definition.unlock() : null; } } /// Get achievement progress for user Future> getAchievementProgress(String userId) async { return await _achievementRepository.getAchievementProgress(userId); } /// Check if achievement condition is met Future _checkAchievementCondition( String userId, UserDataModel userData, AchievementDto achievement, ) async { switch (achievement.type) { // case AchievementType.firstWordLearned: // return userData.totalCards > 0; // case AchievementType.firstTestCompleted: // return userData.totalTests > 0; case AchievementType.firstPackCompleted: final userPacks = await _userRepository.getUserPacks(userId); return userPacks.isNotEmpty; // case AchievementType.words10Learned: // return userData.totalCards >= 10; // case AchievementType.words50Learned: // return userData.totalCards >= 50; // case AchievementType.words100Learned: // return userData.totalCards >= 100; // case AchievementType.words500Learned: // return userData.totalCards >= 500; // case AchievementType.words1000Learned: // return userData.totalCards >= 1000; case AchievementType.streak7Days: return userData.currentStreak >= 7; case AchievementType.streak30Days: return userData.currentStreak >= 30; case AchievementType.streak100Days: return userData.currentStreak >= 100; // case AchievementType.perfectTest: // return userData.totalTests > 0; // case AchievementType.speedLearner: // return userData.totalCards >= 100 && userData.totalStudyTimeMinutes < 600; default: return false; } } /// Calculate progress towards achievement Future _calculateAchievementProgress( String userId, UserDataModel userData, AchievementDto achievement, ) async { switch (achievement.type) { // case AchievementType.words10Learned: // return (userData.totalCards / 10.0).clamp(0.0, 1.0); // case AchievementType.words50Learned: // return (userData.totalCards / 50.0).clamp(0.0, 1.0); // case AchievementType.words100Learned: // return (userData.totalCards / 100.0).clamp(0.0, 1.0); // case AchievementType.words500Learned: // return (userData.totalCards / 500.0).clamp(0.0, 1.0); // case AchievementType.words1000Learned: // return (userData.totalCards / 1000.0).clamp(0.0, 1.0); default: return 0.0; } } }