db = Database::connection(); } public function state(int $matchId): array { $match = $this->match($matchId); $sets = $this->sets($matchId); $events = $this->events($matchId); return [ 'match' => $match, 'sets' => $sets, 'sets_won' => [ 'home' => count(array_filter($sets, fn ($set) => ($set['winner_team_id'] ?? null) === $match['home_team_id'])), 'away' => count(array_filter($sets, fn ($set) => ($set['winner_team_id'] ?? null) === $match['away_team_id'])), ], 'events' => $events, ]; } public function addEvent(int $matchId, array $data, array $user): array { $match = $this->match($matchId); if (($match['status'] ?? '') === 'finished') { throw new InvalidArgumentException('El partido ya finalizó'); } $type = $data['event_type'] ?? 'point'; $teamId = (int) ($data['team_id'] ?? 0); if ($teamId && !in_array($teamId, [(int) $match['home_team_id'], (int) $match['away_team_id']], true)) { throw new InvalidArgumentException('El equipo no participa en este partido'); } $this->db->beginTransaction(); try { $set = $this->currentSet($matchId); if ($type === 'point') { $homePoint = $teamId === (int) $match['home_team_id'] ? 1 : 0; $awayPoint = $teamId === (int) $match['away_team_id'] ? 1 : 0; $stmt = $this->db->prepare( 'UPDATE match_sets SET home_points = home_points + :home, away_points = away_points + :away WHERE id = :id' ); $stmt->execute(['home' => $homePoint, 'away' => $awayPoint, 'id' => $set['id']]); $set = $this->setById((int) $set['id']); } $stmt = $this->db->prepare( 'INSERT INTO match_events (match_id, set_number, team_id, player_id, event_type, points_home, points_away, notes, created_by) VALUES (:match_id, :set_number, :team_id, :player_id, :event_type, :points_home, :points_away, :notes, :created_by)' ); $stmt->execute([ 'match_id' => $matchId, 'set_number' => $set['set_number'], 'team_id' => $teamId ?: null, 'player_id' => $data['player_id'] ?? null, 'event_type' => $type, 'points_home' => $set['home_points'], 'points_away' => $set['away_points'], 'notes' => $data['notes'] ?? null, 'created_by' => $user['sub'] ?? null, ]); $this->evaluateSetAndMatch($matchId, $match, $set); $this->db->commit(); } catch (\Throwable $e) { $this->db->rollBack(); throw $e; } return $this->state($matchId); } public function standings(int $tournamentId): array { $stmt = $this->db->prepare( 'SELECT t.id, t.name, COALESCE(s.played, 0) played, COALESCE(s.won, 0) won, COALESCE(s.lost, 0) lost, COALESCE(s.sets_for, 0) sets_for, COALESCE(s.sets_against, 0) sets_against, COALESCE(s.points, 0) points FROM teams t LEFT JOIN team_standings s ON s.team_id = t.id WHERE t.tournament_id = :tournament_id ORDER BY points DESC, (sets_for - sets_against) DESC, sets_for DESC, t.name ASC' ); $stmt->execute(['tournament_id' => $tournamentId]); return $stmt->fetchAll(); } public function stats(int $tournamentId): array { $stmt = $this->db->prepare( 'SELECT p.id, CONCAT(p.first_name, " ", p.last_name) AS player_name, tm.name AS team_name, SUM(e.event_type = "point") AS points, SUM(e.event_type = "ace") AS aces, SUM(e.event_type = "block") AS blocks, SUM(e.event_type = "attack") AS attacks, SUM(e.event_type = "mvp") AS mvp FROM players p JOIN teams tm ON tm.id = p.team_id LEFT JOIN match_events e ON e.player_id = p.id WHERE tm.tournament_id = :tournament_id GROUP BY p.id, player_name, team_name ORDER BY points DESC, aces DESC, blocks DESC LIMIT 50' ); $stmt->execute(['tournament_id' => $tournamentId]); return ['players' => $stmt->fetchAll(), 'teams' => $this->standings($tournamentId)]; } private function evaluateSetAndMatch(int $matchId, array $match, array $set): void { $target = (int) $set['set_number'] === 5 ? 15 : 25; $home = (int) $set['home_points']; $away = (int) $set['away_points']; if (max($home, $away) < $target || abs($home - $away) < 2) { $this->markLive($matchId); return; } $winner = $home > $away ? $match['home_team_id'] : $match['away_team_id']; $stmt = $this->db->prepare('UPDATE match_sets SET winner_team_id = :winner WHERE id = :id'); $stmt->execute(['winner' => $winner, 'id' => $set['id']]); $sets = $this->sets($matchId); $homeSets = count(array_filter($sets, fn ($row) => ($row['winner_team_id'] ?? null) === $match['home_team_id'])); $awaySets = count(array_filter($sets, fn ($row) => ($row['winner_team_id'] ?? null) === $match['away_team_id'])); if ($homeSets === 3 || $awaySets === 3) { $winnerTeam = $homeSets === 3 ? $match['home_team_id'] : $match['away_team_id']; $this->finishMatch($matchId, (int) $winnerTeam, $homeSets, $awaySets); return; } $nextNumber = (int) $set['set_number'] + 1; $sideSwitch = $nextNumber % 2 === 0 ? 'switched' : 'normal'; $stmt = $this->db->prepare( 'INSERT IGNORE INTO match_sets (match_id, set_number, home_points, away_points, side_state) VALUES (:match_id, :set_number, 0, 0, :side_state)' ); $stmt->execute(['match_id' => $matchId, 'set_number' => $nextNumber, 'side_state' => $sideSwitch]); } private function finishMatch(int $matchId, int $winnerTeamId, int $homeSets, int $awaySets): void { $stmt = $this->db->prepare( 'UPDATE matches SET status = "finished", winner_team_id = :winner, home_sets = :home_sets, away_sets = :away_sets WHERE id = :id' ); $stmt->execute(['winner' => $winnerTeamId, 'home_sets' => $homeSets, 'away_sets' => $awaySets, 'id' => $matchId]); $this->rebuildStandings((int) $this->match($matchId)['tournament_id']); } public function rebuildStandings(int $tournamentId): void { $this->db->prepare('DELETE FROM team_standings WHERE tournament_id = :id')->execute(['id' => $tournamentId]); $teams = $this->db->prepare('SELECT id FROM teams WHERE tournament_id = :id'); $teams->execute(['id' => $tournamentId]); foreach ($teams->fetchAll() as $team) { $stmt = $this->db->prepare( 'SELECT SUM((home_team_id = :team OR away_team_id = :team) AND status = "finished") played, SUM(winner_team_id = :team) won, SUM(status = "finished" AND winner_team_id <> :team) lost, SUM(CASE WHEN home_team_id = :team THEN home_sets WHEN away_team_id = :team THEN away_sets ELSE 0 END) sets_for, SUM(CASE WHEN home_team_id = :team THEN away_sets WHEN away_team_id = :team THEN home_sets ELSE 0 END) sets_against FROM matches WHERE tournament_id = :tournament_id' ); $stmt->execute(['team' => $team['id'], 'tournament_id' => $tournamentId]); $row = $stmt->fetch(); $won = (int) ($row['won'] ?? 0); $lost = (int) ($row['lost'] ?? 0); $this->db->prepare( 'INSERT INTO team_standings (tournament_id, team_id, played, won, lost, sets_for, sets_against, points) VALUES (:tournament_id, :team_id, :played, :won, :lost, :sets_for, :sets_against, :points)' )->execute([ 'tournament_id' => $tournamentId, 'team_id' => $team['id'], 'played' => (int) ($row['played'] ?? 0), 'won' => $won, 'lost' => $lost, 'sets_for' => (int) ($row['sets_for'] ?? 0), 'sets_against' => (int) ($row['sets_against'] ?? 0), 'points' => ($won * 3) + $lost, ]); } } private function currentSet(int $matchId): array { $stmt = $this->db->prepare('SELECT * FROM match_sets WHERE match_id = :id AND winner_team_id IS NULL ORDER BY set_number LIMIT 1'); $stmt->execute(['id' => $matchId]); $set = $stmt->fetch(); if ($set) { return $set; } $this->db->prepare('INSERT INTO match_sets (match_id, set_number) VALUES (:id, 1)')->execute(['id' => $matchId]); return $this->setById((int) $this->db->lastInsertId()); } private function markLive(int $matchId): void { $this->db->prepare('UPDATE matches SET status = "live" WHERE id = :id AND status = "scheduled"')->execute(['id' => $matchId]); } private function match(int $matchId): array { $stmt = $this->db->prepare('SELECT * FROM matches WHERE id = :id'); $stmt->execute(['id' => $matchId]); $match = $stmt->fetch(); if (!$match) { throw new InvalidArgumentException('Partido no encontrado'); } return $match; } private function setById(int $id): array { $stmt = $this->db->prepare('SELECT * FROM match_sets WHERE id = :id'); $stmt->execute(['id' => $id]); return $stmt->fetch(); } private function sets(int $matchId): array { $stmt = $this->db->prepare('SELECT * FROM match_sets WHERE match_id = :id ORDER BY set_number'); $stmt->execute(['id' => $matchId]); return $stmt->fetchAll(); } private function events(int $matchId): array { $stmt = $this->db->prepare( 'SELECT e.*, CONCAT(p.first_name, " ", p.last_name) AS player_name, t.name AS team_name FROM match_events e LEFT JOIN players p ON p.id = e.player_id LEFT JOIN teams t ON t.id = e.team_id WHERE e.match_id = :id ORDER BY e.id DESC LIMIT 100' ); $stmt->execute(['id' => $matchId]); return $stmt->fetchAll(); } }