import { Component, DestroyRef, OnInit, inject } from '@angular/core'; import { Router, RouterLink } from '@angular/router'; import { forkJoin, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AuthService } from '../../services/auth.service'; import { GameApiService } from '../../services/game-api.service'; import { GameHistoryService } from '../../services/game-history.service'; import { GameFull, GameStatus } from '../../models/game.models'; type GamesTab = 'active' | 'history'; const FINISHED_STATUSES: GameStatus[] = [ 'checkmate', 'stalemate', 'resign', 'draw', 'insufficientMaterial' ]; @Component({ selector: 'app-games', standalone: true, imports: [RouterLink], templateUrl: './games.component.html', styleUrl: './games.component.css' }) export class GamesComponent implements OnInit { private readonly destroyRef = inject(DestroyRef); private readonly router = inject(Router); private readonly authService = inject(AuthService); private readonly gameApi = inject(GameApiService); private readonly gameHistory = inject(GameHistoryService); tab: GamesTab = 'active'; loading = true; activeGames: GameFull[] = []; finishedGames: GameFull[] = []; ngOnInit(): void { this.authService.currentUser$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { if (!user) void this.router.navigate(['/']); }); this.loadGames(); } setTab(tab: GamesTab): void { this.tab = tab; } resumeGame(gameId: string): void { void this.router.navigate(['/game', gameId]); } removeGame(gameId: string): void { this.gameHistory.removeGame(gameId); this.activeGames = this.activeGames.filter((g) => g.gameId !== gameId); this.finishedGames = this.finishedGames.filter((g) => g.gameId !== gameId); } statusLabel(status: GameStatus): string { const labels: Record = { started: 'In Progress', check: 'Check', checkmate: 'Checkmate', stalemate: 'Stalemate', resign: 'Resigned', draw: 'Draw', drawOffered: 'Draw Offered', fiftyMoveAvailable: 'In Progress', promotionPending: 'In Progress', insufficientMaterial: 'Draw' }; return labels[status] ?? status; } isFinished(status: GameStatus): boolean { return FINISHED_STATUSES.includes(status); } private loadGames(): void { const ids = this.gameHistory.getGameIds(); if (ids.length === 0) { this.loading = false; return; } const requests = ids.map((id) => this.gameApi.getGame(id).pipe( catchError((err: unknown) => { if (typeof err === 'object' && err !== null && (err as { status: number }).status === 404) { this.gameHistory.removeGame(id); } return of(null); }) ) ); forkJoin(requests) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((results) => { const valid = results.filter((g): g is GameFull => g !== null); this.activeGames = valid.filter((g) => !FINISHED_STATUSES.includes(g.state.status)); this.finishedGames = valid.filter((g) => FINISHED_STATUSES.includes(g.state.status)); this.loading = false; }); } }