import { Component, DestroyRef, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Router, RouterLink } from '@angular/router'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { TournamentService } from '../../services/tournament.service'; import { AuthService } from '../../services/auth.service'; import { BotService } from '../../services/bot.service'; import { Bot } from '../../models/bot.models'; import { Tournament, TournamentResult, RoundPairings } from '../../models/tournament.models'; import { CurrentUser } from '../../models/auth.models'; type StatusTab = 'started' | 'created' | 'finished'; @Component({ selector: 'app-tournaments', standalone: true, imports: [CommonModule, RouterLink, ReactiveFormsModule], templateUrl: './tournaments.component.html', styleUrl: './tournaments.component.css' }) export class TournamentsComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); private readonly tournamentService = inject(TournamentService); private readonly authService = inject(AuthService); private readonly fb = inject(FormBuilder); private readonly botService = inject(BotService); private readonly router = inject(Router); loading = true; tab: StatusTab = 'started'; currentUser: CurrentUser | null = null; started: Tournament[] = []; created: Tournament[] = []; finished: Tournament[] = []; selectedTournament: Tournament | null = null; pairings: RoundPairings | null = null; pairingsLoading = false; showCreateDialog = false; createForm: FormGroup = this.fb.group({ name: ['', [Validators.required, Validators.minLength(3)]], nbRounds: [4, [Validators.required, Validators.min(1), Validators.max(20)]], clockLimitMinutes: [3, [Validators.required, Validators.min(1), Validators.max(60)]], clockIncrement: [0, [Validators.required, Validators.min(0), Validators.max(60)]], rated: [false] }); createLoading = false; createError: string | null = null; startingId: string | null = null; joinDialogTournamentId: string | null = null; userBots: Bot[] = []; botsLoading = false; joiningBotId: string | null = null; joinError: string | null = null; ngOnInit(): void { this.authService.currentUser$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(u => { this.currentUser = u; }); this.loadTournaments(); } openCreateDialog(): void { this.createForm.reset({ name: '', nbRounds: 4, clockLimitMinutes: 3, clockIncrement: 0, rated: false }); this.createError = null; this.showCreateDialog = true; } closeCreateDialog(): void { this.showCreateDialog = false; } submitCreate(): void { if (this.createForm.invalid) return; this.createLoading = true; this.createError = null; this.tournamentService.create(this.createForm.value).subscribe({ next: t => { this.createLoading = false; this.showCreateDialog = false; this.created = [t, ...this.created]; this.tab = 'created'; this.selectedTournament = null; }, error: err => { this.createLoading = false; this.createError = err.error?.message ?? err.error?.error ?? 'Failed to create tournament.'; } }); } setTab(tab: StatusTab): void { this.tab = tab; this.selectedTournament = null; this.pairings = null; } selectTournament(t: Tournament): void { if (this.selectedTournament?.id === t.id) { this.selectedTournament = null; this.pairings = null; return; } this.selectedTournament = t; this.pairings = null; if (t.round > 0) { this.loadPairings(t.id, t.round); } } get activeList(): Tournament[] { return this[this.tab]; } clockDisplay(t: Tournament): string { const min = Math.floor(t.clock.limit / 60); return `${min}+${t.clock.increment}`; } rankMedal(rank: number): string { if (rank === 1) return '🥇'; if (rank === 2) return '🥈'; if (rank === 3) return '🥉'; return `${rank}.`; } scoreDisplay(r: TournamentResult): string { return r.points % 1 === 0 ? `${r.points}` : `${r.points}`; } startTournament(event: MouseEvent, t: Tournament): void { event.stopPropagation(); this.startingId = t.id; this.tournamentService.start(t.id).subscribe({ next: updated => { this.startingId = null; const list = this.created.map(x => x.id === t.id ? updated : x); this.created = list.filter(x => x.status === 'created'); if (!this.started.find(x => x.id === updated.id)) this.started = [updated, ...this.started]; this.selectedTournament = updated; this.tab = 'started'; }, error: () => { this.startingId = null; } }); } watchGame(gameId: string): void { void this.router.navigate(['/game', gameId]); } openJoinDialog(event: MouseEvent, tournamentId: string): void { event.stopPropagation(); this.joinDialogTournamentId = tournamentId; this.joinError = null; this.botsLoading = true; this.botService.list() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: bots => { this.userBots = bots; this.botsLoading = false; }, error: () => { this.botsLoading = false; } }); } closeJoinDialog(): void { this.joinDialogTournamentId = null; this.joiningBotId = null; this.joinError = null; } joinWithBot(bot: Bot): void { if (!this.joinDialogTournamentId || this.joiningBotId) return; this.joiningBotId = bot.id; this.joinError = null; this.botService.rotateToken(bot.id).subscribe({ next: token => { this.tournamentService.joinWithBotToken(this.joinDialogTournamentId!, token).subscribe({ next: () => { this.joiningBotId = null; const tid = this.joinDialogTournamentId!; this.closeJoinDialog(); this.tournamentService.get(tid) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(updated => { this.created = this.created.map(x => x.id === tid ? updated : x); this.started = this.started.map(x => x.id === tid ? updated : x); if (this.selectedTournament?.id === tid) this.selectedTournament = updated; }); }, error: err => { this.joiningBotId = null; this.joinError = err.error?.message ?? err.error?.error ?? 'Failed to join tournament.'; } }); }, error: () => { this.joiningBotId = null; this.joinError = 'Failed to get bot token.'; } }); } private loadTournaments(): void { this.tournamentService.list() .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: list => { this.started = list.started; this.created = list.created; this.finished = list.finished; this.loading = false; if (this.started.length === 0 && this.created.length > 0) this.tab = 'created'; else if (this.started.length === 0 && this.finished.length > 0) this.tab = 'finished'; }, error: () => { this.loading = false; } }); } private loadPairings(id: string, round: number): void { this.pairingsLoading = true; this.tournamentService.roundPairings(id, round) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: p => { this.pairings = p; this.pairingsLoading = false; }, error: () => { this.pairingsLoading = false; } }); } }