import 'package:drift/drift.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/db/tables/player_match_table.dart'; import 'package:tallee/data/db/tables/team_table.dart'; import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/score_entry.dart'; import 'package:tallee/data/models/team.dart'; part 'team_dao.g.dart'; @DriftAccessor(tables: [TeamTable, PlayerMatchTable]) class TeamDao extends DatabaseAccessor with _$TeamDaoMixin { TeamDao(super.db); /* Create */ /// Adds a new [team] to the database. /// Returns `true` if the team was added, `false` otherwise. Future addTeam({required Team team, required String matchId}) async { if (await teamExists(teamId: team.id)) return false; await into(teamTable).insert( TeamTableCompanion.insert( id: team.id, name: team.name, createdAt: team.createdAt, color: Value(team.color.name), score: Value(team.score), ), mode: InsertMode.insertOrReplace, ); await db.batch((batch) async { for (final player in team.members) { await into(playerMatchTable).insert( PlayerMatchTableCompanion.insert( playerId: player.id, matchId: matchId, teamId: Value(team.id), ), mode: InsertMode.insertOrReplace, ); } }); return true; } /// Adds multiple [teams] to the database in a batch operation. Future addTeamsAsList({ required List teams, required String matchId, }) async { if (teams.isEmpty) return false; await db.batch( (b) => b.insertAll( teamTable, teams .map( (team) => TeamTableCompanion.insert( id: team.id, name: team.name, createdAt: team.createdAt, color: Value(team.color.name), score: Value(team.score), ), ) .toList(), mode: InsertMode.insertOrIgnore, ), ); for (final team in teams) { await db.batch((batch) async { for (final player in team.members) { await into(db.playerMatchTable).insert( PlayerMatchTableCompanion.insert( playerId: player.id, matchId: matchId, teamId: Value(team.id), ), mode: InsertMode.insertOrReplace, ); } }); } return true; } /* Read */ /// Retrieves the total count of teams in the database. Future getTeamCount() async { final count = await (selectOnly(teamTable)..addColumns([teamTable.id.count()])) .map((row) => row.read(teamTable.id.count())) .getSingle(); return count ?? 0; } /// Checks if a team with the given [teamId] exists in the database. /// Returns `true` if the team exists, `false` otherwise. Future teamExists({required String teamId}) async { final query = select(teamTable)..where((t) => t.id.equals(teamId)); final result = await query.getSingleOrNull(); return result != null; } /// Retrieves all teams from the database. Future> getAllTeams() async { final query = select(teamTable); final result = await query.get(); return Future.wait( result.map((row) async { final members = await _getTeamMembers(teamId: row.id); return Team( id: row.id, name: row.name, createdAt: row.createdAt, color: AppColor.values.byName(row.color), score: row.score, members: members, ); }), ); } Future> getTeamsByMatchId({required String matchId}) async { final playerMatchQuery = select(db.playerMatchTable) ..where((pm) => pm.matchId.equals(matchId)); final playerMatches = await playerMatchQuery.get(); if (playerMatches.isEmpty) return []; final teamIds = playerMatches .map((pm) => pm.teamId) .whereType() .toSet(); final teams = await Future.wait( teamIds.map((id) => getTeamById(teamId: id)), ); return teams; } /// Retrieves a [Team] by its [teamId], including its members. Future getTeamById({required String teamId}) async { final query = select(teamTable)..where((t) => t.id.equals(teamId)); final result = await query.getSingle(); final members = await _getTeamMembers(teamId: teamId); return Team( id: result.id, name: result.name, createdAt: result.createdAt, color: AppColor.values.byName(result.color), score: result.score, members: members, ); } /// Helper method to get team members from PlayerMatchTable. Future> _getTeamMembers({required String teamId}) async { // Get all player_match entries with this teamId final playerMatchQuery = select(db.playerMatchTable) ..where((pm) => pm.teamId.equals(teamId)); final playerMatches = await playerMatchQuery.get(); if (playerMatches.isEmpty) return []; // Get unique player IDs final playerIds = playerMatches.map((pm) => pm.playerId).toSet(); // Fetch all players final players = await Future.wait( playerIds.map((id) => db.playerDao.getPlayerById(playerId: id)), ); return players ..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); } /* Update */ /// Updates the name of the team with the given [teamId]. Future updateTeamName({ required String teamId, required String name, }) async { final rowsAffected = await (update(teamTable)..where((t) => t.id.equals(teamId))).write( TeamTableCompanion(name: Value(name)), ); return rowsAffected > 0; } /// Updates the color of the team with the given [teamId]. Future updateTeamColor({ required String teamId, required AppColor color, }) async { final rowsAffected = await (update(teamTable)..where((t) => t.id.equals(teamId))).write( TeamTableCompanion(color: Value(color.name)), ); return rowsAffected > 0; } /// Updates the score of the team with the given [teamId]. /// Updates the member scores correspondingly Future updateTeamScore({ required String teamId, required String matchId, required int score, }) async { await (update(teamTable)..where((t) => t.id.equals(teamId))).write( const TeamTableCompanion(score: Value(null)), ); await _deleteAllScoresForMembersOfTeam(teamId: teamId, matchId: matchId); final rowsAffected = await (update(teamTable)..where((t) => t.id.equals(teamId))).write( TeamTableCompanion(score: Value(score)), ); final members = await _getTeamMembers(teamId: teamId); for (final member in members) { await db.scoreEntryDao.addScore( playerId: member.id, matchId: matchId, entry: ScoreEntry(score: score), ); } return rowsAffected > 0; } Future removeScoreForTeam({ required String teamId, required String matchId, }) async { await (update(teamTable)..where((t) => t.id.equals(teamId))).write( const TeamTableCompanion(score: Value(null)), ); await _deleteAllScoresForMembersOfTeam(teamId: teamId, matchId: matchId); return true; } /// Removes the scores for all teams in the match with the given [matchId] by setting their scores to null. Future removeAllTeamScores({required String matchId}) async { // collect all teamIds for the given matchId from playerMatchTable final teamIds = await (selectOnly(playerMatchTable) ..addColumns([playerMatchTable.teamId]) ..where(playerMatchTable.matchId.equals(matchId))) .map((row) => row.read(playerMatchTable.teamId)) .get(); // filter null or duplicates final filteredTeamIds = teamIds.whereType().toSet().toList(); var rowsAffected = 0; if (filteredTeamIds.isNotEmpty) { rowsAffected = await (update(teamTable)..where((t) => t.id.isIn(filteredTeamIds))) .write(const TeamTableCompanion(score: Value(null))); } await db.scoreEntryDao.deleteAllScoresForMatch(matchId: matchId); return rowsAffected > 0; } /* Delete */ /// Deletes all teams from the database. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future deleteAllTeams() async { final query = delete(teamTable); final rowsAffected = await query.go(); return rowsAffected > 0; } /// Deletes the team with the given [teamId] from the database. /// Returns `true` if the team was deleted, `false` otherwise. Future deleteTeam({required String teamId}) async { final query = delete(teamTable)..where((t) => t.id.equals(teamId)); final rowsAffected = await query.go(); return rowsAffected > 0; } /* Score handling */ /// Sets the team with the given [teamId] as the winner of the match with the given [matchId] by assigning a score of 1. /// Returns `true` if the score was updated successfully, `false` otherwise. Future setWinnerTeam({ required String teamId, required String matchId, }) async { return await updateTeamScore(teamId: teamId, matchId: matchId, score: 1); } /// Sets multiple teams as winners of the match with the given [matchId] by assigning a score of 1 to each team. /// Returns `true` if all scores were updated successfully, `false` otherwise. Future setWinnerTeams({ required List winners, required String matchId, }) async { // Reset all team scores . await removeAllTeamScores(matchId: matchId); // Reset all score entries for (final team in winners) { await _deleteAllScoresForMembersOfTeam(teamId: team.id, matchId: matchId); } for (final team in winners) { await updateTeamScore(teamId: team.id, matchId: matchId, score: 1); } return true; } /// Removes the winner status from all Teams with the given [matchId] by setting its score to null. /// Returns `true` if the score was updated successfully, `false` otherwise. Future removeWinnerTeam({required String matchId}) async { return await removeAllTeamScores(matchId: matchId); } /// Sets the team with the given [teamId] as the loser of the match with the given [matchId] by assigning a score of 0. /// Returns `true` if the score was updated successfully, `false` otherwise. Future setLoserTeam({ required String teamId, required String matchId, }) async { return await updateTeamScore(teamId: teamId, matchId: matchId, score: 0); } /// Removes the loser from the match with the given [matchId] by setting its score to null. /// Returns `true` if the score was updated successfully, `false` otherwise. Future removeLoserTeam({required String matchId}) async { return await removeAllTeamScores(matchId: matchId); } /// Sets the placements for the teams in the match with the given [matchId] by assigning scores based on their order in the [teams] list. /// Returns `true` if all scores were updated successfully, `false` otherwise. Future setTeamPlacements({ required String matchId, required List teams, }) async { List success = List.generate(teams.length, (index) => null); for (int i = 0; i < teams.length; i++) { success[i] = await updateTeamScore( matchId: matchId, teamId: teams[i].id, score: teams.length - i, ); } return success.every((result) => result == true); } /// Helper method to delete all scores for members of a team in a specific match. Future _deleteAllScoresForMembersOfTeam({ required String teamId, required String matchId, }) async { final playerMatchQuery = select(db.playerMatchTable) ..where((pm) => pm.teamId.equals(teamId) & pm.matchId.equals(matchId)); final playerMatches = await playerMatchQuery.get(); if (playerMatches.isEmpty) return false; for (final pm in playerMatches) { await db.scoreEntryDao.deleteAllScoresForPlayerInMatch( playerId: pm.playerId, matchId: matchId, ); } return true; } }