<?php
/**
 * Team tournaments: teams, team matches and boards.
 *
 * Implements the data model required for team standings and TRF16 013 section.
 *
 * @package Chess_Podium
 */

if (!defined('ABSPATH')) {
    exit;
}

final class ChessPodium_TeamTournaments
{
    private const MAX_TEAMS = 400;
    private const MAX_BOARDS_PER_MATCH = 20;

    public static function create_tables(): void
    {
        global $wpdb;
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $charset = $wpdb->get_charset_collate();

        $teams = $wpdb->prefix . 'regina_teams';
        $matches = $wpdb->prefix . 'regina_team_matches';
        $boards = $wpdb->prefix . 'regina_team_boards';

        $sql = "
        CREATE TABLE $teams (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            tournament_id BIGINT UNSIGNED NOT NULL,
            name VARCHAR(191) NOT NULL,
            federation VARCHAR(3) NULL,
            start_number INT NULL,
            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY tournament_id (tournament_id)
        ) $charset;

        CREATE TABLE $matches (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            tournament_id BIGINT UNSIGNED NOT NULL,
            round_no INT NOT NULL,
            home_team_id BIGINT UNSIGNED NOT NULL,
            away_team_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
            result VARCHAR(20) NULL,
            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY tournament_round (tournament_id, round_no),
            KEY team_round_home (home_team_id, round_no),
            KEY team_round_away (away_team_id, round_no)
        ) $charset;

        CREATE TABLE $boards (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
            tournament_id BIGINT UNSIGNED NOT NULL,
            match_id BIGINT UNSIGNED NOT NULL,
            board_no INT NOT NULL,
            home_player_id BIGINT UNSIGNED NULL,
            away_player_id BIGINT UNSIGNED NULL,
            result VARCHAR(20) NULL,
            pairing_id BIGINT UNSIGNED NULL,
            created_at DATETIME NOT NULL,
            PRIMARY KEY (id),
            KEY match_board (match_id, board_no),
            KEY tournament_match (tournament_id, match_id)
        ) $charset;
        ";

        dbDelta($sql);
    }

    private static function db_table(string $name): string
    {
        global $wpdb;

        return $wpdb->prefix . $name;
    }

    /**
     * @return list<object{id:int,name:string,federation:?string,start_number:?int}>
     */
    public static function get_teams(int $tournamentId): array
    {
        global $wpdb;
        if ($tournamentId <= 0) {
            return [];
        }
        $table = self::db_table('regina_teams');
        $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
            $wpdb->prepare(
                "SELECT id, name, federation, start_number FROM {$table} WHERE tournament_id = %d ORDER BY COALESCE(start_number, 999999) ASC, name ASC, id ASC",
                $tournamentId
            )
        );

        return is_array($rows) ? $rows : [];
    }

    public static function add_team(int $tournamentId, string $name, string $federation = '', int $startNumber = 0): array
    {
        global $wpdb;
        if ($tournamentId <= 0) {
            return ['success' => false, 'message' => __('Invalid tournament.', 'chess-podium')];
        }
        $name = trim(sanitize_text_field($name));
        if ($name === '') {
            return ['success' => false, 'message' => __('Enter a team name.', 'chess-podium')];
        }
        $fed = strtoupper(trim(sanitize_text_field($federation)));
        $fed = preg_replace('/[^A-Z]/', '', (string) $fed);
        $fed = $fed ? substr($fed, 0, 3) : '';
        $startNumber = $startNumber > 0 ? min(9999, $startNumber) : 0;

        $existing = count(self::get_teams($tournamentId));
        if ($existing >= self::MAX_TEAMS) {
            return ['success' => false, 'message' => __('Too many teams for this tournament.', 'chess-podium')];
        }

        $table = self::db_table('regina_teams');
        $ok = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
            $table,
            [
                'tournament_id' => $tournamentId,
                'name' => $name,
                'federation' => $fed !== '' ? $fed : null,
                'start_number' => $startNumber > 0 ? $startNumber : null,
                'created_at' => current_time('mysql'),
            ],
            ['%d', '%s', '%s', '%d', '%s']
        );
        if (!$ok) {
            return ['success' => false, 'message' => __('Could not add team.', 'chess-podium')];
        }

        return ['success' => true, 'message' => __('Team added.', 'chess-podium')];
    }

    public static function delete_team(int $tournamentId, int $teamId): array
    {
        global $wpdb;
        if ($tournamentId <= 0 || $teamId <= 0) {
            return ['success' => false, 'message' => __('Invalid team.', 'chess-podium')];
        }
        // Unassign players first.
        $playersTable = self::db_table('regina_players');
        $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
            $playersTable,
            ['team_id' => null, 'board_no' => null],
            ['tournament_id' => $tournamentId, 'team_id' => $teamId],
            ['%s', '%s'],
            ['%d', '%d']
        );
        $teamsTable = self::db_table('regina_teams');
        $deleted = $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
            $teamsTable,
            ['id' => $teamId, 'tournament_id' => $tournamentId],
            ['%d', '%d']
        );
        if (!$deleted) {
            return ['success' => false, 'message' => __('Could not delete team.', 'chess-podium')];
        }
        return ['success' => true, 'message' => __('Team deleted.', 'chess-podium')];
    }

    /**
     * Save team/board assignments from admin UI.
     *
     * @param array<int,array{team_id?:mixed,board_no?:mixed}> $rows
     */
    public static function save_player_assignments(int $tournamentId, array $rows): array
    {
        global $wpdb;
        if ($tournamentId <= 0) {
            return ['success' => false, 'message' => __('Invalid tournament.', 'chess-podium')];
        }
        $playersTable = self::db_table('regina_players');
        $teams = self::get_teams($tournamentId);
        $teamSet = [];
        foreach ($teams as $t) {
            $teamSet[(int) $t->id] = true;
        }
        $updated = 0;
        foreach ($rows as $pid => $data) {
            $pid = (int) $pid;
            if ($pid <= 0) {
                continue;
            }
            $teamId = isset($data['team_id']) ? (int) $data['team_id'] : 0;
            $boardNo = isset($data['board_no']) ? (int) $data['board_no'] : 0;
            if ($teamId > 0 && !isset($teamSet[$teamId])) {
                $teamId = 0;
            }
            if ($teamId <= 0) {
                $teamId = null;
                $boardNo = null;
            } else {
                $boardNo = $boardNo > 0 ? min(99, $boardNo) : null;
            }
            $ok = $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
                $playersTable,
                ['team_id' => $teamId, 'board_no' => $boardNo],
                ['id' => $pid, 'tournament_id' => $tournamentId],
                ['%d', '%d'],
                ['%d', '%d']
            );
            if ($ok !== false) {
                $updated++;
            }
        }

        return [
            'success' => true,
            'message' => sprintf(
                /* translators: %d: number of updated rows */
                __('Saved %d player assignments.', 'chess-podium'),
                $updated
            ),
        ];
    }

    /**
     * @return list<object{id:int,round_no:int,home_team_id:int,away_team_id:int,result:?string}>
     */
    public static function get_matches(int $tournamentId, int $roundNo = 0): array
    {
        global $wpdb;
        if ($tournamentId <= 0) {
            return [];
        }
        $table = self::db_table('regina_team_matches');
        if ($roundNo > 0) {
            $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
                $wpdb->prepare(
                    "SELECT id, round_no, home_team_id, away_team_id, result FROM {$table} WHERE tournament_id = %d AND round_no = %d ORDER BY id ASC",
                    $tournamentId,
                    $roundNo
                )
            );
        } else {
            $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
                $wpdb->prepare(
                    "SELECT id, round_no, home_team_id, away_team_id, result FROM {$table} WHERE tournament_id = %d ORDER BY round_no ASC, id ASC",
                    $tournamentId
                )
            );
        }
        return is_array($rows) ? $rows : [];
    }

    /**
     * @return list<object{id:int,board_no:int,home_player_id:?int,away_player_id:?int,result:?string}>
     */
    public static function get_match_boards(int $tournamentId, int $matchId): array
    {
        global $wpdb;
        if ($tournamentId <= 0 || $matchId <= 0) {
            return [];
        }
        $table = self::db_table('regina_team_boards');
        $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
            $wpdb->prepare(
                "SELECT id, board_no, home_player_id, away_player_id, result FROM {$table} WHERE tournament_id = %d AND match_id = %d ORDER BY board_no ASC, id ASC",
                $tournamentId,
                $matchId
            )
        );
        return is_array($rows) ? $rows : [];
    }

    public static function add_match(int $tournamentId, int $roundNo, int $homeTeamId, int $awayTeamId, int $boardsPerMatch): array
    {
        global $wpdb;
        if ($tournamentId <= 0) {
            return ['success' => false, 'message' => __('Invalid tournament.', 'chess-podium')];
        }
        $roundNo = max(1, $roundNo);
        $boardsPerMatch = max(1, min(self::MAX_BOARDS_PER_MATCH, $boardsPerMatch));
        if ($homeTeamId <= 0 || $awayTeamId <= 0 || $homeTeamId === $awayTeamId) {
            return ['success' => false, 'message' => __('Select two different teams.', 'chess-podium')];
        }
        $teams = self::get_teams($tournamentId);
        $teamSet = [];
        foreach ($teams as $t) {
            $teamSet[(int) $t->id] = true;
        }
        if (!isset($teamSet[$homeTeamId]) || !isset($teamSet[$awayTeamId])) {
            return ['success' => false, 'message' => __('Invalid team selection.', 'chess-podium')];
        }
        $matchesTable = self::db_table('regina_team_matches');
        $ok = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
            $matchesTable,
            [
                'tournament_id' => $tournamentId,
                'round_no' => $roundNo,
                'home_team_id' => $homeTeamId,
                'away_team_id' => $awayTeamId,
                'result' => null,
                'created_at' => current_time('mysql'),
            ],
            ['%d', '%d', '%d', '%d', '%s', '%s']
        );
        if (!$ok) {
            return ['success' => false, 'message' => __('Could not create match.', 'chess-podium')];
        }
        $matchId = (int) $wpdb->insert_id;
        $boardsTable = self::db_table('regina_team_boards');
        for ($b = 1; $b <= $boardsPerMatch; $b++) {
            $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
                $boardsTable,
                [
                    'tournament_id' => $tournamentId,
                    'match_id' => $matchId,
                    'board_no' => $b,
                    'home_player_id' => null,
                    'away_player_id' => null,
                    'result' => null,
                    'pairing_id' => null,
                    'created_at' => current_time('mysql'),
                ],
                ['%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s']
            );
        }

        return ['success' => true, 'message' => __('Match created.', 'chess-podium')];
    }

    private static function add_bye_match(int $tournamentId, int $roundNo, int $homeTeamId, int $boardsPerMatch): array
    {
        global $wpdb;
        if ($tournamentId <= 0 || $roundNo <= 0 || $homeTeamId <= 0) {
            return ['success' => false, 'message' => __('Invalid match.', 'chess-podium')];
        }
        $boardsPerMatch = max(1, min(self::MAX_BOARDS_PER_MATCH, $boardsPerMatch));
        $matchesTable = self::db_table('regina_team_matches');
        $ok = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
            $matchesTable,
            [
                'tournament_id' => $tournamentId,
                'round_no' => $roundNo,
                'home_team_id' => $homeTeamId,
                'away_team_id' => 0,
                'result' => 'BYE',
                'created_at' => current_time('mysql'),
            ],
            ['%d', '%d', '%d', '%d', '%s', '%s']
        );
        if (!$ok) {
            return ['success' => false, 'message' => __('Could not create match.', 'chess-podium')];
        }
        $matchId = (int) $wpdb->insert_id;
        $boardsTable = self::db_table('regina_team_boards');
        for ($b = 1; $b <= $boardsPerMatch; $b++) {
            $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
                $boardsTable,
                [
                    'tournament_id' => $tournamentId,
                    'match_id' => $matchId,
                    'board_no' => $b,
                    'home_player_id' => null,
                    'away_player_id' => null,
                    // Treat bye as forfeit wins for home across all boards.
                    'result' => '1F',
                    'pairing_id' => null,
                    'created_at' => current_time('mysql'),
                ],
                ['%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s']
            );
        }

        return ['success' => true, 'message' => __('Bye assigned.', 'chess-podium')];
    }

    /**
     * Generate Swiss-style team pairings for a round (enhanced):
     * - group by score (primary score), float down when a group is odd
     * - avoid repeats when possible (fallback to repeats if forced)
     * - assign home/away to balance "colors" across rounds (home vs away count).
     *
     * Note: This is not a full implementation of FIDE C.04.6, but it follows the
     * core Swiss principles and produces stable pairings for most club/team events.
     */
    public static function generate_round_pairings(int $tournamentId, int $roundNo, int $boardsPerMatch, string $primaryScore = 'mp'): array
    {
        if ($tournamentId <= 0) {
            return ['success' => false, 'message' => __('Invalid tournament.', 'chess-podium')];
        }
        $roundNo = max(1, $roundNo);
        $boardsPerMatch = max(1, min(self::MAX_BOARDS_PER_MATCH, $boardsPerMatch));
        $primaryScore = in_array($primaryScore, ['mp', 'bp'], true) ? $primaryScore : 'mp';

        // Do not generate if the round already has matches.
        $existing = self::get_matches($tournamentId, $roundNo);
        if ($existing !== []) {
            return ['success' => false, 'message' => __('This round already has team matches. Delete them first if you want to regenerate.', 'chess-podium')];
        }

        $teams = self::get_teams($tournamentId);
        if (count($teams) < 2) {
            return ['success' => false, 'message' => __('Add at least two teams first.', 'chess-podium')];
        }

        // Build opponent history (team_id => set(opponent_id)).
        $opp = [];
        foreach ($teams as $t) {
            $opp[(int) $t->id] = [];
        }
        $homeAway = [];
        foreach ($teams as $t) {
            $homeAway[(int) $t->id] = ['home' => 0, 'away' => 0];
        }
        foreach (self::get_matches($tournamentId, 0) as $m) {
            $h = (int) ($m->home_team_id ?? 0);
            $a = (int) ($m->away_team_id ?? 0);
            if ($h > 0 && $a > 0) {
                $opp[$h][$a] = true;
                $opp[$a][$h] = true;
                if (isset($homeAway[$h])) {
                    $homeAway[$h]['home']++;
                }
                if (isset($homeAway[$a])) {
                    $homeAway[$a]['away']++;
                }
            }
        }

        // Standings order.
        $rows = self::compute_standings($tournamentId, $primaryScore);
        if ($rows === []) {
            return ['success' => false, 'message' => __('Could not compute team list.', 'chess-podium')];
        }

        // Group by primary score.
        $groups = [];
        foreach ($rows as $r) {
            $teamId = (int) ($r['team_id'] ?? 0);
            if ($teamId <= 0) {
                continue;
            }
            $scoreKey = $primaryScore === 'bp'
                ? (string) round((float) ($r['bp'] ?? 0.0), 1)
                : (string) ((int) ($r['mp'] ?? 0));
            $groups[$scoreKey] = $groups[$scoreKey] ?? [];
            $groups[$scoreKey][] = $teamId;
        }
        if ($groups === []) {
            return ['success' => false, 'message' => __('Could not compute team list.', 'chess-podium')];
        }
        // Process groups from highest to lowest score.
        $keys = array_keys($groups);
        usort($keys, static function ($a, $b) use ($primaryScore) {
            if ($primaryScore === 'bp') {
                return (float) $b <=> (float) $a;
            }
            return (int) $b <=> (int) $a;
        });

        $pairs = [];
        $floater = null;
        foreach ($keys as $k) {
            $list = $groups[$k];
            if ($floater !== null) {
                array_unshift($list, $floater);
                $floater = null;
            }
            if (count($list) % 2 === 1) {
                // Float down the last team (lowest within the group).
                $floater = array_pop($list);
            }
            $pairs = array_merge($pairs, self::pair_group_avoiding_repeats($list, $opp));
        }
        if ($floater !== null) {
            $byeRes = self::add_bye_match($tournamentId, $roundNo, (int) $floater, $boardsPerMatch);
            if (empty($byeRes['success'])) {
                return ['success' => false, 'message' => $byeRes['message'] ?? __('Could not create match.', 'chess-podium')];
            }
        }

        if ($pairs === []) {
            return ['success' => false, 'message' => __('Could not generate pairings.', 'chess-podium')];
        }

        $created = 0;
        foreach ($pairs as [$a, $b]) {
            [$home, $away] = self::decide_home_away((int) $a, (int) $b, $homeAway);
            $res = self::add_match($tournamentId, $roundNo, $home, $away, $boardsPerMatch);
            if (empty($res['success'])) {
                return ['success' => false, 'message' => __('Pairing generation failed while creating matches.', 'chess-podium')];
            }
            // update home/away counts for next decisions in same round
            if (isset($homeAway[$home])) {
                $homeAway[$home]['home']++;
            }
            if (isset($homeAway[$away])) {
                $homeAway[$away]['away']++;
            }
            $created++;
        }

        return [
            'success' => true,
            'message' => sprintf(
                /* translators: 1: round number, 2: number of matches */
                __('Generated round %1$d with %2$d team matches.', 'chess-podium'),
                $roundNo,
                $created
            ),
        ];
    }

    /**
     * Pair a score group top-down, avoiding repeats when possible.
     *
     * @param list<int> $teamIds already ordered by standings within the group
     * @param array<int,array<int,bool>> $opp
     * @return list<array{0:int,1:int}>
     */
    private static function pair_group_avoiding_repeats(array $teamIds, array $opp): array
    {
        $teamIds = array_values(array_filter($teamIds, static fn ($id) => (int) $id > 0));
        if (count($teamIds) < 2) {
            return [];
        }
        if (count($teamIds) % 2 === 1) {
            // Should not happen: caller floats down.
            array_pop($teamIds);
        }

        $pairs = [];
        $tryPair = static function (array $remaining, array $opp, array &$pairsOut) use (&$tryPair): bool {
            if ($remaining === []) {
                return true;
            }
            $a = array_shift($remaining);
            // Prefer non-repeats first.
            foreach ($remaining as $i => $b) {
                if (!empty($opp[$a][$b])) {
                    continue;
                }
                $next = $remaining;
                array_splice($next, $i, 1);
                $pairsOut[] = [$a, $b];
                if ($tryPair($next, $opp, $pairsOut)) {
                    return true;
                }
                array_pop($pairsOut);
            }
            // Fallback to repeats.
            foreach ($remaining as $i => $b) {
                $next = $remaining;
                array_splice($next, $i, 1);
                $pairsOut[] = [$a, $b];
                if ($tryPair($next, $opp, $pairsOut)) {
                    return true;
                }
                array_pop($pairsOut);
            }
            return false;
        };
        $ok = $tryPair($teamIds, $opp, $pairs);

        return $ok ? $pairs : [];
    }

    /**
     * Decide home/away to balance counts (home vs away).
     *
     * @param array<int,array{home:int,away:int}> $homeAway
     * @return array{0:int,1:int} [home, away]
     */
    private static function decide_home_away(int $a, int $b, array $homeAway): array
    {
        $aHome = $homeAway[$a]['home'] ?? 0;
        $aAway = $homeAway[$a]['away'] ?? 0;
        $bHome = $homeAway[$b]['home'] ?? 0;
        $bAway = $homeAway[$b]['away'] ?? 0;
        $aNeedHome = ($aHome - $aAway);
        $bNeedHome = ($bHome - $bAway);
        // Lower (more negative) means "needs home" more.
        if ($aNeedHome === $bNeedHome) {
            // stable fallback: keep order
            return [$a, $b];
        }
        if ($aNeedHome > $bNeedHome) {
            // a has more home already -> give home to b
            return [$b, $a];
        }
        return [$a, $b];
    }

    /**
     * @param array<int,array{home_player_id?:mixed,away_player_id?:mixed,result?:mixed}> $rows board_id => data
     */
    public static function save_match_boards(int $tournamentId, int $matchId, array $rows): array
    {
        global $wpdb;
        if ($tournamentId <= 0 || $matchId <= 0) {
            return ['success' => false, 'message' => __('Invalid match.', 'chess-podium')];
        }
        $table = self::db_table('regina_team_boards');
        $updated = 0;
        foreach ($rows as $boardId => $data) {
            $boardId = (int) $boardId;
            if ($boardId <= 0) {
                continue;
            }
            $homePid = isset($data['home_player_id']) ? (int) $data['home_player_id'] : 0;
            $awayPid = isset($data['away_player_id']) ? (int) $data['away_player_id'] : 0;
            $res = isset($data['result']) ? (string) $data['result'] : '';
            $res = trim($res);
            $res = $res === '' ? null : $res;
            $ok = $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
                $table,
                [
                    'home_player_id' => $homePid > 0 ? $homePid : null,
                    'away_player_id' => $awayPid > 0 ? $awayPid : null,
                    'result' => $res,
                ],
                ['id' => $boardId, 'tournament_id' => $tournamentId, 'match_id' => $matchId],
                ['%d', '%d', '%s'],
                ['%d', '%d', '%d']
            );
            if ($ok !== false) {
                $updated++;
            }
        }

        return [
            'success' => true,
            'message' => sprintf(
                /* translators: %d: number of boards updated */
                __('Saved %d board results.', 'chess-podium'),
                $updated
            ),
        ];
    }

    private static function result_to_points(?string $res, bool $isHome): float
    {
        $r = is_string($res) ? trim($res) : '';
        if ($r === '') {
            return 0.0;
        }
        if (class_exists('ChessPodiumPlugin')) {
            $r = ChessPodiumPlugin::normalize_result($r);
        }
        if ($r === '1-0' || $r === '1F') {
            return $isHome ? 1.0 : 0.0;
        }
        if ($r === '0-1' || $r === '0F') {
            return $isHome ? 0.0 : 1.0;
        }
        if ($r === '1/2-1/2') {
            return 0.5;
        }
        return 0.0;
    }

    /**
     * @return list<array{team_id:int,name:string,mp:int,bp:float,played:int,won:int,draw:int,lost:int,start_number:int}>
     */
    public static function compute_standings(int $tournamentId, string $primaryScore = 'mp'): array
    {
        if ($tournamentId <= 0) {
            return [];
        }
        $primaryScore = in_array($primaryScore, ['mp', 'bp'], true) ? $primaryScore : 'mp';

        $teams = self::get_teams($tournamentId);
        $teamName = [];
        $startNo = [];
        foreach ($teams as $t) {
            $tid = (int) $t->id;
            $teamName[$tid] = (string) ($t->name ?? '');
            $startNo[$tid] = (int) ($t->start_number ?? 9999);
        }
        if ($teamName === []) {
            return [];
        }
        $rows = [];
        foreach (array_keys($teamName) as $teamId) {
            $rows[$teamId] = [
                'team_id' => $teamId,
                'name' => $teamName[$teamId],
                'mp' => 0,
                'bp' => 0.0,
                'played' => 0,
                'won' => 0,
                'draw' => 0,
                'lost' => 0,
                'start_number' => $startNo[$teamId] ?: 9999,
            ];
        }

        $matchMatrix = []; // team => opp => ['mp'=>int,'bp'=>float,'factor'=>float]
        foreach (self::get_matches($tournamentId, 0) as $m) {
            $mid = (int) ($m->id ?? 0);
            $home = (int) ($m->home_team_id ?? 0);
            $away = (int) ($m->away_team_id ?? 0);
            if ($mid <= 0 || $home <= 0) {
                continue;
            }
            if (!isset($rows[$home]) || ($away > 0 && !isset($rows[$away]))) {
                continue;
            }
            $boards = self::get_match_boards($tournamentId, $mid);
            $homeBp = 0.0;
            $awayBp = 0.0;
            $anyResult = false;
            foreach ($boards as $b) {
                $res = isset($b->result) ? (string) $b->result : '';
                if (trim($res) !== '') {
                    $anyResult = true;
                }
                $homeBp += self::result_to_points($res, true);
                $awayBp += self::result_to_points($res, false);
            }
            if (!$anyResult) {
                continue;
            }
            $rows[$home]['bp'] += $homeBp;
            if ($away > 0) {
                $rows[$away]['bp'] += $awayBp;
            }
            $rows[$home]['played']++;
            if ($away > 0) {
                $rows[$away]['played']++;
            }

            if ($homeBp > $awayBp + 0.0001) {
                $rows[$home]['mp'] += 2;
                if ($away > 0) {
                    $rows[$away]['mp'] += 0;
                }
                $rows[$home]['won']++;
                if ($away > 0) {
                    $rows[$away]['lost']++;
                }
            } elseif ($awayBp > $homeBp + 0.0001) {
                if ($away > 0) {
                    $rows[$away]['mp'] += 2;
                }
                $rows[$home]['mp'] += 0;
                if ($away > 0) {
                    $rows[$away]['won']++;
                }
                $rows[$home]['lost']++;
            } else {
                $rows[$home]['mp'] += 1;
                if ($away > 0) {
                    $rows[$away]['mp'] += 1;
                }
                $rows[$home]['draw']++;
                if ($away > 0) {
                    $rows[$away]['draw']++;
                }
            }

            // Store direct encounter info for tie-breaks (ignore BYE).
            if ($away > 0) {
                $homeMp = ($homeBp > $awayBp + 0.0001) ? 2 : (($awayBp > $homeBp + 0.0001) ? 0 : 1);
                $awayMp = ($awayBp > $homeBp + 0.0001) ? 2 : (($homeBp > $awayBp + 0.0001) ? 0 : 1);
                $homeFactor = $homeMp === 2 ? 1.0 : ($homeMp === 1 ? 0.5 : 0.0);
                $awayFactor = $awayMp === 2 ? 1.0 : ($awayMp === 1 ? 0.5 : 0.0);
                $matchMatrix[$home] = $matchMatrix[$home] ?? [];
                $matchMatrix[$away] = $matchMatrix[$away] ?? [];
                $matchMatrix[$home][$away] = ['mp' => $homeMp, 'bp' => $homeBp, 'factor' => $homeFactor];
                $matchMatrix[$away][$home] = ['mp' => $awayMp, 'bp' => $awayBp, 'factor' => $awayFactor];
            }
        }

        $out = array_values($rows);
        // Precompute Buchholz (sum of opponents' scores) on MP and BP.
        $mpByTeam = [];
        $bpByTeam = [];
        foreach ($out as $r) {
            $tid = (int) ($r['team_id'] ?? 0);
            $mpByTeam[$tid] = (int) ($r['mp'] ?? 0);
            $bpByTeam[$tid] = (float) ($r['bp'] ?? 0.0);
        }
        $buchholzMp = [];
        $buchholzBp = [];
        foreach ($mpByTeam as $tid => $_) {
            $sumMp = 0;
            $sumBp = 0.0;
            foreach (($matchMatrix[$tid] ?? []) as $oppId => $_m) {
                $sumMp += (int) ($mpByTeam[$oppId] ?? 0);
                $sumBp += (float) ($bpByTeam[$oppId] ?? 0.0);
            }
            $buchholzMp[$tid] = $sumMp;
            $buchholzBp[$tid] = $sumBp;
        }
        foreach ($out as &$r) {
            $tid = (int) ($r['team_id'] ?? 0);
            $r['tb_bh_mp'] = (int) ($buchholzMp[$tid] ?? 0);
            $r['tb_bh_bp'] = (float) ($buchholzBp[$tid] ?? 0.0);
            $r['tb_esb_mp'] = 0.0;
            $r['tb_esb_bp'] = 0.0;
            $r['tb_ede_mp'] = 0;
            $r['tb_ede_bp'] = 0.0;
        }
        unset($r);

        // ESB (Extended Sonneborn-Berger) and EDE (Extended Direct Encounter) for teams.
        // ESB_MP: sum(opponent MP * match factor {win=1,draw=0.5,loss=0})
        // ESB_BP: sum(opponent BP * board points scored vs opponent)
        $esbMp = [];
        $esbBp = [];
        foreach ($mpByTeam as $tid => $_) {
            $sumMp = 0.0;
            $sumBp = 0.0;
            foreach (($matchMatrix[$tid] ?? []) as $oppId => $m) {
                $sumMp += (float) ($mpByTeam[$oppId] ?? 0) * (float) ($m['factor'] ?? 0.0);
                $sumBp += (float) ($bpByTeam[$oppId] ?? 0.0) * (float) ($m['bp'] ?? 0.0);
            }
            $esbMp[$tid] = $sumMp;
            $esbBp[$tid] = $sumBp;
        }

        // Compute EDE within tie groups (same primary/secondary/BH).
        $keyOf = static function (array $r) use ($primaryScore): string {
            $mp = (int) ($r['mp'] ?? 0);
            $bp = round((float) ($r['bp'] ?? 0.0), 1);
            $bhMp = (int) ($r['tb_bh_mp'] ?? 0);
            $bhBp = round((float) ($r['tb_bh_bp'] ?? 0.0), 1);
            if ($primaryScore === 'bp') {
                return $bp . '|' . $mp . '|' . $bhMp . '|' . $bhBp;
            }
            return $mp . '|' . $bp . '|' . $bhMp . '|' . $bhBp;
        };
        $tieGroups = [];
        foreach ($out as $r) {
            $k = $keyOf($r);
            $tieGroups[$k] = $tieGroups[$k] ?? [];
            $tieGroups[$k][] = (int) ($r['team_id'] ?? 0);
        }
        $edeMp = [];
        $edeBp = [];
        foreach ($tieGroups as $ids) {
            $ids = array_values(array_filter($ids, static fn ($id) => $id > 0));
            if (count($ids) < 2) {
                continue;
            }
            $set = array_fill_keys($ids, true);
            foreach ($ids as $tid) {
                $sumMp = 0;
                $sumBp = 0.0;
                foreach (($matchMatrix[$tid] ?? []) as $oppId => $m) {
                    if (!isset($set[$oppId])) {
                        continue;
                    }
                    $sumMp += (int) ($m['mp'] ?? 0);
                    $sumBp += (float) ($m['bp'] ?? 0.0);
                }
                $edeMp[$tid] = $sumMp;
                $edeBp[$tid] = $sumBp;
            }
        }

        foreach ($out as &$r) {
            $tid = (int) ($r['team_id'] ?? 0);
            $r['tb_esb_mp'] = round((float) ($esbMp[$tid] ?? 0.0), 2);
            $r['tb_esb_bp'] = round((float) ($esbBp[$tid] ?? 0.0), 2);
            $r['tb_ede_mp'] = (int) ($edeMp[$tid] ?? 0);
            $r['tb_ede_bp'] = round((float) ($edeBp[$tid] ?? 0.0), 1);
        }
        unset($r);

        usort($out, static function ($a, $b) use ($primaryScore) {
            if ($primaryScore === 'bp') {
                if (abs(($b['bp'] ?? 0.0) - ($a['bp'] ?? 0.0)) > 0.0001) {
                    return ($b['bp'] <=> $a['bp']);
                }
                if (($b['mp'] ?? 0) !== ($a['mp'] ?? 0)) {
                    return ($b['mp'] <=> $a['mp']);
                }
            } else {
                if (($b['mp'] ?? 0) !== ($a['mp'] ?? 0)) {
                    return ($b['mp'] <=> $a['mp']);
                }
                if (abs(($b['bp'] ?? 0.0) - ($a['bp'] ?? 0.0)) > 0.0001) {
                    return ($b['bp'] <=> $a['bp']);
                }
            }
            // Tie-break: Buchholz on MP, then Buchholz on BP.
            if (($b['tb_bh_mp'] ?? 0) !== ($a['tb_bh_mp'] ?? 0)) {
                return ((int) ($b['tb_bh_mp'] ?? 0)) <=> ((int) ($a['tb_bh_mp'] ?? 0));
            }
            if (abs(((float) ($b['tb_bh_bp'] ?? 0.0)) - ((float) ($a['tb_bh_bp'] ?? 0.0))) > 0.0001) {
                return ((float) ($b['tb_bh_bp'] ?? 0.0)) <=> ((float) ($a['tb_bh_bp'] ?? 0.0));
            }
            // Tie-break: EDE (direct encounter) then ESB.
            if (($b['tb_ede_mp'] ?? 0) !== ($a['tb_ede_mp'] ?? 0)) {
                return ((int) ($b['tb_ede_mp'] ?? 0)) <=> ((int) ($a['tb_ede_mp'] ?? 0));
            }
            if (abs(((float) ($b['tb_ede_bp'] ?? 0.0)) - ((float) ($a['tb_ede_bp'] ?? 0.0))) > 0.0001) {
                return ((float) ($b['tb_ede_bp'] ?? 0.0)) <=> ((float) ($a['tb_ede_bp'] ?? 0.0));
            }
            if (abs(((float) ($b['tb_esb_mp'] ?? 0.0)) - ((float) ($a['tb_esb_mp'] ?? 0.0))) > 0.0001) {
                return ((float) ($b['tb_esb_mp'] ?? 0.0)) <=> ((float) ($a['tb_esb_mp'] ?? 0.0));
            }
            if (abs(((float) ($b['tb_esb_bp'] ?? 0.0)) - ((float) ($a['tb_esb_bp'] ?? 0.0))) > 0.0001) {
                return ((float) ($b['tb_esb_bp'] ?? 0.0)) <=> ((float) ($a['tb_esb_bp'] ?? 0.0));
            }
            if (($a['start_number'] ?? 9999) !== ($b['start_number'] ?? 9999)) {
                return ($a['start_number'] <=> $b['start_number']);
            }
            return strcmp((string) ($a['name'] ?? ''), (string) ($b['name'] ?? ''));
        });

        return $out;
    }

    public static function render_admin_tab(object $tournament, bool $isActiveTab): void
    {
        $tid = (int) ($tournament->id ?? 0);
        $teams = self::get_teams($tid);
        $players = class_exists('ChessPodiumPlugin') ? ChessPodiumPlugin::get_players($tid) : [];
        $boardsPerMatch = (int) ($tournament->team_boards ?? 4);
        $boardsPerMatch = max(1, min(self::MAX_BOARDS_PER_MATCH, $boardsPerMatch));
        $primaryScore = isset($tournament->team_scoring) ? (string) $tournament->team_scoring : 'mp';
        $standings = self::compute_standings($tid, $primaryScore);
        $roundNo = isset($_GET['cp_team_round']) ? max(1, (int) $_GET['cp_team_round']) : max(1, (int) ($tournament->current_round ?? 1));
        $matches = self::get_matches($tid, $roundNo);
        $teamsById = [];
        foreach ($teams as $t) {
            $teamsById[(int) $t->id] = $t;
        }
        $playersById = [];
        foreach ($players as $p) {
            $playersById[(int) $p->id] = $p;
        }

        ?>
        <div class="cp-tab-panel <?php echo $isActiveTab ? 'cp-tab-active' : ''; ?>" data-panel="teams">
            <h3><?php echo esc_html__('Teams', 'chess-podium'); ?></h3>
            <p class="description"><?php echo esc_html__('Create teams, then assign each player to a team and (optionally) a board number. Team standings and TRF16 team exports use these assignments.', 'chess-podium'); ?></p>

            <h4><?php echo esc_html__('Add team', 'chess-podium'); ?></h4>
            <form method="post" style="max-width:720px;">
                <?php wp_nonce_field('regina_torneo_action', 'regina_nonce'); ?>
                <input type="hidden" name="tournament_id" value="<?php echo esc_attr((string) $tid); ?>">
                <input type="hidden" name="cp_tab" value="teams">
                <p>
                    <label><strong><?php echo esc_html__('Team name', 'chess-podium'); ?></strong></label><br>
                    <input type="text" class="regular-text" name="cp_team_name" required>
                </p>
                <p style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;">
                    <span>
                        <label><strong><?php echo esc_html__('Federation', 'chess-podium'); ?></strong></label><br>
                        <input type="text" name="cp_team_fed" value="" placeholder="<?php echo esc_attr__('e.g. ITA', 'chess-podium'); ?>" style="width:90px;">
                    </span>
                    <span>
                        <label><strong><?php echo esc_html__('Start no.', 'chess-podium'); ?></strong></label><br>
                        <input type="number" name="cp_team_start_number" min="0" max="9999" value="" style="width:110px;">
                    </span>
                    <span>
                        <button type="submit" class="button button-primary" name="regina_action" value="cp_team_add"><?php echo esc_html__('Add team', 'chess-podium'); ?></button>
                    </span>
                </p>
            </form>

            <h4 style="margin-top:1.25em;"><?php echo esc_html__('Teams list', 'chess-podium'); ?></h4>
            <?php if ($teams === []): ?>
                <p class="description"><?php echo esc_html__('No teams yet.', 'chess-podium'); ?></p>
            <?php else: ?>
                <table class="widefat striped" style="max-width:720px;">
                    <thead><tr>
                        <th><?php echo esc_html__('Name', 'chess-podium'); ?></th>
                        <th><?php echo esc_html__('Fed', 'chess-podium'); ?></th>
                        <th><?php echo esc_html_x('Start', 'admin table header (team start number)', 'chess-podium'); ?></th>
                        <th><?php echo esc_html_x('Actions', 'admin table header', 'chess-podium'); ?></th>
                    </tr></thead>
                    <tbody>
                    <?php foreach ($teams as $t): ?>
                        <tr>
                            <td><?php echo esc_html((string) ($t->name ?? '')); ?></td>
                            <td><?php echo esc_html((string) ($t->federation ?? '')); ?></td>
                            <td><?php echo esc_html((string) ((int) ($t->start_number ?? 0))); ?></td>
                            <td>
                                <form method="post" style="display:inline;" onsubmit="return confirm('<?php echo esc_js(__('Delete this team? Players will be unassigned.', 'chess-podium')); ?>');">
                                    <?php wp_nonce_field('regina_torneo_action', 'regina_nonce'); ?>
                                    <input type="hidden" name="tournament_id" value="<?php echo esc_attr((string) $tid); ?>">
                                    <input type="hidden" name="cp_tab" value="teams">
                                    <input type="hidden" name="team_id" value="<?php echo (int) $t->id; ?>">
                                    <button type="submit" class="button button-link-delete" name="regina_action" value="cp_team_delete"><?php echo esc_html__('Delete', 'chess-podium'); ?></button>
                                </form>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            <?php endif; ?>

            <h4 style="margin-top:1.25em;"><?php echo esc_html__('Player assignments', 'chess-podium'); ?></h4>
            <form method="post">
                <?php wp_nonce_field('regina_torneo_action', 'regina_nonce'); ?>
                <input type="hidden" name="tournament_id" value="<?php echo esc_attr((string) $tid); ?>">
                <input type="hidden" name="cp_tab" value="teams">
                <table class="widefat striped">
                    <thead><tr>
                        <th><?php echo esc_html__('Player', 'chess-podium'); ?></th>
                        <th><?php echo esc_html__('Team', 'chess-podium'); ?></th>
                        <th><?php echo esc_html_x('Board', 'team assignment: board number', 'chess-podium'); ?></th>
                    </tr></thead>
                    <tbody>
                    <?php foreach ($players as $p): ?>
                        <tr>
                            <td><?php echo esc_html((string) ($p->name ?? '')); ?></td>
                            <td>
                                <select name="cp_team_assign[<?php echo (int) $p->id; ?>][team_id]">
                                    <option value="0"><?php echo esc_html__('— none —', 'chess-podium'); ?></option>
                                    <?php foreach ($teams as $t): ?>
                                        <option value="<?php echo (int) $t->id; ?>" <?php selected((int) ($p->team_id ?? 0), (int) $t->id); ?>>
                                            <?php echo esc_html((string) ($t->name ?? '')); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </td>
                            <td>
                                <input type="number" min="1" max="99" style="width:90px;"
                                    name="cp_team_assign[<?php echo (int) $p->id; ?>][board_no]"
                                    value="<?php echo esc_attr((string) ((int) ($p->board_no ?? 0) ?: '')); ?>"
                                    placeholder="<?php echo esc_attr__('optional', 'chess-podium'); ?>">
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
                <p style="margin-top:1rem;">
                    <button type="submit" class="button button-primary" name="regina_action" value="cp_team_assign_save"><?php echo esc_html__('Save assignments', 'chess-podium'); ?></button>
                </p>
            </form>

            <h4 style="margin-top:1.5em;"><?php echo esc_html__('Team standings', 'chess-podium'); ?></h4>
            <?php if ($standings === []): ?>
                <p class="description"><?php echo esc_html__('No team results yet.', 'chess-podium'); ?></p>
            <?php else: ?>
                <table class="widefat striped" style="max-width:720px;">
                    <thead><tr>
                        <th><?php echo esc_html__('Pos', 'chess-podium'); ?></th>
                        <th><?php echo esc_html__('Team', 'chess-podium'); ?></th>
                        <th><?php echo esc_html__('MP', 'chess-podium'); ?></th>
                        <th><?php echo esc_html__('BP', 'chess-podium'); ?></th>
                        <th><?php echo esc_html__('Played', 'chess-podium'); ?></th>
                    </tr></thead>
                    <tbody>
                    <?php foreach ($standings as $i => $r): ?>
                        <tr>
                            <td><?php echo (int) ($i + 1); ?></td>
                            <td><?php echo esc_html((string) ($r['name'] ?? '')); ?></td>
                            <td><?php echo esc_html((string) ((int) ($r['mp'] ?? 0))); ?></td>
                            <td><?php echo esc_html(number_format((float) ($r['bp'] ?? 0.0), 1)); ?></td>
                            <td><?php echo esc_html((string) ((int) ($r['played'] ?? 0))); ?></td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            <?php endif; ?>

            <h4 style="margin-top:1.5em;"><?php echo esc_html__('Round matches', 'chess-podium'); ?></h4>
            <form method="get" style="max-width:720px;">
                <input type="hidden" name="page" value="chess-podium">
                <input type="hidden" name="tournament_id" value="<?php echo esc_attr((string) $tid); ?>">
                <input type="hidden" name="cp_tab" value="teams">
                <label for="cp_team_round"><strong><?php echo esc_html__('Round', 'chess-podium'); ?></strong></label>
                <input id="cp_team_round" name="cp_team_round" type="number" min="1" max="99" value="<?php echo esc_attr((string) $roundNo); ?>" style="width:80px;">
                <button class="button" type="submit"><?php echo esc_html__('Go', 'chess-podium'); ?></button>
            </form>

            <h4 style="margin-top:1em;"><?php echo esc_html__('Create match', 'chess-podium'); ?></h4>
            <form method="post" style="max-width:720px;">
                <?php wp_nonce_field('regina_torneo_action', 'regina_nonce'); ?>
                <input type="hidden" name="tournament_id" value="<?php echo esc_attr((string) $tid); ?>">
                <input type="hidden" name="cp_tab" value="teams">
                <input type="hidden" name="cp_team_round" value="<?php echo esc_attr((string) $roundNo); ?>">
                <p style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;">
                    <span>
                        <label><strong><?php echo esc_html_x('Home', 'team match: home side', 'chess-podium'); ?></strong></label><br>
                        <select name="cp_match_home_team">
                            <option value="0"><?php echo esc_html__('— select —', 'chess-podium'); ?></option>
                            <?php foreach ($teams as $t): ?>
                                <option value="<?php echo (int) $t->id; ?>"><?php echo esc_html((string) ($t->name ?? '')); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </span>
                    <span>
                        <label><strong><?php echo esc_html__('Away', 'chess-podium'); ?></strong></label><br>
                        <select name="cp_match_away_team">
                            <option value="0"><?php echo esc_html__('— select —', 'chess-podium'); ?></option>
                            <?php foreach ($teams as $t): ?>
                                <option value="<?php echo (int) $t->id; ?>"><?php echo esc_html((string) ($t->name ?? '')); ?></option>
                            <?php endforeach; ?>
                        </select>
                    </span>
                    <span>
                        <button type="submit" class="button button-primary" name="regina_action" value="cp_team_match_add"><?php echo esc_html__('Create', 'chess-podium'); ?></button>
                    </span>
                </p>
            </form>

            <h4 style="margin-top:1em;"><?php echo esc_html__('Auto-pair this round', 'chess-podium'); ?></h4>
            <p class="description" style="max-width:720px;"><?php echo esc_html__('Generates Swiss-style team pairings based on current standings. Avoids repeats when possible. If the round already has matches, it will not overwrite them.', 'chess-podium'); ?></p>
            <form method="post" style="max-width:720px;">
                <?php wp_nonce_field('regina_torneo_action', 'regina_nonce'); ?>
                <input type="hidden" name="tournament_id" value="<?php echo esc_attr((string) $tid); ?>">
                <input type="hidden" name="cp_tab" value="teams">
                <input type="hidden" name="cp_team_round" value="<?php echo esc_attr((string) $roundNo); ?>">
                <button type="submit" class="button" name="regina_action" value="cp_team_generate_round" <?php disabled($teams === []); ?>>
                    <?php echo esc_html__('Generate pairings for this round', 'chess-podium'); ?>
                </button>
            </form>

            <?php if ($matches === []): ?>
                <p class="description"><?php echo esc_html__('No matches for this round yet.', 'chess-podium'); ?></p>
            <?php else: ?>
                <?php foreach ($matches as $m): ?>
                    <?php
                    $mid = (int) ($m->id ?? 0);
                    $homeId = (int) ($m->home_team_id ?? 0);
                    $awayId = (int) ($m->away_team_id ?? 0);
                    $homeName = isset($teamsById[$homeId]) ? (string) ($teamsById[$homeId]->name ?? '') : ('#' . $homeId);
                    $awayName = isset($teamsById[$awayId]) ? (string) ($teamsById[$awayId]->name ?? '') : ('#' . $awayId);
                    $boards = self::get_match_boards($tid, $mid);
                    usort($boards, static function ($a, $b): int {
                        $ano = (int) ($a->board_no ?? 0);
                        $bno = (int) ($b->board_no ?? 0);
                        if ($ano !== $bno) {
                            return $ano <=> $bno;
                        }
                        return (int) ($a->id ?? 0) <=> (int) ($b->id ?? 0);
                    });
                    ?>
                    <div style="margin:1em 0;padding:12px;border:1px solid #dcdcde;border-radius:8px;background:#fff;max-width:920px;">
                        <h4 style="margin:0 0 10px;"><?php echo esc_html(sprintf(__('Round %d', 'chess-podium'), $roundNo)); ?>: <?php echo esc_html($homeName . ' vs ' . $awayName); ?></h4>
                        <form method="post">
                            <?php wp_nonce_field('regina_torneo_action', 'regina_nonce'); ?>
                            <input type="hidden" name="tournament_id" value="<?php echo esc_attr((string) $tid); ?>">
                            <input type="hidden" name="cp_tab" value="teams">
                            <input type="hidden" name="cp_team_round" value="<?php echo esc_attr((string) $roundNo); ?>">
                            <input type="hidden" name="match_id" value="<?php echo esc_attr((string) $mid); ?>">
                            <table class="widefat striped">
                                <thead><tr>
                                    <th style="width:70px;"><?php echo esc_html__('Board', 'chess-podium'); ?></th>
                                    <th><?php echo esc_html($homeName); ?></th>
                                    <th><?php echo esc_html($awayName); ?></th>
                                    <th style="width:190px;"><?php echo esc_html__('Result', 'chess-podium'); ?></th>
                                </tr></thead>
                                <tbody>
                                <?php foreach ($boards as $b): ?>
                                    <?php
                                    $bid = (int) ($b->id ?? 0);
                                    $bno = (int) ($b->board_no ?? 0);
                                    $homePid = (int) ($b->home_player_id ?? 0);
                                    $awayPid = (int) ($b->away_player_id ?? 0);
                                    $res = isset($b->result) ? (string) $b->result : '';
                                    ?>
                                    <tr>
                                        <td><strong><?php echo esc_html((string) $bno); ?></strong></td>
                                        <td>
                                            <select name="cp_match_boards[<?php echo (int) $bid; ?>][home_player_id]">
                                                <option value="0"><?php echo esc_html__('—', 'chess-podium'); ?></option>
                                                <?php foreach ($players as $p): ?>
                                                    <?php if ((int) ($p->team_id ?? 0) !== $homeId) { continue; } ?>
                                                    <option value="<?php echo (int) $p->id; ?>" <?php selected($homePid, (int) $p->id); ?>>
                                                        <?php echo esc_html((string) ($p->board_no ? ($p->board_no . '. ') : '') . ($p->name ?? '')); ?>
                                                    </option>
                                                <?php endforeach; ?>
                                            </select>
                                        </td>
                                        <td>
                                            <select name="cp_match_boards[<?php echo (int) $bid; ?>][away_player_id]">
                                                <option value="0"><?php echo esc_html__('—', 'chess-podium'); ?></option>
                                                <?php foreach ($players as $p): ?>
                                                    <?php if ((int) ($p->team_id ?? 0) !== $awayId) { continue; } ?>
                                                    <option value="<?php echo (int) $p->id; ?>" <?php selected($awayPid, (int) $p->id); ?>>
                                                        <?php echo esc_html((string) ($p->board_no ? ($p->board_no . '. ') : '') . ($p->name ?? '')); ?>
                                                    </option>
                                                <?php endforeach; ?>
                                            </select>
                                        </td>
                                        <td>
                                            <select name="cp_match_boards[<?php echo (int) $bid; ?>][result]">
                                                <option value="" <?php selected($res, ''); ?>><?php echo esc_html__('—', 'chess-podium'); ?></option>
                                                <option value="1-0" <?php selected($res, '1-0'); ?>><?php echo esc_html__('1-0', 'chess-podium'); ?></option>
                                                <option value="0-1" <?php selected($res, '0-1'); ?>><?php echo esc_html__('0-1', 'chess-podium'); ?></option>
                                                <option value="1/2-1/2" <?php selected($res, '1/2-1/2'); ?>><?php echo esc_html__('1/2-1/2', 'chess-podium'); ?></option>
                                                <option value="1F" <?php selected($res, '1F'); ?>><?php echo esc_html__('1-0F (Home wins by forfeit)', 'chess-podium'); ?></option>
                                                <option value="0F" <?php selected($res, '0F'); ?>><?php echo esc_html__('0-1F (Away wins by forfeit)', 'chess-podium'); ?></option>
                                            </select>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                                </tbody>
                            </table>
                            <p style="margin-top:10px;">
                                <button type="submit" class="button button-primary" name="regina_action" value="cp_team_match_save"><?php echo esc_html__('Save match', 'chess-podium'); ?></button>
                            </p>
                        </form>
                    </div>
                <?php endforeach; ?>
            <?php endif; ?>
        </div>
        <?php
    }
}

