using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.EntityFrameworkCore; using TournamentOrganizer; using TournamentOrganizer.Models; namespace TournamentOrganizer.ViewModels; public partial class MatchesViewModel : ViewModelBase { private readonly TournamentContext _context; [ObservableProperty] private ObservableCollection _availableEvents = []; [ObservableProperty] private EventOption? _selectedEvent; [ObservableProperty] private ObservableCollection _availableTournaments = []; [ObservableProperty] private TournamentOption? _selectedTournament; [ObservableProperty] private ObservableCollection _matches = []; [ObservableProperty] private MatchDisplay? _selectedMatch; [ObservableProperty] private ObservableCollection _matchTeams = []; [ObservableProperty] private ObservableCollection _rounds = []; [ObservableProperty] private RoundDisplay? _selectedRound; [ObservableProperty] private string _eventFilterSearch = string.Empty; [ObservableProperty] private string _tournamentFilterSearch = string.Empty; [ObservableProperty] private bool _isEventDropdownOpen; [ObservableProperty] private bool _isTournamentDropdownOpen; [ObservableProperty] private string _statusMessage = string.Empty; private readonly ObservableCollection _filteredEvents = []; public ObservableCollection FilteredEvents => _filteredEvents; private readonly ObservableCollection _filteredTournaments = []; public ObservableCollection FilteredTournaments => _filteredTournaments; public bool IsTournamentFilterEnabled => SelectedEvent != null; public bool CanCreateRound => Rounds.Count == 0 || Rounds.All(r => r.State == RoundState.Finished); public bool CanStartRound => SelectedRound != null && SelectedRound.State == RoundState.Waiting; public bool CanAwardVictory => SelectedRound != null && SelectedRound.State == RoundState.Started; public MatchesViewModel() { _context = new TournamentContext(); } public async Task LoadEvents() { var events = await _context.Events .Include(e => e.Tournaments) .ThenInclude(t => t.Game) .ToListAsync(); AvailableEvents.Clear(); _filteredEvents.Clear(); foreach (var e in events) { var option = new EventOption(e); AvailableEvents.Add(option); _filteredEvents.Add(option); } UpdateAvailableTournaments(); UpdateFilteredTournaments(); } private void UpdateAvailableTournaments() { AvailableTournaments.Clear(); if (SelectedEvent == null) { return; } foreach (var t in SelectedEvent.Event.Tournaments) { AvailableTournaments.Add(new TournamentOption(t)); } } private async Task LoadMatches() { if (SelectedTournament == null) { Matches.Clear(); return; } int? selectedMatchId = SelectedMatch?.Id; var matches = await _context.Matches .Where(m => m.TournamentId == SelectedTournament.Tournament.Id) .Include(m => m.Teams) .ThenInclude(tp => tp.Team) .Include(m => m.Rounds) .ToListAsync(); Matches.Clear(); MatchDisplay? newSelectedMatch = null; foreach (var match in matches) { var matchDisplay = new MatchDisplay(match); Matches.Add(matchDisplay); if (match.Id == selectedMatchId) { newSelectedMatch = matchDisplay; } } if (newSelectedMatch != null) { SelectedMatch = newSelectedMatch; } } private async Task LoadMatchDetails() { if (SelectedMatch == null) { MatchTeams.Clear(); Rounds.Clear(); _lastLoadedMatchId = null; return; } _lastLoadedMatchId = SelectedMatch.Id; var match = await _context.Matches .Where(m => m.Id == SelectedMatch.Id) .Include(m => m.Teams) .ThenInclude(tp => tp.Team) .Include(m => m.Rounds) .FirstOrDefaultAsync(); if (match == null) { MatchTeams.Clear(); Rounds.Clear(); return; } MatchTeams.Clear(); foreach (var team in match.Teams) { MatchTeams.Add(new TeamParticipantDisplay(team)); } Rounds.Clear(); foreach (var round in match.Rounds) { Rounds.Add(new RoundDisplay(round)); } OnPropertyChanged(nameof(CanCreateRound)); OnPropertyChanged(nameof(CanStartRound)); OnPropertyChanged(nameof(CanAwardVictory)); } partial void OnSelectedEventChanged(EventOption? value) { OnPropertyChanged(nameof(IsTournamentFilterEnabled)); UpdateAvailableTournaments(); UpdateFilteredTournaments(); SelectedTournament = null; Matches.Clear(); MatchTeams.Clear(); Rounds.Clear(); } partial void OnSelectedTournamentChanged(TournamentOption? value) { _ = LoadMatches(); MatchTeams.Clear(); Rounds.Clear(); } private int? _lastLoadedMatchId; partial void OnSelectedMatchChanged(MatchDisplay? value) { if (value?.Id == _lastLoadedMatchId) return; _ = LoadMatchDetails(); } partial void OnSelectedRoundChanged(RoundDisplay? value) { OnPropertyChanged(nameof(CanStartRound)); OnPropertyChanged(nameof(CanAwardVictory)); } partial void OnEventFilterSearchChanged(string value) => UpdateFilteredEvents(); partial void OnTournamentFilterSearchChanged(string value) => UpdateFilteredTournaments(); private void UpdateFilteredEvents() { _filteredEvents.Clear(); foreach (var e in AvailableEvents.Where(e => string.IsNullOrWhiteSpace(EventFilterSearch) || e.DisplayName.ToLower().Contains(EventFilterSearch.ToLower()))) { _filteredEvents.Add(e); } } private void UpdateFilteredTournaments() { _filteredTournaments.Clear(); if (SelectedEvent == null) { return; } foreach (var t in AvailableTournaments.Where(t => string.IsNullOrWhiteSpace(TournamentFilterSearch) || t.DisplayName.ToLower().Contains(TournamentFilterSearch.ToLower()))) { _filteredTournaments.Add(t); } } public void SelectEventFilter(EventOption option) { SelectedEvent = option; IsEventDropdownOpen = false; EventFilterSearch = string.Empty; } public void ClearEventFilter() { SelectedEvent = null; EventFilterSearch = string.Empty; } public void SelectTournamentFilter(TournamentOption option) { SelectedTournament = option; IsTournamentDropdownOpen = false; TournamentFilterSearch = string.Empty; } public void ClearTournamentFilter() { SelectedTournament = null; TournamentFilterSearch = string.Empty; } [RelayCommand] private async Task CreateRound() { if (SelectedMatch == null) { StatusMessage = "Select a match first"; return; } var match = await _context.Matches .Where(m => m.Id == SelectedMatch.Id) .Include(m => m.Rounds) .FirstOrDefaultAsync(); if (match == null) { StatusMessage = "Match not found"; return; } if (match.Rounds.Any(r => r.State != RoundState.Finished)) { StatusMessage = "All rounds must be finished before creating a new round"; return; } var round = new Round { MatchId = match.Id, Match = match, State = RoundState.Waiting, Players = [] }; match.Rounds.Add(round); await _context.SaveChangesAsync(); StatusMessage = "Round created"; await LoadMatches(); await LoadMatchDetails(); } [RelayCommand] private async Task StartRound() { if (SelectedRound == null) { StatusMessage = "Select a round to start"; return; } var round = await _context.Rounds.FindAsync(SelectedRound.Id); if (round == null) { StatusMessage = "Round not found"; return; } if (round.State != RoundState.Waiting) { StatusMessage = "Round must be in Waiting state to start"; return; } round.State = RoundState.Started; await _context.SaveChangesAsync(); StatusMessage = "Round started"; await LoadMatches(); await LoadMatchDetails(); } [RelayCommand] private async Task AwardVictory(object? parameter) { if (SelectedRound == null) { StatusMessage = "Select a round first"; return; } if (SelectedMatch == null) { StatusMessage = "No match selected"; return; } if (SelectedRound.State != RoundState.Started) { StatusMessage = "Round must be in Started state to award victory"; return; } if (!int.TryParse(parameter?.ToString(), out int teamIndex)) { StatusMessage = "Invalid team index"; return; } var round = await _context.Rounds.FindAsync(SelectedRound.Id); if (round == null) { StatusMessage = "Round not found"; return; } round.State = RoundState.Finished; var match = await _context.Matches .Where(m => m.Id == SelectedMatch.Id) .Include(m => m.Teams) .FirstOrDefaultAsync(); if (match != null && teamIndex >= 0 && teamIndex < match.Teams.Count) { match.Teams[teamIndex].Score++; } await _context.SaveChangesAsync(); // Promote winner and loser to next matches await PromoteTeams(match); await _context.SaveChangesAsync(); var teamName = teamIndex < MatchTeams.Count ? MatchTeams[teamIndex].TeamName : "Unknown"; StatusMessage = $"Victory awarded to {teamName}"; await LoadMatches(); await LoadMatchDetails(); } private async Task PromoteTeams(Match? match) { if (match == null || match.Teams.Count < 2) return; // Determine winner (higher score) and loser var sortedTeams = match.Teams.OrderByDescending(t => t.Score).ToList(); var winner = sortedTeams[0]; var loser = sortedTeams[1]; // Promote winner to WinnerMatch if (match.WinnerMatchId.HasValue) { var winnerMatch = await _context.Matches.FindAsync(match.WinnerMatchId.Value); if (winnerMatch != null && !winnerMatch.Teams.Any(t => t.TeamId == winner.TeamId)) { winnerMatch.Teams.Add(new TeamParticipant { TeamId = winner.TeamId, Seed = winner.Seed, Score = 0, Team = winner.Team, MatchId = winnerMatch.Id, Round = null! }); // Check if winnerMatch now has a bye (only 1 team) and needs recursive promotion if (winnerMatch.Teams.Count == 1 && winnerMatch.WinnerMatchId.HasValue) { await PromoteRecursive(winnerMatch); } } } // Promote loser to LoserMatch (for double elimination) // Only if this match has a LoserMatchId (meaning it's a winner bracket match) if (match.LoserMatchId.HasValue) { var loserMatch = await _context.Matches.FindAsync(match.LoserMatchId.Value); if (loserMatch != null && !loserMatch.Teams.Any(t => t.TeamId == loser.TeamId)) { loserMatch.Teams.Add(new TeamParticipant { TeamId = loser.TeamId, Seed = loser.Seed, Score = 0, Team = loser.Team, MatchId = loserMatch.Id, Round = null! }); // Check if loserMatch now has a bye and needs recursive promotion if (loserMatch.Teams.Count == 1 && loserMatch.WinnerMatchId.HasValue) { await PromoteRecursive(loserMatch); } } } } private async Task PromoteRecursive(Match match) { if (match.Teams.Count != 1 || !match.WinnerMatchId.HasValue) return; var team = match.Teams.First(); var nextMatch = await _context.Matches.FindAsync(match.WinnerMatchId.Value); if (nextMatch != null && !nextMatch.Teams.Any(t => t.TeamId == team.TeamId)) { nextMatch.Teams.Add(new TeamParticipant { TeamId = team.TeamId, Seed = team.Seed, Score = 0, Team = team.Team, MatchId = nextMatch.Id, Round = null! }); // Continue recursive promotion if needed if (nextMatch.Teams.Count == 1 && nextMatch.WinnerMatchId.HasValue) { await PromoteRecursive(nextMatch); } } } } public class TeamParticipantDisplay { public int TeamId { get; set; } public string TeamName { get; set; } = string.Empty; public int Score { get; set; } public int Seed { get; set; } public TeamParticipantDisplay() { } public TeamParticipantDisplay(TeamParticipant tp) { TeamId = tp.TeamId; TeamName = tp.Team.Name; Score = tp.Score; Seed = tp.Seed; } } public class RoundDisplay { public int Id { get; set; } public RoundState State { get; set; } public RoundDisplay() { } public RoundDisplay(Round round) { Id = round.Id; State = round.State; } }