<?php
declare(strict_types=1);

function manager_invite_ready(array $invite): bool {
  $accepted = $invite['accepted'] ?? [];
  foreach (($invite['users'] ?? []) as $name) {
    if (empty($accepted[$name])) return false;
  }
  return true;
}

function manager_cpu_id(string $teamId): string {
  return 'cpu:' . $teamId;
}

function is_cpu_manager(string $id): bool {
  return str_starts_with($id, 'cpu:');
}

function manager_mark_team_ids(array &$keys, array $team): void {
  foreach ([(string)($team['id'] ?? ''), (string)($team['sourceId'] ?? '')] as $id) {
    if ($id === '') continue;
    $keys[$id] = true;
    $source = source_team_id($id);
    if ($source !== '') $keys[$source] = true;
  }
}

function manager_human_team_keys(array $state, ?array $season = null): array {
  $keys = [];
  $names = is_array($season) ? array_values($season['humanManagers'] ?? []) : array_keys($state['users'] ?? []);
  foreach ($names as $name) {
    $name = (string)$name;
    if (isset($state['users'][$name]) && has_team($state['users'][$name])) {
      manager_mark_team_ids($keys, user_team($state['users'][$name]));
    }
    if (is_array($season)) {
      $participant = manager_participant($season, $name);
      $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
      manager_mark_team_ids($keys, $team);
    }
  }
  return $keys;
}

function manager_game_human_team_keys(array $state, string $managerGameId, array $selected = [], ?array $previousSeason = null): array {
  $keys = [];
  $managerGameId = (string)$managerGameId;
  $markUser = function (string $name) use (&$state, &$keys): void {
    $name = clean_name($name);
    if ($name !== '' && isset($state['users'][$name]) && has_team($state['users'][$name])) {
      manager_mark_team_ids($keys, user_team($state['users'][$name]));
    }
  };
  $markSeason = function (?array $season) use (&$keys, $managerGameId, $markUser): void {
    if (!is_array($season) || ($season['status'] ?? '') === 'aborted') return;
    if ($managerGameId !== '' && manager_game_id($season) !== $managerGameId) return;
    foreach (($season['humanManagers'] ?? []) as $name) {
      $name = clean_name((string)$name);
      $markUser($name);
      $participant = manager_participant($season, $name);
      if (is_array($participant['team'] ?? null)) manager_mark_team_ids($keys, $participant['team']);
    }
  };
  foreach ($selected as $name) $markUser((string)$name);
  if ($previousSeason !== null) $markSeason($previousSeason);
  foreach (($state['managerSeasons'] ?? []) as $season) {
    if (is_array($season)) $markSeason($season);
  }
  return $keys;
}

function manager_team_unique_key(array $team): string {
  foreach ([(string)($team['sourceId'] ?? ''), (string)($team['id'] ?? '')] as $id) {
    $key = source_team_id($id);
    if ($key !== '') return $key;
  }
  return player_id_slug((string)($team['name'] ?? ''));
}

function manager_duplicate_human_team(array $state, array $selected): ?array {
  $seen = [];
  foreach ($selected as $name) {
    $name = clean_name((string)$name);
    if ($name === '' || !isset($state['users'][$name]) || !has_team($state['users'][$name])) continue;
    $team = user_team($state['users'][$name]);
    $key = manager_team_unique_key($team);
    if ($key === '') continue;
    if (isset($seen[$key])) {
      return [
        'team' => $team,
        'users' => [$seen[$key], $name],
      ];
    }
    $seen[$key] = $name;
  }
  return null;
}

function manager_team_choice_from_id(string $teamId): ?array {
  $teamId = source_team_id($teamId);
  if ($teamId === '') return null;
  $team = team_by_id($teamId);
  return is_array($team) ? apply_team_flag_fields($team) : null;
}

function manager_team_choice_for_user(array $state, string $user, $rawChoice = null): ?array {
  $user = clean_name($user);
  $teamId = '';
  if (is_array($rawChoice)) {
    $teamId = (string)($rawChoice['teamId'] ?? $rawChoice['id'] ?? $rawChoice['sourceId'] ?? '');
  } elseif (is_scalar($rawChoice)) {
    $teamId = (string)$rawChoice;
  }
  $team = manager_team_choice_from_id($teamId);
  if ($team) return $team;
  if ($user !== '' && isset($state['users'][$user]) && has_team($state['users'][$user])) {
    return user_team($state['users'][$user]);
  }
  return null;
}

function manager_team_choices_from_payload(array $state, array $selected, $rawChoices): array {
  $rawChoices = is_array($rawChoices) ? $rawChoices : [];
  $normalizedChoices = [];
  foreach ($rawChoices as $key => $value) {
    $cleanKey = clean_name((string)$key);
    if ($cleanKey !== '') $normalizedChoices[$cleanKey] = $value;
  }
  $out = [];
  foreach ($selected as $name) {
    $name = clean_name((string)$name);
    if ($name === '') continue;
    $raw = $normalizedChoices[$name] ?? null;
    $team = manager_team_choice_for_user($state, $name, $raw);
    if (!$team) return [];
    $out[$name] = $team;
  }
  return $out;
}

function manager_invite_team_choices(array $state, array $invite): array {
  $stored = is_array($invite['teamsByUser'] ?? null) ? $invite['teamsByUser'] : [];
  $normalizedStored = [];
  foreach ($stored as $key => $value) {
    $cleanKey = clean_name((string)$key);
    if ($cleanKey !== '') $normalizedStored[$cleanKey] = $value;
  }
  $out = [];
  foreach (($invite['users'] ?? []) as $name) {
    $name = clean_name((string)$name);
    if ($name === '') continue;
    $team = manager_team_choice_for_user($state, $name, $normalizedStored[$name] ?? null);
    if ($team) $out[$name] = $team;
  }
  return $out;
}

function manager_duplicate_team_choices(array $teamChoices, ?array $users = null): ?array {
  $seen = [];
  $allowed = $users === null ? null : array_fill_keys(array_map(fn($name) => clean_name((string)$name), $users), true);
  foreach ($teamChoices as $name => $team) {
    $name = clean_name((string)$name);
    if ($name === '' || ($allowed !== null && empty($allowed[$name])) || !is_array($team)) continue;
    $key = manager_team_unique_key($team);
    if ($key === '') continue;
    if (isset($seen[$key])) {
      return [
        'team' => $team,
        'users' => [$seen[$key], $name],
      ];
    }
    $seen[$key] = $name;
  }
  return null;
}

function manager_apply_team_choice_to_user(array &$state, string $user, array $team, int $divisionLevel = 5): bool {
  $user = clean_name($user);
  if ($user === '' || empty($state['users'][$user]) || !is_array($state['users'][$user])) return false;
  $catalogTeam = manager_team_choice_from_id((string)($team['id'] ?? $team['sourceId'] ?? ''));
  if ($catalogTeam) $team = $catalogTeam;
  $teamId = source_team_id((string)($team['id'] ?? $team['sourceId'] ?? ''));
  if ($teamId === '' || empty($team['name'])) return false;
  reset_user_manager_transfer_state($state, $user);
  $state['users'][$user]['teamId'] = $teamId;
  $state['users'][$user]['teamName'] = (string)$team['name'];
  $state['users'][$user]['shirt'] = (string)($team['shirt'] ?? '#f5732c');
  $state['users'][$user]['trim'] = (string)($team['trim'] ?? '#1e1e1e');
  $state['users'][$user]['flagPattern'] = (string)($team['flagPattern'] ?? 'plain');
  $state['users'][$user]['flagAccent'] = (string)($team['flagAccent'] ?? ($team['trim'] ?? '#111111'));
  $state['users'][$user]['managerDivisionLevel'] = clamp_int($divisionLevel, 1, 5);
  reset_user_manager_squad($state['users'][$user]);
  reset_user_manager_finance($state['users'][$user]);
  return true;
}

function manager_team_controlled_by_human(array $state, ?array $season, string $teamId): bool {
  $teamId = source_team_id($teamId);
  if ($teamId === '') return false;
  $keys = manager_human_team_keys($state, $season);
  return isset($keys[$teamId]);
}

function manager_division_id(int $level): string {
  return match (clamp_int($level, 1, 5)) {
    1 => 'premier-league',
    2 => 'championship',
    3 => 'league-one',
    4 => 'league-two',
    default => 'national-league',
  };
}

function manager_division_target_count(int $level): int {
  return clamp_int($level, 1, 5) === 1 ? 20 : 24;
}

function manager_promotion_rules(int $level): array {
  return match (clamp_int($level, 1, 5)) {
	    1 => [
	      'direct' => 0,
	      'playoffStart' => 0,
	      'playoffEnd' => 0,
	      'relegation' => 3,
	      'europe' => [
	        'championsLeagueStart' => 1,
	        'championsLeagueEnd' => 5,
	        'europaLeagueStart' => 6,
	        'europaLeagueEnd' => 7,
	        'conferenceLeagueStart' => 8,
	        'conferenceLeagueEnd' => 8,
	      ],
	      'text' => 'Premier League: top 5 Champions League. 6th-7th Europa League. 8th Conference League. Bottom 3 relegated.',
	    ],
	    2 => [
	      'direct' => 2,
	      'playoffStart' => 3,
	      'playoffEnd' => 6,
	      'relegation' => 3,
	      'text' => 'Top 2 promoted. 3rd-6th enter playoffs. Bottom 3 relegated.',
	    ],
	    3 => [
	      'direct' => 2,
	      'playoffStart' => 3,
	      'playoffEnd' => 6,
	      'relegation' => 4,
	      'text' => 'Top 2 promoted. 3rd-6th enter playoffs. Bottom 4 relegated.',
	    ],
    4 => [
      'direct' => 3,
      'playoffStart' => 4,
      'playoffEnd' => 7,
      'relegation' => 2,
      'text' => 'Top 3 promoted. 4th-7th enter playoffs. Bottom 2 relegated.',
    ],
    default => [
      'direct' => 1,
      'playoffStart' => 2,
      'playoffEnd' => 7,
      'relegation' => 0,
      'text' => 'Champion promoted. 2nd-7th enter playoffs.',
    ],
  };
}

function manager_relegation_count_from_above(int $level): int {
  return match (clamp_int($level, 1, 5)) {
    2, 3 => 3,
    4 => 4,
    5 => 2,
    default => 0,
  };
}

function manager_seeded_unit(string $seed): float {
  return (crc32($seed) % 10000) / 10000.0;
}

function manager_shadow_team_strength(array $team): int {
  $base = (int)($team['baseRating'] ?? team_base_rating((string)($team['id'] ?? '')));
  return $base * 360 + (int)(crc32((string)($team['id'] ?? '') . '|shadow') % 900);
}

function manager_shadow_score(string $seed, int $homeStrength, int $awayStrength): array {
  $odds = manager_match_probabilities($homeStrength, $awayStrength);
  $roll = 1 + (int)floor(manager_seeded_unit($seed . '|result') * 100);
  if ($roll <= $odds['home']) $outcome = 'home';
  elseif ($roll <= $odds['home'] + $odds['draw']) $outcome = 'draw';
  else $outcome = 'away';
  $baseGoals = (int)floor(manager_seeded_unit($seed . '|goals') * 3);
  if ($outcome === 'draw') return [$baseGoals, $baseGoals];
  $loserGoals = (int)floor(manager_seeded_unit($seed . '|loser') * 2);
  $margin = 1 + (manager_seeded_unit($seed . '|margin') > 0.78 ? 1 : 0);
  $winnerGoals = min(5, $loserGoals + $margin);
  return $outcome === 'home' ? [$winnerGoals, $loserGoals] : [$loserGoals, $winnerGoals];
}

function manager_shadow_relegation_report(int $level, string $seasonId): array {
  $level = clamp_int($level, 1, 5);
  $count = manager_relegation_count_from_above($level);
  if ($count <= 0 || $level <= 1 || $seasonId === '') return ['teams' => [], 'table' => []];
  $seed = $seasonId . '|relegated|' . $level;
  $upperTeams = manager_division_teams($level - 1);
  $rows = [];
  foreach ($upperTeams as $team) {
    $id = (string)($team['id'] ?? '');
    if ($id === '') continue;
    $rows[$id] = [
      'team' => $team,
      'played' => 0,
      'won' => 0,
      'drawn' => 0,
      'lost' => 0,
      'gf' => 0,
      'ga' => 0,
      'gd' => 0,
      'points' => 0,
      'strength' => manager_shadow_team_strength($team),
    ];
  }
  $ids = array_keys($rows);
  for ($i = 0; $i < count($ids); $i++) {
    for ($j = $i + 1; $j < count($ids); $j++) {
      $home = $ids[$i];
      $away = $ids[$j];
      [$hg, $ag] = manager_shadow_score($seed . '|' . $home . '|' . $away, (int)$rows[$home]['strength'], (int)$rows[$away]['strength']);
      foreach ([[$home, $hg, $ag], [$away, $ag, $hg]] as $result) {
        [$id, $gf, $ga] = $result;
        $rows[$id]['played']++;
        $rows[$id]['gf'] += $gf;
        $rows[$id]['ga'] += $ga;
        $rows[$id]['gd'] = $rows[$id]['gf'] - $rows[$id]['ga'];
        if ($gf > $ga) {
          $rows[$id]['won']++;
          $rows[$id]['points'] += 3;
        } elseif ($gf === $ga) {
          $rows[$id]['drawn']++;
          $rows[$id]['points'] += 1;
        } else {
          $rows[$id]['lost']++;
        }
      }
    }
  }
  $table = array_values($rows);
  usort($table, fn($a, $b) => [$b['points'], $b['gd'], $b['gf'], $a['team']['name'] ?? ''] <=> [$a['points'], $a['gd'], $a['gf'], $b['team']['name'] ?? '']);
  $relegatedRows = array_slice(array_reverse($table), 0, $count);
  return [
    'fromDivision' => manager_division_name($level - 1),
    'teams' => array_map(fn($row) => $row['team'], $relegatedRows),
    'table' => $table,
  ];
}

function manager_relegated_from_above_teams(int $level, string $seasonId, ?array $previousSeason = null): array {
  if (is_array($previousSeason)) {
    $stored = $previousSeason['shadowRelegationFromAbove']['teams'] ?? $previousSeason['relegatedFromAbove'] ?? null;
    if (is_array($stored)) return array_values($stored);
  }
  return manager_shadow_relegation_report($level, $seasonId)['teams'];
}

function manager_division_teams(int $level): array {
  $divisionId = manager_division_id($level);
  foreach (training_divisions() as $division) {
    if (($division['id'] ?? '') !== $divisionId) continue;
    return array_map(function ($team) use ($divisionId) {
      return [
        'id' => $divisionId === 'national-league' ? (string)($team['sourceId'] ?? $team['id']) : (string)$team['id'],
        'sourceId' => (string)($team['sourceId'] ?? $team['id']),
        'divisionId' => $divisionId,
        'divisionName' => (string)($team['divisionName'] ?? manager_division_name(5)),
        'name' => (string)$team['name'],
        'shirt' => (string)($team['shirt'] ?? '#ffffff'),
        'trim' => (string)($team['trim'] ?? '#111111'),
        'flagPattern' => (string)($team['flagPattern'] ?? 'plain'),
        'flagAccent' => (string)($team['flagAccent'] ?? ($team['trim'] ?? '#111111')),
        'baseRating' => (int)($team['baseRating'] ?? team_base_rating((string)($team['id'] ?? ''))),
      ];
    }, $division['teams'] ?? []);
  }
  return teams();
}

function manager_all_division_teams(): array {
  $teams = [];
  for ($level = 1; $level <= 5; $level++) {
    foreach (manager_division_teams($level) as $team) {
      $team['managerDivisionLevel'] = $level;
      $team['managerDivisionName'] = manager_division_name($level);
      $teams[] = $team;
    }
  }
  return $teams;
}

function cup_cpu_manager_participant(array $team, int $divisionLevel): array {
  $participant = cpu_manager_participant($team, $divisionLevel);
  unset($participant['squad']);
  return $participant;
}

function manager_human_cup_participant(array &$state, string $name): ?array {
  if (!isset($state['users'][$name]) || !has_team($state['users'][$name])) return null;
  ensure_user_squad($state['users'][$name]);
  ensure_manager_finance($state['users'][$name]);
  $level = clamp_int((int)($state['users'][$name]['managerDivisionLevel'] ?? 5), 1, 5);
  return array_merge([
    'user' => $name,
    'managerType' => 'human',
    'divisionLevel' => $level,
    'divisionName' => manager_division_name($level),
  ], manager_human_participant_fields_from_user($name, $state['users'][$name]));
}

function manager_cup_participants_for_levels(array &$state, array $selected, array $levels, int $maxTeams = 0, string $managerGameId = '', ?array $previousSeason = null): array {
  $levels = array_values(array_unique(array_map(fn($level) => clamp_int((int)$level, 1, 5), $levels)));
  $participants = [];
  $managedTeamIds = manager_game_human_team_keys($state, $managerGameId, $selected, $previousSeason);
  foreach ($selected as $name) {
    $participant = manager_human_cup_participant($state, (string)$name);
    if ($participant === null || !in_array((int)$participant['divisionLevel'], $levels, true)) continue;
    $team = $participant['team'];
    manager_mark_team_ids($managedTeamIds, $team);
    $participants[] = $participant;
  }
  $cpu = [];
  foreach ($levels as $level) {
    foreach (manager_division_teams($level) as $team) {
      $teamId = (string)($team['id'] ?? '');
      $sourceId = (string)($team['sourceId'] ?? $teamId);
      if ($teamId === '' || isset($managedTeamIds[$teamId]) || ($sourceId !== '' && isset($managedTeamIds[$sourceId]))) continue;
      $team['managerDivisionLevel'] = $level;
      $team['managerDivisionName'] = manager_division_name($level);
      $cpu[] = cup_cpu_manager_participant($team, $level);
    }
  }
  usort($cpu, fn($a, $b) => ((int)($b['rating'] ?? 0)) <=> ((int)($a['rating'] ?? 0)));
  foreach ($cpu as $participant) {
    if ($maxTeams > 0 && count($participants) >= $maxTeams) break;
    $participants[] = $participant;
  }
  return $participants;
}

function manager_cup_participants(array &$state, array $selected): array {
  return manager_cup_participants_for_levels($state, $selected, [1, 2, 3, 4, 5]);
}

function manager_generated_team(string $id, string $name, int $baseRating, string $shirt, string $trim, string $divisionName): array {
  return [
    'id' => $id,
    'sourceId' => $id,
    'divisionId' => strtolower(preg_replace('/[^a-z0-9]+/', '-', $divisionName)),
    'divisionName' => $divisionName,
    'name' => $name,
    'shirt' => $shirt,
    'trim' => $trim,
    'flagPattern' => 'plain',
    'flagAccent' => $trim,
    'baseRating' => $baseRating,
  ];
}

function manager_academy_cup_participants(): array {
  $clubs = [
    ['arsenal-u21', 'ARSENAL U21', 76, '#d71920', '#ffffff'],
    ['aston-villa-u21', 'ASTON VILLA U21', 69, '#7a003c', '#95c5e8'],
    ['chelsea-u21', 'CHELSEA U21', 77, '#034694', '#ffffff'],
    ['everton-u21', 'EVERTON U21', 68, '#003399', '#ffffff'],
    ['leeds-united-u21', 'LEEDS UNITED U21', 67, '#ffffff', '#1d428a'],
    ['leicester-city-u21', 'LEICESTER CITY U21', 70, '#003090', '#fdbE11'],
    ['liverpool-u21', 'LIVERPOOL U21', 78, '#c8102e', '#f6eb61'],
    ['manchester-city-u21', 'MANCHESTER CITY U21', 80, '#6cabdd', '#1c2c5b'],
    ['manchester-united-u21', 'MANCHESTER UNITED U21', 78, '#da291c', '#fbe122'],
    ['newcastle-united-u21', 'NEWCASTLE UNITED U21', 70, '#111111', '#ffffff'],
    ['nottingham-forest-u21', 'NOTTINGHAM FOREST U21', 66, '#dd0000', '#ffffff'],
    ['southampton-u21', 'SOUTHAMPTON U21', 65, '#d71920', '#ffffff'],
    ['tottenham-hotspur-u21', 'TOTTENHAM HOTSPUR U21', 76, '#ffffff', '#132257'],
    ['west-ham-united-u21', 'WEST HAM UNITED U21', 68, '#7a263a', '#1bb1e7'],
    ['wolves-u21', 'WOLVES U21', 66, '#fdb913', '#231f20'],
    ['brighton-u21', 'BRIGHTON U21', 68, '#0057b8', '#ffffff'],
  ];
  return array_map(function ($club) {
    [$id, $name, $base, $shirt, $trim] = $club;
    return cup_cpu_manager_participant(manager_generated_team('academy:' . $id, $name, $base, $shirt, $trim, 'Premier League 2'), 1);
  }, $clubs);
}

function manager_lower_nonleague_cup_participants(): array {
  $clubs = [
    ['maidstone-united', 'MAIDSTONE UNITED', 48, '#ffcc00', '#111111'],
    ['torquay-united', 'TORQUAY UNITED', 47, '#f7d117', '#2257a7'],
    ['truro-city', 'TRURO CITY', 45, '#ffffff', '#111111'],
    ['worthing-step2', 'WORTHING', 46, '#e71939', '#ffffff'],
    ['scarborough-athletic', 'SCARBOROUGH ATHLETIC', 45, '#d71920', '#ffffff'],
    ['chester', 'CHESTER', 46, '#0057b8', '#ffffff'],
    ['brackley-town', 'BRACKLEY TOWN', 47, '#ff0000', '#ffffff'],
    ['kidderminster-step2', 'KIDDERMINSTER HARRIERS', 48, '#e60012', '#ffffff'],
    ['weymouth', 'WEYMOUTH', 43, '#7a003c', '#95c5e8'],
    ['billericay-town', 'BILLERICAY TOWN', 42, '#0033a0', '#ffffff'],
    ['haringey-borough', 'HARINGEY BOROUGH', 40, '#ffd100', '#00843d'],
    ['dulwich-hamlet', 'DULWICH HAMLET', 44, '#ec008c', '#00205b'],
  ];
  return array_map(function ($club) {
    [$id, $name, $base, $shirt, $trim] = $club;
    return cup_cpu_manager_participant(manager_generated_team('nonleague:' . $id, $name, $base, $shirt, $trim, 'National League System Step 2-4'), 5);
  }, $clubs);
}

function manager_euro_cup_participants(int $target, int $baseRating = 82): array {
  $clubs = [
    ['real-madrid', 'REAL MADRID', 94, '#ffffff', '#c8a64b'],
    ['barcelona', 'BARCELONA', 91, '#004d98', '#a50044'],
    ['bayern-munich', 'BAYERN MUNICH', 92, '#dc052d', '#ffffff'],
    ['paris-saint-germain', 'PARIS SAINT-GERMAIN', 90, '#004170', '#da291c'],
    ['inter', 'INTER', 89, '#0057b8', '#111111'],
    ['ac-milan', 'AC MILAN', 87, '#fb090b', '#111111'],
    ['juventus', 'JUVENTUS', 86, '#ffffff', '#111111'],
    ['atletico-madrid', 'ATLETICO MADRID', 86, '#cb3524', '#ffffff'],
    ['borussia-dortmund', 'BORUSSIA DORTMUND', 86, '#fde100', '#111111'],
    ['bayer-leverkusen', 'BAYER LEVERKUSEN', 85, '#e32221', '#111111'],
    ['benfica', 'BENFICA', 84, '#e83030', '#ffffff'],
    ['porto', 'PORTO', 83, '#0057b8', '#ffffff'],
    ['ajax', 'AJAX', 82, '#ffffff', '#d71920'],
    ['psv', 'PSV', 82, '#d71920', '#ffffff'],
    ['feyenoord', 'FEYENOORD', 81, '#d71920', '#111111'],
    ['sporting-cp', 'SPORTING CP', 82, '#00843d', '#ffffff'],
    ['napoli', 'NAPOLI', 85, '#12a0d7', '#ffffff'],
    ['roma', 'ROMA', 82, '#8e1f2f', '#f0bc42'],
    ['marseille', 'MARSEILLE', 81, '#ffffff', '#2faee0'],
    ['lyon', 'LYON', 79, '#ffffff', '#0055a4'],
    ['monaco', 'MONACO', 80, '#ffffff', '#d71920'],
    ['celtic', 'CELTIC', 78, '#00843d', '#ffffff'],
    ['rangers', 'RANGERS', 77, '#0033a0', '#ffffff'],
    ['galatasaray', 'GALATASARAY', 80, '#a90432', '#fdb913'],
    ['fenerbahce', 'FENERBAHCE', 79, '#002f6c', '#ffed00'],
    ['shakhtar-donetsk', 'SHAKHTAR DONETSK', 78, '#f58220', '#111111'],
    ['dinamo-zagreb', 'DINAMO ZAGREB', 76, '#005bac', '#ffffff'],
    ['anderlecht', 'ANDERLECHT', 75, '#5f259f', '#ffffff'],
    ['club-brugge', 'CLUB BRUGGE', 78, '#0057b8', '#111111'],
    ['salzburg', 'SALZBURG', 78, '#d71920', '#ffffff'],
    ['sparta-prague', 'SPARTA PRAGUE', 75, '#7a1f2b', '#f4d03f'],
    ['young-boys', 'YOUNG BOYS', 74, '#f7d117', '#111111'],
    ['olympiacos', 'OLYMPIACOS', 77, '#d71920', '#ffffff'],
    ['real-sociedad', 'REAL SOCIEDAD', 81, '#0057b8', '#ffffff'],
    ['villarreal', 'VILLARREAL', 80, '#fff200', '#0057b8'],
    ['sevilla', 'SEVILLA', 79, '#ffffff', '#d71920'],
    ['lazio', 'LAZIO', 80, '#87ceeb', '#ffffff'],
    ['atalanta', 'ATALANTA', 84, '#0057b8', '#111111'],
  ];
  $clubs = array_slice($clubs, 0, $target);
  return array_map(function ($club) use ($baseRating) {
    [$id, $name, $base, $shirt, $trim] = $club;
    return cup_cpu_manager_participant(manager_generated_team('euro:' . $id, $name, max($baseRating, (int)$base), $shirt, $trim, 'Europe'), 1);
  }, $clubs);
}

function manager_seeded_ids(array $ids, string $seed): array {
  $ids = array_values(array_unique(array_map('strval', $ids)));
  usort($ids, fn($a, $b) => crc32($seed . '|' . $a) <=> crc32($seed . '|' . $b));
  return $ids;
}

function manager_cup_round_label(string $cupName, int $roundNo, int $teamCount, string $stage = 'knockout', string $label = ''): string {
  if ($label !== '') return $cupName . ' ' . $label;
  if ($stage === 'group') return $cupName . ' GROUP ' . $roundNo;
  if ($stage === 'league') return $cupName . ' LEAGUE ' . $roundNo;
  if ($teamCount <= 2) return $cupName . ' FINAL';
  if ($teamCount <= 4) return $cupName . ' SF';
  if ($teamCount <= 8) return $cupName . ' QF';
  if ($teamCount <= 16) return $cupName . ' R16';
  if ($teamCount <= 32) return $cupName . ' R32';
  return $cupName . ' R' . $roundNo;
}

function manager_cup_round(array $ids, int $roundNo, string $seed, array $cup, string $label = '', array $forcedByes = []): array {
  $cupKey = (string)($cup['key'] ?? 'cup');
  $cupName = (string)($cup['name'] ?? 'Cup');
  $ids = manager_seeded_ids($ids, $seed . '|' . $cupKey . '|cup|' . $roundNo);
  $forcedByes = array_values(array_unique(array_map('strval', $forcedByes)));
  $count = count($ids);
  $target = 1;
  while ($target * 2 < ($count + count($forcedByes))) $target *= 2;
  $fixturesNeeded = max(1, $count - $target + count($forcedByes));
  $byeCount = max(0, $target - $fixturesNeeded - count($forcedByes));
  if ($count + count($forcedByes) <= 2) {
    $fixturesNeeded = 1;
    $byeCount = 0;
  }
  $byes = array_values(array_unique(array_merge($forcedByes, array_slice($ids, 0, $byeCount))));
  $play = array_slice($ids, $byeCount);
  $fixtures = [];
  for ($i = 0; $i + 1 < count($play) && count($fixtures) < $fixturesNeeded; $i += 2) {
    $fixtures[] = [
      'home' => $play[$i],
      'away' => $play[$i + 1],
      'status' => 'pending',
      'score' => null,
      'phase' => 'cup',
      'cupKey' => $cupKey,
      'cupName' => $cupName,
      'cupStage' => 'knockout',
      'cupRoundNo' => $roundNo,
    ];
  }
  return [
    'round' => manager_cup_round_label($cupName, $roundNo, $count + count($forcedByes), 'knockout', $label),
    'phase' => 'cup',
    'cupKey' => $cupKey,
    'cupName' => $cupName,
    'cupStage' => 'knockout',
    'cupRoundNo' => $roundNo,
    'cupByes' => $byes,
    'fixtures' => $fixtures,
  ];
}

function manager_insert_round_after(array &$rounds, array $round, int $afterIndex): void {
  $afterIndex = clamp_int($afterIndex, -1, max(-1, count($rounds) - 1));
  array_splice($rounds, $afterIndex + 1, 0, [$round]);
}

function manager_cup_table(array $ids): array {
  $table = [];
  foreach ($ids as $id) {
    $id = (string)$id;
    $table[$id] = ['user' => $id, 'played' => 0, 'won' => 0, 'drawn' => 0, 'lost' => 0, 'gf' => 0, 'ga' => 0, 'gd' => 0, 'points' => 0];
  }
  return $table;
}

function manager_apply_cup_table_result(array &$table, string $home, string $away, int $hg, int $ag): void {
  foreach ([[$home, $hg, $ag], [$away, $ag, $hg]] as $row) {
    [$user, $gf, $ga] = $row;
    if (!isset($table[$user])) continue;
    $table[$user]['played']++;
    $table[$user]['gf'] += $gf;
    $table[$user]['ga'] += $ga;
    $table[$user]['gd'] = $table[$user]['gf'] - $table[$user]['ga'];
    if ($gf > $ga) {
      $table[$user]['won']++;
      $table[$user]['points'] += 3;
    } elseif ($gf === $ga) {
      $table[$user]['drawn']++;
      $table[$user]['points'] += 1;
    } else {
      $table[$user]['lost']++;
    }
  }
}

function manager_rank_cup_table(array $table): array {
  $rows = array_values($table);
  usort($rows, fn($a, $b) => [$b['points'], $b['gd'], $b['gf'], $a['user']] <=> [$a['points'], $a['gd'], $a['gf'], $b['user']]);
  return $rows;
}

function manager_group_pair_rounds(array $ids): array {
  $ids = array_values(array_map('strval', $ids));
  if (count($ids) % 2 === 1) $ids[] = '__bye__';
  $n = count($ids);
  $rounds = [];
  $list = $ids;
  for ($r = 0; $r < $n - 1; $r++) {
    $fixtures = [];
    for ($i = 0; $i < $n / 2; $i++) {
      $home = $list[$i];
      $away = $list[$n - 1 - $i];
      if ($home !== '__bye__' && $away !== '__bye__') {
        if ($r % 2 === 1) [$home, $away] = [$away, $home];
        $fixtures[] = [$home, $away];
      }
    }
    $rounds[] = $fixtures;
    $fixed = $list[0];
    $rest = array_slice($list, 1);
    array_unshift($rest, array_pop($rest));
    $list = array_merge([$fixed], $rest);
  }
  return $rounds;
}

function manager_cup_group_stage_rounds(array $groups, array $cup): array {
  $cupKey = (string)$cup['key'];
  $cupName = (string)$cup['name'];
  $byRound = [];
  foreach ($groups as $groupId => $ids) {
    $pairRounds = (($cup['groupMode'] ?? '') === 'cross-academy') ? manager_cross_academy_group_rounds($ids, (int)($cup['groupRounds'] ?? 4)) : manager_group_pair_rounds($ids);
    foreach ($pairRounds as $roundNo => $pairs) {
      foreach ($pairs as $pair) {
        [$home, $away] = $pair;
        $byRound[$roundNo][] = [
          'home' => $home,
          'away' => $away,
          'status' => 'pending',
          'score' => null,
          'phase' => 'cup',
          'cupKey' => $cupKey,
          'cupName' => $cupName,
          'cupStage' => 'group',
          'cupGroup' => (string)$groupId,
          'cupRoundNo' => $roundNo + 1,
        ];
      }
    }
  }
  $rounds = [];
  foreach ($byRound as $i => $fixtures) {
    $roundNo = (int)$i + 1;
    $rounds[] = [
      'round' => manager_cup_round_label($cupName, $roundNo, 0, 'group'),
      'phase' => 'cup',
      'cupKey' => $cupKey,
      'cupName' => $cupName,
      'cupStage' => 'group',
      'cupRoundNo' => $roundNo,
      'fixtures' => $fixtures,
    ];
  }
  return $rounds;
}

function manager_cross_academy_group_rounds(array $ids, int $roundLimit): array {
  $academy = array_values(array_filter(array_map('strval', $ids), fn($id) => str_contains($id, 'academy:')));
  $senior = array_values(array_filter(array_map('strval', $ids), fn($id) => !str_contains($id, 'academy:')));
  if (!$academy || !$senior) return manager_group_pair_rounds($ids);
  $rounds = [];
  $roundLimit = max(1, min($roundLimit, count($academy)));
  for ($r = 0; $r < $roundLimit; $r++) {
    $fixtures = [];
    foreach ($senior as $i => $home) {
      $away = $academy[($i + $r) % count($academy)];
      $fixtures[] = [$home, $away];
    }
    $rounds[] = $fixtures;
  }
  return $rounds;
}

function manager_cup_league_phase_rounds(array $ids, array $cup, int $roundCount): array {
  $cupKey = (string)$cup['key'];
  $cupName = (string)$cup['name'];
  $ids = array_values(array_unique(array_map('strval', $ids)));
  $rounds = [];
  for ($roundNo = 1; $roundNo <= $roundCount; $roundNo++) {
    $list = manager_seeded_ids($ids, (string)($cup['seed'] ?? $cupKey) . '|league|' . $roundNo);
    if (count($list) % 2 === 1) $list[] = '__bye__';
    $fixtures = [];
    for ($i = 0; $i < count($list); $i += 2) {
      if (($list[$i] ?? '__bye__') === '__bye__' || ($list[$i + 1] ?? '__bye__') === '__bye__') continue;
      $fixtures[] = [
        'home' => $list[$i],
        'away' => $list[$i + 1],
        'status' => 'pending',
        'score' => null,
        'phase' => 'cup',
        'cupKey' => $cupKey,
        'cupName' => $cupName,
        'cupStage' => 'league',
        'cupGroup' => 'LEAGUE',
        'cupRoundNo' => $roundNo,
      ];
    }
    $rounds[] = [
      'round' => manager_cup_round_label($cupName, $roundNo, count($ids), 'league'),
      'phase' => 'cup',
      'cupKey' => $cupKey,
      'cupName' => $cupName,
      'cupStage' => 'league',
      'cupRoundNo' => $roundNo,
      'fixtures' => $fixtures,
    ];
  }
  return $rounds;
}

function manager_cup_definitions(): array {
  return [
    ['key' => 'community-shield', 'name' => 'Community Shield', 'format' => 'final', 'slot' => -1, 'rules' => 'Premier League champion vs FA Cup winner; if same club, Premier League runner-up is used.'],
    ['key' => 'fa-cup', 'name' => 'FA Cup', 'format' => 'knockout', 'levels' => [1, 2, 3, 4, 5], 'slot' => 5, 'rules' => 'Senior knockout cup. Winners stay in; losers are out. Lower qualifying rounds are abstracted.'],
    ['key' => 'efl-cup', 'name' => 'EFL Cup', 'format' => 'knockout', 'levels' => [1, 2, 3, 4], 'slot' => 2, 'rules' => 'Premier League and EFL clubs. Winners stay in; losers are out. European entry exemptions are abstracted into seeded byes.'],
    ['key' => 'efl-trophy', 'name' => 'EFL Trophy', 'format' => 'groups', 'levels' => [3, 4], 'slot' => 3, 'groupSize' => 4, 'advancePerGroup' => 2, 'extra' => 'academy', 'groupPenaltyBonus' => true, 'rules' => 'League One, League Two and invited academy teams. Top two per group advance to knockout rounds; drawn group games have a penalty bonus point.'],
    ['key' => 'fa-trophy', 'name' => 'FA Trophy', 'format' => 'knockout', 'levels' => [5], 'slot' => 8, 'extra' => 'nonleague', 'rules' => 'National League System Step 1-4 cup. Winners stay in; losers are out.'],
    ['key' => 'national-league-cup', 'name' => 'National League Cup', 'format' => 'groups', 'levels' => [5], 'slot' => 4, 'maxTeams' => 16, 'groupSize' => 8, 'groupRounds' => 4, 'groupMode' => 'cross-academy', 'advancePerGroup' => 2, 'extra' => 'academy', 'groupPenaltyBonus' => true, 'rules' => 'National League clubs plus PL2 academy teams. Groups contain senior and academy clubs; top two per group advance; drawn group games have a penalty bonus point.'],
    ['key' => 'uefa-champions-league', 'name' => 'UEFA Champions League', 'format' => 'league-phase', 'slot' => 6, 'targetTeams' => 36, 'leagueRounds' => 8, 'europe' => 'ucl', 'rules' => 'Premier League places 1-5 enter a 36-team league phase; top 24 stay in Europe, top 8 skip the knockout playoff.'],
    ['key' => 'uefa-europa-league', 'name' => 'UEFA Europa League', 'format' => 'league-phase', 'slot' => 7, 'targetTeams' => 36, 'leagueRounds' => 8, 'europe' => 'uel', 'rules' => 'Premier League places 6-7 enter a 36-team league phase; top 24 stay in Europe, top 8 skip the knockout playoff.'],
    ['key' => 'uefa-conference-league', 'name' => 'UEFA Conference League', 'format' => 'league-phase', 'slot' => 8, 'targetTeams' => 36, 'leagueRounds' => 6, 'europe' => 'uecl', 'rules' => 'Premier League place 8 enters the league phase; top 24 stay in Europe, top 8 skip the knockout playoff.'],
    ['key' => 'uefa-super-cup', 'name' => 'UEFA Super Cup', 'format' => 'final', 'slot' => 1, 'rules' => 'Champions League winner vs Europa League winner from the previous season.'],
  ];
}

function manager_participant_ids(array $participants): array {
  return array_values(array_map(fn($p) => (string)($p['user'] ?? ''), $participants));
}

function manager_cup_has_human(array $participants, array $selected): bool {
  $ids = array_flip(manager_participant_ids($participants));
  foreach ($selected as $name) {
    if (isset($ids[(string)$name])) return true;
  }
  return false;
}

function manager_participant_from_previous_or_current(array &$state, array $selected, ?array $previousSeason, string $id, int $fallbackLevel = 1): ?array {
  foreach ($selected as $name) {
    if ((string)$name !== $id) continue;
    return manager_human_cup_participant($state, (string)$name);
  }
  if ($previousSeason !== null) {
    $participant = manager_participant($previousSeason, $id);
    if ($participant !== null) return $participant;
  }
  for ($level = 1; $level <= 5; $level++) {
    foreach (manager_division_teams($level) as $team) {
      if (manager_cpu_id((string)$team['id']) === $id || (string)$team['id'] === $id) return cup_cpu_manager_participant($team, $level);
    }
  }
  return null;
}

function manager_static_premier_participants(): array {
  $teams = manager_division_teams(1);
  $participants = array_map(fn($team) => cup_cpu_manager_participant($team, 1), $teams);
  usort($participants, fn($a, $b) => ((int)($b['rating'] ?? 0)) <=> ((int)($a['rating'] ?? 0)));
  return $participants;
}

function manager_previous_premier_rows(?array $previousSeason): array {
  if ($previousSeason === null || (int)($previousSeason['divisionLevel'] ?? 0) !== 1) return [];
  return manager_ranked_rows($previousSeason);
}

function manager_community_shield_participants(array &$state, array $selected, ?array $previousSeason): array {
  $premier = manager_static_premier_participants();
  $champion = $premier[0] ?? null;
  $runnerUp = $premier[1] ?? null;
  $rows = manager_previous_premier_rows($previousSeason);
  if (!empty($rows[0])) $champion = manager_participant_from_previous_or_current($state, $selected, $previousSeason, (string)$rows[0]['user'], 1) ?? $champion;
  if (!empty($rows[1])) $runnerUp = manager_participant_from_previous_or_current($state, $selected, $previousSeason, (string)$rows[1]['user'], 1) ?? $runnerUp;
  $faWinnerId = (string)($previousSeason['cups']['fa-cup']['winner'] ?? $previousSeason['cup']['winner'] ?? '');
  $faWinner = $faWinnerId !== '' ? manager_participant_from_previous_or_current($state, $selected, $previousSeason, $faWinnerId, 1) : null;
  $second = $faWinner;
  if ($champion !== null && $second !== null && (string)$champion['user'] === (string)$second['user']) $second = $runnerUp;
  $participants = array_values(array_filter([$champion, $second]));
  return manager_cup_has_human($participants, $selected) ? $participants : [];
}

function manager_uefa_super_cup_participants(array &$state, array $selected, ?array $previousSeason): array {
  if ($previousSeason === null) return [];
  $ucl = (string)($previousSeason['cups']['uefa-champions-league']['winner'] ?? '');
  $uel = (string)($previousSeason['cups']['uefa-europa-league']['winner'] ?? '');
  if ($ucl === '' || $uel === '' || $ucl === $uel) return [];
  $participants = array_values(array_filter([
    manager_participant_from_previous_or_current($state, $selected, $previousSeason, $ucl, 1),
    manager_participant_from_previous_or_current($state, $selected, $previousSeason, $uel, 1),
  ]));
  return manager_cup_has_human($participants, $selected) ? $participants : [];
}

function manager_european_english_participants(array &$state, array $selected, ?array $previousSeason, string $type): array {
  $rows = manager_previous_premier_rows($previousSeason);
  $premier = manager_static_premier_participants();
  $ids = [];
  if ($rows) {
    $rankIds = array_map(fn($row) => (string)($row['user'] ?? ''), $rows);
    if ($type === 'ucl') $ids = array_slice($rankIds, 0, 5);
    elseif ($type === 'uel') $ids = array_slice($rankIds, 5, 2);
    else $ids = array_slice($rankIds, 7, 1);
  } else {
    $staticIds = manager_participant_ids($premier);
    if ($type === 'ucl') $ids = array_slice($staticIds, 0, 5);
    elseif ($type === 'uel') $ids = array_slice($staticIds, 5, 2);
    else $ids = array_slice($staticIds, 7, 1);
  }
  $participants = [];
  foreach (array_values(array_unique(array_filter($ids))) as $id) {
    $participant = manager_participant_from_previous_or_current($state, $selected, $previousSeason, $id, 1);
    if ($participant !== null) $participants[] = $participant;
  }
  return manager_cup_has_human($participants, $selected) ? $participants : [];
}

function manager_cup_participants_for_definition(array &$state, array $selected, array $cup, ?array $previousSeason, string $managerGameId = ''): array {
  $format = (string)($cup['format'] ?? 'knockout');
  if ($format === 'final' && ($cup['key'] ?? '') === 'community-shield') return manager_community_shield_participants($state, $selected, $previousSeason);
  if ($format === 'final' && ($cup['key'] ?? '') === 'uefa-super-cup') return manager_uefa_super_cup_participants($state, $selected, $previousSeason);
  if (!empty($cup['europe'])) {
    $english = manager_european_english_participants($state, $selected, $previousSeason, (string)$cup['europe']);
    if (!$english) return [];
    $target = (int)($cup['targetTeams'] ?? 36);
    return array_values(array_merge($english, manager_euro_cup_participants(max(0, $target - count($english)), (string)$cup['europe'] === 'ucl' ? 82 : 74)));
  }
  $participants = manager_cup_participants_for_levels($state, $selected, $cup['levels'] ?? [], (int)($cup['maxTeams'] ?? 0), $managerGameId, $previousSeason);
  if (!manager_cup_has_human($participants, $selected)) return [];
  if (($cup['extra'] ?? '') === 'academy') $participants = array_values(array_merge($participants, manager_academy_cup_participants()));
  if (($cup['extra'] ?? '') === 'nonleague') $participants = array_values(array_merge($participants, manager_lower_nonleague_cup_participants()));
  return $participants;
}

function manager_sync_primary_cup(array &$season): void {
  $cups = $season['cups'] ?? [];
  $season['cup'] = is_array($cups) && $cups ? array_values($cups)[0] : null;
}

function manager_add_cup_participants(array &$season, array $participants): void {
  $map = [];
  foreach (($season['cupParticipants'] ?? []) as $participant) {
    $map[(string)($participant['user'] ?? '')] = $participant;
  }
  foreach ($participants as $participant) {
    $id = (string)($participant['user'] ?? '');
    if ($id !== '') $map[$id] = $participant;
  }
  $season['cupParticipants'] = array_values($map);
}

function manager_start_cup_competition(array &$season, array $cup, array $participants): void {
  $ids = manager_participant_ids($participants);
  if (count($ids) < 2) return;
  $key = (string)$cup['key'];
  $name = (string)$cup['name'];
  if (!isset($season['cups']) || !is_array($season['cups'])) $season['cups'] = [];
  $season['cups'][$key] = [
    'key' => $key,
    'name' => $name,
    'format' => (string)($cup['format'] ?? 'knockout'),
    'version' => MANAGER_CUP_VERSION,
    'status' => 'active',
    'roundNo' => 1,
    'participantCount' => count($ids),
    'winner' => null,
    'runnerUp' => null,
    'rules' => (string)($cup['rules'] ?? ''),
  ];
  if (!empty($cup['groupPenaltyBonus'])) $season['cups'][$key]['groupPenaltyBonus'] = true;
  $seed = (string)($season['id'] ?? 'cup') . '|' . $key;
  $slot = (int)($cup['slot'] ?? 5);
  $format = (string)($cup['format'] ?? 'knockout');
  manager_add_cup_participants($season, $participants);
  if ($format === 'final') {
    $round = manager_cup_round($ids, 1, $seed, $cup, 'FINAL');
    manager_insert_round_after($season['rounds'], $round, $slot);
  } elseif ($format === 'groups') {
    $groupSize = max(2, (int)($cup['groupSize'] ?? 4));
    $ids = manager_seeded_ids($ids, $seed . '|groups');
    $groups = [];
    $letter = ord('A');
    if (($cup['groupMode'] ?? '') === 'cross-academy') {
      $academy = manager_seeded_ids(array_values(array_filter($ids, fn($id) => str_contains((string)$id, 'academy:'))), $seed . '|academy');
      $senior = manager_seeded_ids(array_values(array_filter($ids, fn($id) => !str_contains((string)$id, 'academy:'))), $seed . '|senior');
      $half = max(1, (int)floor($groupSize / 2));
      $chunks = [];
      $groupCount = max(1, (int)ceil(max(count($senior), count($academy)) / $half));
      for ($i = 0; $i < $groupCount; $i++) {
        $chunks[] = array_values(array_merge(array_slice($senior, $i * $half, $half), array_slice($academy, $i * $half, $half)));
      }
    } else {
      $chunks = array_chunk($ids, $groupSize);
    }
    foreach ($chunks as $chunk) {
      if (count($chunk) < 2) continue;
      $groupId = chr($letter++);
      $groups[$groupId] = $chunk;
      $season['cups'][$key]['groups'][$groupId] = ['teams' => $chunk, 'table' => manager_cup_table($chunk)];
    }
    $season['cups'][$key]['advancePerGroup'] = max(1, (int)($cup['advancePerGroup'] ?? 2));
    foreach (manager_cup_group_stage_rounds($groups, $cup) as $i => $round) {
      manager_insert_round_after($season['rounds'], $round, $slot + ($i * 5));
    }
  } elseif ($format === 'league-phase') {
    $season['cups'][$key]['leagueTable'] = manager_cup_table($ids);
    $season['cups'][$key]['leagueRounds'] = (int)($cup['leagueRounds'] ?? 8);
    foreach (manager_cup_league_phase_rounds($ids, $cup + ['seed' => $seed], (int)$season['cups'][$key]['leagueRounds']) as $i => $round) {
      manager_insert_round_after($season['rounds'], $round, $slot + ($i * 4));
    }
  } else {
    $round = manager_cup_round($ids, 1, $seed, $cup);
    manager_insert_round_after($season['rounds'], $round, $slot);
  }
  manager_sync_primary_cup($season);
}

function manager_start_all_cups(array &$state, array &$season, array $selected, ?array $previousSeason = null): void {
  $season['cups'] = [];
  $season['cupParticipants'] = [];
  $managerGameId = manager_game_id($season);
  foreach (manager_cup_definitions() as $cup) {
    $participants = manager_cup_participants_for_definition($state, $selected, $cup, $previousSeason, $managerGameId);
    if (count($participants) < 2) continue;
    manager_start_cup_competition($season, $cup, $participants);
  }
  manager_sync_primary_cup($season);
}

function manager_start_english_cup(array &$season, array $cupParticipants): void {
  manager_start_cup_competition($season, ['key' => 'fa-cup', 'name' => 'FA Cup', 'format' => 'knockout', 'slot' => 5, 'rules' => 'Senior knockout cup. Winners stay in; losers are out.'], $cupParticipants);
}

function manager_cup_round_finished(array $round): bool {
  foreach (($round['fixtures'] ?? []) as $fixture) {
    if (($fixture['status'] ?? '') !== 'played') return false;
  }
  return true;
}

function manager_cup_has_unplayed_stage(array $season, string $cupKey, string $stage): bool {
  foreach (($season['rounds'] ?? []) as $round) {
    if (($round['phase'] ?? '') !== 'cup' || (string)($round['cupKey'] ?? '') !== $cupKey || (string)($round['cupStage'] ?? '') !== $stage) continue;
    foreach (($round['fixtures'] ?? []) as $fixture) {
      if (in_array(($fixture['status'] ?? ''), ['pending', 'watching'], true)) return true;
    }
  }
  return false;
}

function manager_cup_has_unplayed_matches(array $season, string $cupKey): bool {
  foreach (($season['rounds'] ?? []) as $round) {
    if (($round['phase'] ?? '') !== 'cup' || (string)($round['cupKey'] ?? '') !== $cupKey) continue;
    foreach (($round['fixtures'] ?? []) as $fixture) {
      if (in_array(($fixture['status'] ?? ''), ['pending', 'watching', 'simulating'], true)) return true;
    }
  }
  return false;
}

function manager_public_cups(array $season, ?array $state = null): array {
  $cups = [];
  foreach (($season['cups'] ?? []) as $key => $cup) {
    if (!is_array($cup)) continue;
    $cupKey = (string)($cup['key'] ?? $key);
    $ongoing = (string)($cup['status'] ?? '') !== 'finished' || manager_cup_has_unplayed_matches($season, $cupKey);
    if ($state !== null) {
      foreach (manager_related_current_season_ids($state, $season) as $relatedId) {
        $related = $state['managerSeasons'][$relatedId] ?? null;
        if (!is_array($related) || (string)($related['id'] ?? $relatedId) === (string)($season['id'] ?? '')) continue;
        $relatedCup = $related['cups'][$cupKey] ?? null;
        if (!is_array($relatedCup)) continue;
        if ((string)($relatedCup['status'] ?? '') !== 'finished' || manager_cup_has_unplayed_matches($related, $cupKey)) {
          $ongoing = true;
          break;
        }
      }
    }
    $public = $cup;
    $public['key'] = $cupKey;
    $public['localStatus'] = (string)($cup['status'] ?? '');
    $public['ongoing'] = $ongoing;
    if ($ongoing) {
      $public['status'] = 'ongoing';
      $public['winner'] = null;
      $public['runnerUp'] = null;
    }
    $cups[] = $public;
  }
  return $cups;
}

function manager_cup_apply_result(array &$season, string $cupKey, string $stage, string $group, string $home, string $away, int $hg, int $ag, string $bonusWinner = ''): void {
  if (empty($season['cups'][$cupKey])) return;
  if ($stage === 'group') {
    if (!isset($season['cups'][$cupKey]['groups'][$group]['table'])) return;
    manager_apply_cup_table_result($season['cups'][$cupKey]['groups'][$group]['table'], $home, $away, $hg, $ag);
    if ($hg === $ag && $bonusWinner !== '' && !empty($season['cups'][$cupKey]['groupPenaltyBonus']) && isset($season['cups'][$cupKey]['groups'][$group]['table'][$bonusWinner])) {
      $season['cups'][$cupKey]['groups'][$group]['table'][$bonusWinner]['points']++;
    }
  } elseif ($stage === 'league') {
    if (!isset($season['cups'][$cupKey]['leagueTable'])) return;
    manager_apply_cup_table_result($season['cups'][$cupKey]['leagueTable'], $home, $away, $hg, $ag);
  }
}

function manager_cup_add_next_knockout(array &$season, string $cupKey, array $ids, int $nextNo, string $label = '', array $byes = []): void {
  if (empty($season['cups'][$cupKey])) return;
  $cup = $season['cups'][$cupKey];
  $round = manager_cup_round($ids, $nextNo, (string)($season['id'] ?? 'cup') . '|' . $cupKey, $cup, $label, $byes);
  $season['cups'][$cupKey]['roundNo'] = $nextNo;
  $season['cups'][$cupKey]['status'] = 'knockout';
  manager_insert_round_after($season['rounds'], $round, min(count($season['rounds']) - 1, manager_round_seq($season)));
  manager_sync_primary_cup($season);
}

function manager_cup_advance_from_groups(array &$season, string $cupKey): void {
  $cup =& $season['cups'][$cupKey];
  $advance = max(1, (int)($cup['advancePerGroup'] ?? 2));
  $qualified = [];
  foreach (($cup['groups'] ?? []) as $group) {
    foreach (array_slice(manager_rank_cup_table($group['table'] ?? []), 0, $advance) as $row) {
      $qualified[] = (string)$row['user'];
    }
  }
  unset($cup);
  manager_cup_add_next_knockout($season, $cupKey, $qualified, 1, '');
}

function manager_cup_advance_from_league(array &$season, string $cupKey): void {
  $rows = manager_rank_cup_table($season['cups'][$cupKey]['leagueTable'] ?? []);
  $top8 = array_map(fn($row) => (string)$row['user'], array_slice($rows, 0, 8));
  $playoff = array_map(fn($row) => (string)$row['user'], array_slice($rows, 8, 16));
  manager_cup_add_next_knockout($season, $cupKey, $playoff, 1, 'KO PLAYOFF', $top8);
}

function advance_manager_cup(array &$season, int $roundIndex): void {
  if (empty($season['rounds'][$roundIndex]) || (($season['rounds'][$roundIndex]['phase'] ?? '') !== 'cup')) return;
  $round = $season['rounds'][$roundIndex];
  if (!manager_cup_round_finished($round)) return;
  $cupKey = (string)($round['cupKey'] ?? 'fa-cup');
  $stage = (string)($round['cupStage'] ?? 'knockout');
  if (empty($season['cups'][$cupKey])) return;
  if ($stage === 'group') {
    if (!manager_cup_has_unplayed_stage($season, $cupKey, 'group')) manager_cup_advance_from_groups($season, $cupKey);
    manager_sync_primary_cup($season);
    return;
  }
  if ($stage === 'league') {
    if (!manager_cup_has_unplayed_stage($season, $cupKey, 'league')) manager_cup_advance_from_league($season, $cupKey);
    manager_sync_primary_cup($season);
    return;
  }
  $alive = array_values(array_map('strval', $round['cupByes'] ?? []));
  foreach (($round['fixtures'] ?? []) as $fixture) {
    if (!empty($fixture['winner'])) $alive[] = (string)$fixture['winner'];
  }
  $alive = array_values(array_unique(array_filter($alive)));
  if (count($alive) <= 1) {
    $winner = $alive[0] ?? '';
    $runnerUp = '';
    if (count($round['fixtures'] ?? []) === 1) {
      $fixture = $round['fixtures'][0];
      $runnerUp = (string)(($fixture['winner'] ?? '') === ($fixture['home'] ?? '') ? ($fixture['away'] ?? '') : ($fixture['home'] ?? ''));
    }
    $season['cups'][$cupKey]['status'] = 'finished';
    $season['cups'][$cupKey]['winner'] = $winner;
    $season['cups'][$cupKey]['runnerUp'] = $runnerUp;
    manager_sync_primary_cup($season);
    return;
  }
  $nextNo = (int)($round['cupRoundNo'] ?? 1) + 1;
  $season['cups'][$cupKey]['roundNo'] = $nextNo;
  $next = manager_cup_round($alive, $nextNo, (string)($season['id'] ?? 'cup') . '|' . $cupKey, $season['cups'][$cupKey]);
  manager_insert_round_after($season['rounds'], $next, min(count($season['rounds']) - 1, $roundIndex + 5));
  manager_sync_primary_cup($season);
}
