import 'package:drift/drift.dart'; import 'package:injectable/injectable.dart'; import 'package:mnemo_cards_backend/database/database.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 AppDatabase _db; AchievementManager(this._db); /// 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 _db.achievementDao.updateAchievementProgress(userId, achievement.id, progress); } } } return unlockedAchievements; } /// Get achievements for a user Future> getUserAchievements(String userId) async { final userAchievements = await _db.achievementDao.getUserAchievements(userId); final progressMap = await _db.achievementDao.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, 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 _db.achievementDao.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(DateTime.now()), progress: Value(1.0), ); try { await _db.achievementDao.unlockAchievement(companion); return definition.unlock(); } catch (e) { // Achievement might already exist, return existing final existing = await _db.achievementDao.getUserAchievement(userId, achievementId); return existing != null ? definition.unlock() : null; } } /// Get achievement progress for user Future> getAchievementProgress(String userId) async { return await _db.achievementDao.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 _db.userDao.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; } } }