<?php
declare(strict_types=1);

function team_trophy_key(array $team): string {
  return strtolower((string)($team['sourceId'] ?? $team['id'] ?? $team['name'] ?? 'team'));
}

function team_trophy_visual(array $trophy): string {
  $kind = strtolower((string)($trophy['kind'] ?? ''));
  $visual = strtolower((string)($trophy['visual'] ?? ''));
  $source = trim($visual . ' ' . $kind);
  if (str_contains($source, 'runner') || str_contains($source, 'silver')) return 'medal_silver';
  if (str_contains($source, 'top_scorer') || str_contains($source, 'golden')) return 'medal';
  if (str_contains($source, 'promotion') || str_contains($source, 'medal')) return 'medal';
  return 'cup';
}

function add_team_trophy(array &$state, array $team, array $trophy): void {
  $key = team_trophy_key($team);
  if ($key === '') return;
  if (!isset($state['teamTrophies']) || !is_array($state['teamTrophies'])) $state['teamTrophies'] = [];
  if (!isset($state['teamTrophies'][$key]) || !is_array($state['teamTrophies'][$key])) $state['teamTrophies'][$key] = [];
  $trophy['visual'] = team_trophy_visual($trophy);
  $trophy['team'] = ['id' => $team['id'] ?? '', 'sourceId' => $team['sourceId'] ?? ($team['id'] ?? ''), 'name' => $team['name'] ?? ''];
  $fingerprint = implode('|', [(string)($trophy['seasonId'] ?? ''), (string)($trophy['kind'] ?? ''), (string)($trophy['title'] ?? ''), (string)($trophy['seasonYear'] ?? '')]);
  foreach ($state['teamTrophies'][$key] as $existing) {
    $existingFingerprint = implode('|', [(string)($existing['seasonId'] ?? ''), (string)($existing['kind'] ?? ''), (string)($existing['title'] ?? ''), (string)($existing['seasonYear'] ?? '')]);
    if ($existingFingerprint === $fingerprint) return;
  }
  $state['teamTrophies'][$key][] = $trophy;
}

function manager_trophy_season_ids(array $state, array $season): array {
  $ids = [];
  $current = $season;
  for ($guard = 0; $guard < 20 && is_array($current); $guard++) {
    $id = (string)($current['id'] ?? '');
    if ($id !== '') $ids[$id] = true;
    $previousId = (string)($current['continuedFrom'] ?? '');
    if ($previousId === '' || isset($ids[$previousId])) break;
    if (!isset($state['managerSeasons'][$previousId]) || !is_array($state['managerSeasons'][$previousId])) {
      $ids[$previousId] = true;
      break;
    }
    $current = $state['managerSeasons'][$previousId];
  }
  return $ids;
}

function public_team_trophies(array $state, array $team, ?array $seasonIds = null): array {
  $items = array_values($state['teamTrophies'][team_trophy_key($team)] ?? []);
  $items = array_values(array_filter($items, 'is_array'));
  if ($seasonIds !== null) {
    $items = array_values(array_filter($items, function ($item) use ($seasonIds) {
      $seasonId = (string)($item['seasonId'] ?? '');
      return $seasonId !== '' && isset($seasonIds[$seasonId]);
    }));
  }
  $items = array_map(function ($item) {
    $item['visual'] = team_trophy_visual($item);
    return $item;
  }, $items);
  usort($items, fn($a, $b) => [(int)($b['seasonYear'] ?? 0), (string)($b['title'] ?? '')] <=> [(int)($a['seasonYear'] ?? 0), (string)($a['title'] ?? '')]);
  return $items;
}

function manager_human_player_report(array $season): array {
  $humans = array_flip(array_map('strval', $season['humanManagers'] ?? []));
  if (!$humans) return ['workhorses' => [], 'best' => []];
  $players = [];
  foreach (($season['matchReports'] ?? []) as $report) {
    if (!is_array($report)) continue;
    $sideUsers = ['home' => (string)($report['home'] ?? ''), 'away' => (string)($report['away'] ?? '')];
    foreach (($report['players'] ?? []) as $player) {
      if (!is_array($player)) continue;
      $side = (string)($player['side'] ?? '');
      $user = $sideUsers[$side] ?? '';
      if ($user === '' || !isset($humans[$user])) continue;
      $name = (string)($player['name'] ?? 'Player');
      $key = $user . '|' . strtolower($name);
      if (!isset($players[$key])) {
        $players[$key] = [
          'user' => $user,
          'name' => $name,
          'teamName' => (string)($player['teamName'] ?? $user),
          'role' => (string)($player['role'] ?? ''),
          'reports' => 0,
          'distanceMeters' => 0,
          'impactTotal' => 0.0,
          'touches' => 0,
          'shots' => 0,
          'tackles' => 0,
          'complaints' => 0,
        ];
      }
      $players[$key]['reports']++;
      $players[$key]['distanceMeters'] += (int)round((float)($player['distanceMeters'] ?? 0));
      $players[$key]['impactTotal'] += (float)($player['impact'] ?? 0);
      $players[$key]['touches'] += (int)($player['touches'] ?? 0);
      $players[$key]['shots'] += (int)($player['shots'] ?? 0);
      $players[$key]['tackles'] += (int)($player['tackles'] ?? 0);
      $players[$key]['complaints'] += (int)($player['complaints'] ?? 0);
    }
  }
  $rows = array_values(array_map(function ($row) {
    $reports = max(1, (int)($row['reports'] ?? 0));
    $row['avgImpact'] = round(((float)($row['impactTotal'] ?? 0)) / $reports, 1);
    $row['impactTotal'] = round((float)($row['impactTotal'] ?? 0), 1);
    return $row;
  }, $players));
  $workhorses = $rows;
  usort($workhorses, fn($a, $b) => [(int)($b['distanceMeters'] ?? 0), (float)($b['impactTotal'] ?? 0)] <=> [(int)($a['distanceMeters'] ?? 0), (float)($a['impactTotal'] ?? 0)]);
  $best = $rows;
  usort($best, fn($a, $b) => [(float)($b['avgImpact'] ?? 0), (float)($b['impactTotal'] ?? 0), (int)($b['reports'] ?? 0)] <=> [(float)($a['avgImpact'] ?? 0), (float)($a['impactTotal'] ?? 0), (int)($a['reports'] ?? 0)]);
  return [
    'workhorses' => array_slice($workhorses, 0, 5),
    'best' => array_slice($best, 0, 5),
  ];
}

function manager_praise_player_key(string $user, string $playerId, string $name): string {
  $base = $playerId !== '' ? 'id:' . $playerId : 'name:' . strtolower(trim($name));
  return $user . '|' . $base;
}

function manager_report_player_same(array $a, ?array $b): bool {
  if (!$b) return false;
  if ((string)($a['side'] ?? '') !== (string)($b['side'] ?? '')) return false;
  $aid = (string)($a['id'] ?? '');
  $bid = (string)($b['id'] ?? '');
  if ($aid !== '' && $bid !== '') return $aid === $bid;
  return strcasecmp((string)($a['name'] ?? ''), (string)($b['name'] ?? '')) === 0;
}

function manager_report_positive_behavior_points(string $behavior): int {
  return match (strtoupper($behavior)) {
    'KEPT TEAM IN IT' => 2,
    'SHOT A LOT', 'WORKED HARD', 'PASSED WELL' => 1,
    default => 0,
  };
}

function manager_report_praise_entries(array $season, array $report): array {
  $humans = array_flip(array_map('strval', $season['humanManagers'] ?? []));
  if (!$humans) return [];
  $sideUsers = ['home' => (string)($report['home'] ?? ''), 'away' => (string)($report['away'] ?? '')];
  $players = array_values(array_filter($report['players'] ?? [], fn($row) => is_array($row)));
  if (!$players) return [];
  $humanPlayers = array_values(array_filter($players, function ($row) use ($sideUsers, $humans) {
    $side = (string)($row['side'] ?? '');
    $user = $sideUsers[$side] ?? '';
    return $user !== '' && isset($humans[$user]);
  }));
  if (!$humanPlayers) return [];
  $sideBest = [];
  foreach (['home', 'away'] as $side) {
    $list = array_values(array_filter($humanPlayers, fn($row) => (string)($row['side'] ?? '') === $side && empty($row['terrible'])));
    usort($list, fn($a, $b) => (float)($b['impact'] ?? 0) <=> (float)($a['impact'] ?? 0));
    if (!empty($list[0]) && (float)($list[0]['impact'] ?? 0) >= 5.0) $sideBest[$side] = $list[0];
  }
  $overall = $humanPlayers;
  usort($overall, fn($a, $b) => (float)($b['impact'] ?? 0) <=> (float)($a['impact'] ?? 0));
  $matchBest = !empty($overall[0]) && (float)($overall[0]['impact'] ?? 0) >= 8.0 ? $overall[0] : null;
  $highlights = is_array($report['highlights'] ?? null) ? $report['highlights'] : [];
  $entries = [];
  foreach ($humanPlayers as $player) {
    if (!is_array($player) || !empty($player['terrible'])) continue;
    $side = (string)($player['side'] ?? '');
    $user = $sideUsers[$side] ?? '';
    if ($user === '' || !isset($humans[$user])) continue;
    $points = 0;
    $reasons = [];
    $behavior = (string)($player['behavior'] ?? 'OK');
    $behaviorPoints = manager_report_positive_behavior_points($behavior);
    if ($behaviorPoints > 0) {
      $points += $behaviorPoints;
      $reasons[] = $behavior;
    }
    $impact = (float)($player['impact'] ?? 0);
    if ($impact >= 10.0) {
      $points += 2;
      $reasons[] = 'BIG IMPACT';
    } elseif ($impact >= 7.0) {
      $points += 1;
      $reasons[] = 'GOOD IMPACT';
    }
    if (manager_report_player_same($player, $sideBest[$side] ?? null)) {
      $points += 1;
      $reasons[] = 'TEAM BEST';
    }
    if (manager_report_player_same($player, $matchBest)) {
      $points += 1;
      $reasons[] = 'MATCH STAR';
    }
    foreach (['mostBall' => 'MOST BALL', 'workhorse' => 'WORKHORSE'] as $key => $label) {
      if (manager_report_player_same($player, is_array($highlights[$key] ?? null) ? $highlights[$key] : null)) {
        $points += 1;
        $reasons[] = $label;
      }
    }
    $points = min(4, $points);
    if ($points <= 0) continue;
    $playerId = (string)($player['id'] ?? '');
    $name = (string)($player['name'] ?? 'Player');
    $entries[] = [
      'key' => manager_praise_player_key($user, $playerId, $name),
      'user' => $user,
      'playerId' => $playerId,
      'name' => $name,
      'teamName' => (string)($player['teamName'] ?? $user),
      'role' => (string)($player['role'] ?? ''),
      'points' => $points,
      'impact' => $impact,
      'reasons' => array_values(array_unique($reasons)),
    ];
  }
  return $entries;
}

function manager_merge_player_praise(array &$rows, array $entry): void {
  $key = (string)($entry['key'] ?? '');
  if ($key === '') $key = manager_praise_player_key((string)($entry['user'] ?? ''), (string)($entry['playerId'] ?? ''), (string)($entry['name'] ?? 'Player'));
  if (!isset($rows[$key]) || !is_array($rows[$key])) {
    $rows[$key] = [
      'key' => $key,
      'user' => (string)($entry['user'] ?? ''),
      'playerId' => (string)($entry['playerId'] ?? ''),
      'name' => (string)($entry['name'] ?? 'Player'),
      'teamName' => (string)($entry['teamName'] ?? $entry['user'] ?? ''),
      'role' => (string)($entry['role'] ?? ''),
      'praisePoints' => 0,
      'praiseMatches' => 0,
      'impactTotal' => 0.0,
      'bestImpact' => -999.0,
      'reasons' => [],
    ];
  }
  if ((string)($rows[$key]['playerId'] ?? '') === '' && (string)($entry['playerId'] ?? '') !== '') $rows[$key]['playerId'] = (string)$entry['playerId'];
  $rows[$key]['praisePoints'] = (int)($rows[$key]['praisePoints'] ?? 0) + (int)($entry['points'] ?? 0);
  $rows[$key]['praiseMatches'] = (int)($rows[$key]['praiseMatches'] ?? 0) + 1;
  $rows[$key]['impactTotal'] = (float)($rows[$key]['impactTotal'] ?? 0) + (float)($entry['impact'] ?? 0);
  $rows[$key]['bestImpact'] = max((float)($rows[$key]['bestImpact'] ?? -999), (float)($entry['impact'] ?? 0));
  foreach (($entry['reasons'] ?? []) as $reason) {
    $reason = (string)$reason;
    if ($reason !== '') $rows[$key]['reasons'][$reason] = (int)($rows[$key]['reasons'][$reason] ?? 0) + 1;
  }
}

function manager_record_match_report_praise(array &$season, array $report): void {
  $reportId = (string)($report['id'] ?? '');
  if ($reportId !== '' && !empty($season['playerPraiseReportIds'][$reportId])) return;
  if (!isset($season['playerPraise']) || !is_array($season['playerPraise'])) $season['playerPraise'] = [];
  foreach (manager_report_praise_entries($season, $report) as $entry) manager_merge_player_praise($season['playerPraise'], $entry);
  if ($reportId !== '') {
    if (!isset($season['playerPraiseReportIds']) || !is_array($season['playerPraiseReportIds'])) $season['playerPraiseReportIds'] = [];
    $season['playerPraiseReportIds'][$reportId] = true;
  }
}

function manager_season_player_praise_rows(array $season): array {
  $rows = is_array($season['playerPraise'] ?? null) ? $season['playerPraise'] : [];
  $recorded = is_array($season['playerPraiseReportIds'] ?? null) ? $season['playerPraiseReportIds'] : [];
  foreach (($season['matchReports'] ?? []) as $report) {
    if (!is_array($report)) continue;
    $reportId = (string)($report['id'] ?? '');
    if ($reportId !== '' && !empty($recorded[$reportId])) continue;
    foreach (manager_report_praise_entries($season, $report) as $entry) manager_merge_player_praise($rows, $entry);
  }
  $out = array_values(array_map(function ($row) {
    $matches = max(1, (int)($row['praiseMatches'] ?? 0));
    $row['avgImpact'] = round((float)($row['impactTotal'] ?? 0) / $matches, 1);
    $row['impactTotal'] = round((float)($row['impactTotal'] ?? 0), 1);
    $reasons = $row['reasons'] ?? [];
    if (is_array($reasons)) {
      arsort($reasons);
      $row['topReasons'] = array_slice(array_keys($reasons), 0, 4);
    } else {
      $row['topReasons'] = [];
    }
    return $row;
  }, $rows));
  usort($out, fn($a, $b) => [(int)($b['praisePoints'] ?? 0), (float)($b['avgImpact'] ?? 0), (int)($b['praiseMatches'] ?? 0)] <=> [(int)($a['praisePoints'] ?? 0), (float)($a['avgImpact'] ?? 0), (int)($a['praiseMatches'] ?? 0)]);
  return $out;
}

function manager_season_report(array $season, array $names, ?array $state = null): array {
  $level = clamp_int((int)($season['divisionLevel'] ?? 5), 1, 5);
  $toDivision = $level > 1 ? manager_division_name($level - 1) : '';
  $promoted = [];
  $seen = [];
  $directRaw = $season['promotion']['direct'] ?? [];
  $direct = is_array($directRaw) ? array_map('strval', $directRaw) : [(string)$directRaw];
  foreach ($direct as $id) {
    if ($id === '' || isset($seen[$id])) continue;
    $seen[$id] = true;
    $team = $names[$id]['team'] ?? manager_participant($season, $id)['team'] ?? ['name' => strtoupper($id), 'shirt' => '#fff', 'trim' => '#111'];
    $promoted[] = ['user' => $id, 'team' => $team, 'method' => 'direct'];
  }
  $playoff = (string)($season['promotion']['playoff'] ?? '');
  if ($playoff !== '' && !isset($seen[$playoff])) {
    $team = $names[$playoff]['team'] ?? manager_participant($season, $playoff)['team'] ?? ['name' => strtoupper($playoff), 'shirt' => '#fff', 'trim' => '#111'];
    $promoted[] = ['user' => $playoff, 'team' => $team, 'method' => 'playoff'];
  }

  $relegated = manager_relegated_from_above_teams($level, (string)($season['id'] ?? ''), $season);

  return [
    'fromDivision' => manager_division_name($level),
    'promotedToDivision' => $toDivision,
    'relegatedFromDivision' => $level > 1 ? manager_division_name($level - 1) : '',
    'promoted' => $promoted,
    'relegatedFromAbove' => $relegated,
    'cup' => $season['cup'] ?? null,
    'cups' => manager_public_cups($season, $state),
    'playerReport' => manager_human_player_report($season),
    'playerGrowth' => array_values($season['playerGrowthAwards'] ?? []),
  ];
}

function manager_find_user_player_location(array $user, string $playerId = '', string $playerName = ''): ?array {
  $playerName = trim($playerName);
  foreach (['starters', 'bench'] as $group) {
    foreach (($user['squad'][$group] ?? []) as $index => $player) {
      if (!is_array($player)) continue;
      $id = (string)($player['id'] ?? '');
      if ($playerId !== '' && $id === $playerId) return ['group' => $group, 'index' => (int)$index];
      if ($playerId === '' && $playerName !== '' && strcasecmp((string)($player['name'] ?? ''), $playerName) === 0) return ['group' => $group, 'index' => (int)$index];
    }
  }
  if ($playerId !== '' && $playerName !== '') {
    foreach (['starters', 'bench'] as $group) {
      foreach (($user['squad'][$group] ?? []) as $index => $player) {
        if (is_array($player) && strcasecmp((string)($player['name'] ?? ''), $playerName) === 0) return ['group' => $group, 'index' => (int)$index];
      }
    }
  }
  return null;
}

function manager_add_player_value_boost(array &$player, int $pct, string $reason, array $season): int {
  $pct = clamp_int($pct, 0, MANAGER_PLAYER_VALUE_BOOST_MAX);
  if ($pct <= 0) return 0;
  $before = clamp_int((int)($player['valueBoostPct'] ?? 0), 0, MANAGER_PLAYER_VALUE_BOOST_MAX);
  $after = clamp_int($before + $pct, 0, MANAGER_PLAYER_VALUE_BOOST_MAX);
  $applied = max(0, $after - $before);
  $player['valueBoostPct'] = $after;
  if (!isset($player['valueBoostReasons']) || !is_array($player['valueBoostReasons'])) $player['valueBoostReasons'] = [];
  $player['valueBoostReasons'][] = [
    'seasonId' => (string)($season['id'] ?? ''),
    'seasonYear' => (int)($season['year'] ?? date('Y')),
    'reason' => $reason,
    'pct' => $applied,
    'at' => time(),
  ];
  if (count($player['valueBoostReasons']) > 12) $player['valueBoostReasons'] = array_slice($player['valueBoostReasons'], -12);
  return $applied;
}

function manager_queue_growth_award(array &$awards, string $user, string $playerId, string $playerName, array $data): void {
  $key = manager_praise_player_key($user, $playerId, $playerName);
  if (!isset($awards[$key])) {
    $awards[$key] = [
      'user' => $user,
      'playerId' => $playerId,
      'playerName' => $playerName,
      'statGain' => 0,
      'valueBoostPct' => 0,
      'reasons' => [],
    ];
  }
  if ($playerId !== '' && (string)($awards[$key]['playerId'] ?? '') === '') $awards[$key]['playerId'] = $playerId;
  if ($playerName !== '' && (string)($awards[$key]['playerName'] ?? '') === '') $awards[$key]['playerName'] = $playerName;
  $awards[$key]['statGain'] = max((int)($awards[$key]['statGain'] ?? 0), (int)($data['statGain'] ?? 0));
  $awards[$key]['valueBoostPct'] = (int)($awards[$key]['valueBoostPct'] ?? 0) + (int)($data['valueBoostPct'] ?? 0);
  foreach (($data['reasons'] ?? []) as $reason) {
    $reason = (string)$reason;
    if ($reason !== '') $awards[$key]['reasons'][] = $reason;
  }
  foreach (['praisePoints', 'praiseMatches', 'avgImpact', 'topScorerRank', 'goals'] as $field) {
    if (array_key_exists($field, $data)) $awards[$key][$field] = $data[$field];
  }
}

function manager_player_growth_award_plan(array $season): array {
  $awards = [];
  foreach (manager_season_player_praise_rows($season) as $row) {
    $points = (int)($row['praisePoints'] ?? 0);
    $matches = (int)($row['praiseMatches'] ?? 0);
    $avgImpact = (float)($row['avgImpact'] ?? 0);
    if ($points < 6 && !($matches >= 5 && $avgImpact >= 8.0)) continue;
    manager_queue_growth_award($awards, (string)($row['user'] ?? ''), (string)($row['playerId'] ?? ''), (string)($row['name'] ?? 'Player'), [
      'statGain' => 2,
      'valueBoostPct' => 6,
      'praisePoints' => $points,
      'praiseMatches' => $matches,
      'avgImpact' => $avgImpact,
      'reasons' => ['Season praise'],
    ]);
  }
  $scorers = manager_stat_rows_for_phase($season, 'scorers', 'league', 'goals');
  manager_sort_scorer_rows($scorers);
  $boosts = [1 => 90, 2 => 60, 3 => 40];
  foreach (array_slice($scorers, 0, 3) as $index => $row) {
    $rank = $index + 1;
    $user = (string)($row['user'] ?? '');
    if ($user === '' || (int)($row['goals'] ?? 0) <= 0) continue;
    manager_queue_growth_award($awards, $user, (string)($row['playerId'] ?? ''), (string)($row['player'] ?? 'Player'), [
      'valueBoostPct' => $boosts[$rank] ?? 0,
      'topScorerRank' => $rank,
      'goals' => (int)($row['goals'] ?? 0),
      'reasons' => ['Golden Boot top ' . $rank],
    ]);
  }
  return $awards;
}

function manager_apply_player_growth_awards(array &$state, array &$season): array {
  if (!empty($season['playerGrowthApplied'])) return array_values($season['playerGrowthAwards'] ?? []);
  $applied = [];
  foreach (manager_player_growth_award_plan($season) as $award) {
    $user = clean_name((string)($award['user'] ?? ''));
    if ($user === '' || empty($state['users'][$user]) || !is_array($state['users'][$user])) continue;
    ensure_user_squad($state['users'][$user]);
    $playerId = (string)($award['playerId'] ?? '');
    $playerName = (string)($award['playerName'] ?? 'Player');
    $loc = manager_find_user_player_location($state['users'][$user], $playerId, $playerName);
    if (!$loc) continue;
    $group = (string)$loc['group'];
    $index = (int)$loc['index'];
    $player =& $state['users'][$user]['squad'][$group][$index];
    $statResult = null;
    if ((int)($award['statGain'] ?? 0) > 0) {
      $stat = manager_training_primary_stat($player);
      $statResult = manager_apply_training_gain_to_player($player, $stat, (int)$award['statGain']);
    }
    $reason = implode(' + ', array_values(array_unique($award['reasons'] ?? [])));
    $valueApplied = manager_add_player_value_boost($player, (int)($award['valueBoostPct'] ?? 0), $reason, $season);
    $currentValue = player_value($player);
    $playerName = (string)($player['name'] ?? $playerName);
    unset($player);
    ensure_squad_stats($state['users'][$user]['squad']);
    $applied[] = [
      'user' => $user,
      'playerId' => $playerId,
      'playerName' => $playerName,
      'reasons' => array_values(array_unique($award['reasons'] ?? [])),
      'praisePoints' => (int)($award['praisePoints'] ?? 0),
      'praiseMatches' => (int)($award['praiseMatches'] ?? 0),
      'avgImpact' => (float)($award['avgImpact'] ?? 0),
      'topScorerRank' => (int)($award['topScorerRank'] ?? 0),
      'goals' => (int)($award['goals'] ?? 0),
      'stat' => $statResult ? (string)($statResult['stat'] ?? '') : '',
      'statBefore' => $statResult ? (int)($statResult['before'] ?? 0) : 0,
      'statAfter' => $statResult ? (int)($statResult['after'] ?? 0) : 0,
      'statGain' => $statResult ? max(0, (int)($statResult['after'] ?? 0) - (int)($statResult['before'] ?? 0)) : 0,
      'valueBoostPct' => $valueApplied,
      'valueBoostTotalPct' => (int)($state['users'][$user]['squad'][$group][$index]['valueBoostPct'] ?? 0),
      'value' => $currentValue,
    ];
  }
  $season['playerGrowthAwards'] = $applied;
  $season['playerGrowthApplied'] = true;
  manager_log($state, 'player_growth_awards', ['seasonId' => (string)($season['id'] ?? ''), 'awards' => $applied]);
  return $applied;
}

function manager_participant(array $season, string $id): ?array {
  foreach (($season['participants'] ?? []) as $participant) {
    if (($participant['user'] ?? '') === $id) return $participant;
  }
  foreach (($season['cupParticipants'] ?? []) as $participant) {
    if (($participant['user'] ?? '') === $id) return $participant;
  }
  return null;
}

function manager_human_participant_fields_from_user(string $name, array $user): array {
  $user['accountName'] = clean_name((string)($user['accountName'] ?? $name));
  ensure_user_squad($user);
  ensure_manager_finance($user);
  $team = has_team($user) ? user_team($user) : ['id' => '', 'name' => strtoupper($name), 'shirt' => '#fff', 'trim' => '#111'];
  $squad = is_array($user['squad'] ?? null) ? $user['squad'] : ['starters' => [], 'bench' => []];
  ensure_squad_stats($squad);
  $formation = manager_user_formation($user);
  $snapshot = $user;
  $snapshot['squad'] = $squad;
  unset($snapshot['password'], $snapshot['created'], $snapshot['lastSeen']);
  return [
    'team' => $team,
    'squad' => $squad,
    'formation' => $formation,
    'rating' => squad_skill_rating($squad),
    'strength' => squad_skill_points($squad),
    'matchStrength' => squad_match_skill_points($squad),
    'divisionLevel' => clamp_int((int)($user['managerDivisionLevel'] ?? 5), 1, 5),
    'divisionName' => manager_division_name(clamp_int((int)($user['managerDivisionLevel'] ?? 5), 1, 5)),
    'managerFinance' => is_array($user['managerFinance'] ?? null) ? $user['managerFinance'] : [],
    'userSnapshot' => $snapshot,
  ];
}

function manager_participant_user_snapshot(array $participant, string $id): ?array {
  $snapshot = is_array($participant['userSnapshot'] ?? null) ? $participant['userSnapshot'] : [];
  if (!$snapshot) {
    if (empty($participant['squad']) || !is_array($participant['squad'])) return null;
    $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
    $snapshot = [
      'accountName' => clean_name($id),
      'teamId' => (string)($team['id'] ?? ''),
      'teamName' => (string)($team['name'] ?? strtoupper($id)),
      'shirt' => (string)($team['shirt'] ?? '#fff'),
      'trim' => (string)($team['trim'] ?? '#111'),
      'squad' => $participant['squad'],
      'manager' => ['formation' => valid_manager_formation((string)($participant['formation'] ?? '4-4-2'))],
      'managerDivisionLevel' => clamp_int((int)($participant['divisionLevel'] ?? 5), 1, 5),
      'managerFinance' => is_array($participant['managerFinance'] ?? null) ? $participant['managerFinance'] : [],
    ];
  }
  if (empty($snapshot['accountName'])) $snapshot['accountName'] = clean_name($id);
  $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
  if (empty($snapshot['teamId']) && !empty($team['id'])) $snapshot['teamId'] = (string)$team['id'];
  if (empty($snapshot['teamName']) && !empty($team['name'])) $snapshot['teamName'] = (string)$team['name'];
  if (empty($snapshot['shirt']) && !empty($team['shirt'])) $snapshot['shirt'] = (string)$team['shirt'];
  if (empty($snapshot['trim']) && !empty($team['trim'])) $snapshot['trim'] = (string)$team['trim'];
  if (empty($snapshot['squad']) && is_array($participant['squad'] ?? null)) $snapshot['squad'] = $participant['squad'];
  if (empty($snapshot['manager']) || !is_array($snapshot['manager'])) $snapshot['manager'] = [];
  if (empty($snapshot['manager']['formation'])) $snapshot['manager']['formation'] = valid_manager_formation((string)($participant['formation'] ?? '4-4-2'));
  if (!isset($snapshot['managerDivisionLevel'])) $snapshot['managerDivisionLevel'] = clamp_int((int)($participant['divisionLevel'] ?? 5), 1, 5);
  if (empty($snapshot['managerFinance']) && is_array($participant['managerFinance'] ?? null)) $snapshot['managerFinance'] = $participant['managerFinance'];
  if (!empty($snapshot['squad']) && is_array($snapshot['squad'])) ensure_squad_stats($snapshot['squad']);
  ensure_manager_finance($snapshot);
  return $snapshot;
}

function manager_global_user_matches_season(array $state, array $season, string $id): bool {
  if (!isset($state['users'][$id]) || is_cpu_manager($id)) return false;
  if (($season['status'] ?? '') === 'finished' && !empty($season['jobOfferAccepted'][$id])) return false;
  $seasonId = (string)($season['id'] ?? '');
  $currentId = (string)(current_manager_season_id($state, $id) ?? '');
  if ($currentId === '' || $seasonId === '' || $currentId === $seasonId) return true;
  $currentSeason = is_array($state['managerSeasons'][$currentId] ?? null) ? $state['managerSeasons'][$currentId] : null;
  if (!$currentSeason) return false;
  return manager_season_chain_key($state, $currentSeason) === manager_season_chain_key($state, $season);
}

function manager_refresh_human_participant_snapshots(array &$state, array &$season, ?array $userSnapshots = null): void {
  foreach (['participants', 'cupParticipants'] as $listKey) {
    if (empty($season[$listKey]) || !is_array($season[$listKey])) continue;
    foreach ($season[$listKey] as &$participant) {
      if (!is_array($participant) || ($participant['managerType'] ?? '') !== 'human') continue;
      $name = clean_name((string)($participant['user'] ?? ''));
      if ($name === '') continue;
      $source = null;
      if (is_array($userSnapshots) && is_array($userSnapshots[$name] ?? null)) $source = $userSnapshots[$name];
      elseif ($userSnapshots === null && manager_global_user_matches_season($state, $season, $name)) $source = $state['users'][$name] ?? null;
      if (!is_array($source)) continue;
      $fields = manager_human_participant_fields_from_user($name, $source);
      $level = $listKey === 'participants'
        ? clamp_int((int)($season['divisionLevel'] ?? $participant['divisionLevel'] ?? 5), 1, 5)
        : clamp_int((int)($participant['divisionLevel'] ?? $season['divisionLevel'] ?? 5), 1, 5);
      $fields['divisionLevel'] = $level;
      $fields['divisionName'] = manager_division_name($level);
      if (is_array($fields['userSnapshot'] ?? null)) $fields['userSnapshot']['managerDivisionLevel'] = $level;
      $participant = array_merge($participant, $fields);
    }
    unset($participant);
  }
}

function manager_runtime_account_fields(array $existing, array $incoming = []): array {
  $fields = [];
  $password = (string)($existing['password'] ?? '');
  if ($password === '') $password = (string)($incoming['password'] ?? '');
  if ($password !== '') $fields['password'] = $password;
  foreach (['created', 'lastSeen'] as $key) {
    if (array_key_exists($key, $existing)) $fields[$key] = $existing[$key];
    elseif (array_key_exists($key, $incoming)) $fields[$key] = $incoming[$key];
  }
  return $fields;
}

function manager_apply_runtime_account_fields(array $snapshot, array $existing): array {
  return array_merge($snapshot, manager_runtime_account_fields($existing, $snapshot));
}

function manager_mark_user_active_context(array &$user, array $season): void {
  $seasonId = (string)($season['id'] ?? '');
  if ($seasonId !== '') $user['managerActiveSeasonId'] = $seasonId;
  $gameId = manager_game_id($season);
  if ($gameId !== '') $user['managerActiveGameId'] = $gameId;
}

function manager_store_current_user_snapshot(array &$state, string $user): void {
  $user = clean_name($user);
  if ($user === '' || !is_array($state['users'][$user] ?? null)) return;
  $seasonId = null;
  $activeId = (string)($state['users'][$user]['managerActiveSeasonId'] ?? '');
  if ($activeId !== '' && is_array($state['managerSeasons'][$activeId] ?? null) && manager_season_visible_to_user($state['managerSeasons'][$activeId], $user)) {
    $seasonId = $activeId;
  }
  if ($seasonId === null) $seasonId = current_manager_season_id($state, $user);
  if ($seasonId === null || !is_array($state['managerSeasons'][$seasonId] ?? null)) return;
  $season =& $state['managerSeasons'][$seasonId];
  if (manager_participant($season, $user) === null) return;
  manager_refresh_human_participant_snapshots($state, $season, [$user => $state['users'][$user]]);
}

function manager_restore_user_from_season_snapshot(array &$state, array $season, string $user): void {
  $user = clean_name($user);
  if ($user === '') return;
  $participant = manager_participant($season, $user);
  if ($participant === null) return;
  $snapshot = manager_participant_user_snapshot($participant, $user);
  if (!is_array($snapshot)) return;
  $existing = is_array($state['users'][$user] ?? null) ? $state['users'][$user] : [];
  $snapshot = manager_apply_runtime_account_fields($snapshot, $existing);
  $snapshot['accountName'] = $user;
  ensure_user_squad($snapshot);
  ensure_manager_finance($snapshot);
  manager_mark_user_active_context($snapshot, $season);
  $state['users'][$user] = $snapshot;
}

function manager_cpu_formation_for_team(array $team, int $divisionLevel = 5): string {
  $teamKey = team_source_key($team);
  if ($teamKey === '') $teamKey = (string)($team['id'] ?? $team['name'] ?? 'cpu');
  $formations = ['4-4-2', '4-3-3', '4-2-3-1', '4-5-1', '3-5-2', '5-3-2'];
  $seed = abs((int)crc32($teamKey . '|division:' . clamp_int($divisionLevel, 1, 5) . '|cpu-formation'));
  return $formations[$seed % count($formations)];
}

function normalize_cpu_participant_formation(array &$participant): void {
  if (($participant['managerType'] ?? 'cpu') !== 'cpu') return;
  $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
  if (!$team || empty($participant['squad']) || !is_array($participant['squad'])) return;
  $level = clamp_int((int)($participant['divisionLevel'] ?? 5), 1, 5);
  $formation = manager_cpu_formation_for_team($team, $level);
  $currentFormation = valid_manager_formation((string)($participant['formation'] ?? ''));
  $needsFormation = (int)($participant['formationVersion'] ?? 0) < CPU_FORMATION_VERSION || $currentFormation !== $formation;
  if ($needsFormation) {
    apply_formation_to_squad($participant['squad'], $formation, [], true);
    $participant['formation'] = $formation;
    $participant['formationVersion'] = CPU_FORMATION_VERSION;
  }
  ensure_squad_stats($participant['squad']);
  $participant['rating'] = squad_skill_rating($participant['squad']);
  $participant['strength'] = squad_skill_points($participant['squad']);
}

function normalize_state_cpu_formations(array &$state): void {
  if (empty($state['managerSeasons']) || !is_array($state['managerSeasons'])) return;
  foreach ($state['managerSeasons'] as &$season) {
    if (!is_array($season)) continue;
    foreach (['participants', 'cupParticipants'] as $listKey) {
      if (empty($season[$listKey]) || !is_array($season[$listKey])) continue;
      foreach ($season[$listKey] as &$participant) {
        if (!is_array($participant) || !is_cpu_manager((string)($participant['user'] ?? ''))) continue;
        normalize_cpu_participant_formation($participant);
      }
      unset($participant);
    }
  }
  unset($season);
}

function cpu_manager_participant(array $team, int $divisionLevel = 5): array {
  $base = (int)($team['baseRating'] ?? team_base_rating((string)$team['id']));
  $squad = make_named_squad('home', $base, team_source_key($team), (string)($team['name'] ?? ''));
  ensure_squad_age_profiles($squad, manager_cpu_id((string)$team['id']), team_source_key($team), (string)($team['name'] ?? ''), manager_division_name($divisionLevel));
  $formation = manager_cpu_formation_for_team($team, $divisionLevel);
  apply_formation_to_squad($squad, $formation, [], true);
  return [
    'user' => manager_cpu_id((string)$team['id']),
    'managerType' => 'cpu',
    'team' => $team,
    'rating' => squad_skill_rating($squad),
    'strength' => squad_skill_points($squad),
    'divisionLevel' => clamp_int($divisionLevel, 1, 5),
    'divisionName' => manager_division_name($divisionLevel),
    'ratingBase' => $base,
    'scaleVersion' => SQUAD_SCALE_VERSION,
    'nameVersion' => SQUAD_NAME_VERSION,
    'idVersion' => SQUAD_ID_VERSION,
    'formation' => $formation,
    'formationVersion' => CPU_FORMATION_VERSION,
    'squad' => $squad,
  ];
}

function scaled_cpu_manager_participant(array $participant): array {
  if (($participant['managerType'] ?? 'cpu') !== 'cpu') return $participant;
  $team = $participant['team'] ?? null;
  if (!is_array($team)) return $participant;
  $level = clamp_int((int)($participant['divisionLevel'] ?? 5), 1, 5);
  $expectedBase = (int)($team['baseRating'] ?? team_base_rating((string)($team['id'] ?? '')));
  $hasSquad = !empty($participant['squad']['starters']) && !empty($participant['squad']['bench']);
  if (
    $hasSquad
    && (int)($participant['scaleVersion'] ?? 0) >= SQUAD_SCALE_VERSION
    && (int)($participant['ratingBase'] ?? 0) === $expectedBase
  ) {
    normalize_squad_player_ids($participant['squad'], team_source_key($team), (string)($team['name'] ?? ''), 'home');
    ensure_squad_stats($participant['squad']);
    ensure_squad_age_profiles($participant['squad'], (string)($participant['user'] ?? manager_cpu_id((string)($team['id'] ?? ''))), team_source_key($team), (string)($team['name'] ?? ''), manager_division_name($level));
    if ((int)($participant['nameVersion'] ?? 0) < SQUAD_NAME_VERSION) {
      apply_legacy_names_to_squad($participant['squad'], team_source_key($team), (string)($team['name'] ?? ''));
      $participant['nameVersion'] = SQUAD_NAME_VERSION;
      normalize_squad_player_ids($participant['squad'], team_source_key($team), (string)($team['name'] ?? ''), 'home');
    }
    normalize_cpu_participant_formation($participant);
    $participant['idVersion'] = SQUAD_ID_VERSION;
    $participant['rating'] = squad_skill_rating($participant['squad']);
    $participant['strength'] = squad_skill_points($participant['squad']);
    return $participant;
  }
  $scaled = cpu_manager_participant($team, $level);
  $scaled['user'] = (string)($participant['user'] ?? $scaled['user']);
  return $scaled;
}

function manager_team_key_blocked(array $team, array $blockedKeys): bool {
  $keys = [];
  manager_mark_team_ids($keys, $team);
  foreach (array_keys($keys) as $key) {
    if ($key !== '' && isset($blockedKeys[$key])) return true;
  }
  return false;
}

function manager_season_used_team_keys(array $season): array {
  $keys = [];
  foreach (['participants', 'cupParticipants'] as $listKey) {
    foreach (($season[$listKey] ?? []) as $participant) {
      if (is_array($participant['team'] ?? null)) manager_mark_team_ids($keys, $participant['team']);
    }
  }
  return $keys;
}

function manager_season_fixture_side_counts(array $season): array {
  $counts = [];
  foreach (($season['rounds'] ?? []) as $round) {
    if (!is_array($round)) continue;
    foreach (($round['fixtures'] ?? []) as $fixture) {
      if (!is_array($fixture)) continue;
      foreach (['home', 'away'] as $key) {
        $id = (string)($fixture[$key] ?? '');
        if ($id === '') continue;
        $counts[$id] = ($counts[$id] ?? 0) + 1;
      }
    }
  }
  return $counts;
}

function manager_replacement_cpu_participant(array $season, array $blockedKeys, array $usedKeys, int $level): ?array {
  $level = clamp_int($level, 1, 5);
  foreach (manager_division_teams($level) as $team) {
    $team['divisionId'] = manager_division_id($level);
    $team['divisionName'] = manager_division_name($level);
    $teamKeys = [];
    manager_mark_team_ids($teamKeys, $team);
    $blocked = false;
    foreach (array_keys($teamKeys) as $key) {
      if ($key !== '' && (isset($blockedKeys[$key]) || isset($usedKeys[$key]))) {
        $blocked = true;
        break;
      }
    }
    if (!$blocked) return cpu_manager_participant($team, $level);
  }
  $division = manager_division_name($level);
  $base = match ($level) {
    1 => 74,
    2 => 66,
    3 => 58,
    4 => 52,
    default => 46,
  };
  for ($i = 1; $i <= 99; $i++) {
    $id = manager_division_id($level) . '-reserve-' . $i;
    $team = manager_generated_team($id, strtoupper($division . ' RESERVE ' . $i), $base, '#284b9b', '#f1d24a', $division);
    $teamKeys = [];
    manager_mark_team_ids($teamKeys, $team);
    $blocked = false;
    foreach (array_keys($teamKeys) as $key) {
      if ($key !== '' && (isset($blockedKeys[$key]) || isset($usedKeys[$key]))) {
        $blocked = true;
        break;
      }
    }
    if (!$blocked) return cpu_manager_participant($team, $level);
  }
  return null;
}

function manager_replace_season_side_id(array &$season, string $oldId, array $newParticipant): void {
  $newId = (string)($newParticipant['user'] ?? '');
  if ($oldId === '' || $newId === '' || $oldId === $newId) return;
  $newTeam = is_array($newParticipant['team'] ?? null) ? $newParticipant['team'] : [];
  if (!empty($season['rounds']) && is_array($season['rounds'])) {
    foreach ($season['rounds'] as &$round) {
      if (!is_array($round)) continue;
      if (empty($round['fixtures']) || !is_array($round['fixtures'])) continue;
      foreach ($round['fixtures'] as &$fixture) {
        if (!is_array($fixture)) continue;
        foreach (['home', 'away', 'winner'] as $key) {
          if ((string)($fixture[$key] ?? '') === $oldId) $fixture[$key] = $newId;
        }
        foreach (['strength', 'odds'] as $key) {
          if (!is_array($fixture[$key] ?? null)) continue;
          if (array_key_exists($oldId, $fixture[$key])) {
            $fixture[$key][$newId] = $fixture[$key][$oldId];
            unset($fixture[$key][$oldId]);
          }
        }
      }
      unset($fixture);
    }
    unset($round);
  }

  if (is_array($season['table'] ?? null)) {
    if (isset($season['table'][$oldId]) && is_array($season['table'][$oldId])) {
      $row = $season['table'][$oldId];
      unset($season['table'][$oldId]);
      $row['user'] = $newId;
      $row['team'] = $newTeam;
      $row['managerType'] = 'cpu';
      $season['table'][$newId] = $row;
    }
    foreach ($season['table'] as &$row) {
      if (!is_array($row) || (string)($row['user'] ?? '') !== $oldId) continue;
      $row['user'] = $newId;
      $row['team'] = $newTeam;
      $row['managerType'] = 'cpu';
    }
    unset($row);
  }

  foreach (['scorers', 'assists'] as $statKey) {
    if (!is_array($season[$statKey] ?? null)) continue;
    foreach ($season[$statKey] as &$row) {
      if (!is_array($row) || (string)($row['user'] ?? '') !== $oldId) continue;
      $row['user'] = $newId;
      $row['team'] = $newTeam;
    }
    unset($row);
  }

  if (is_array($season['matchReports'] ?? null)) foreach ($season['matchReports'] as &$report) {
    if (!is_array($report)) continue;
    if ((string)($report['home'] ?? '') === $oldId) {
      $report['home'] = $newId;
      $report['homeTeam'] = $newTeam;
    }
    if ((string)($report['away'] ?? '') === $oldId) {
      $report['away'] = $newId;
      $report['awayTeam'] = $newTeam;
    }
  }
  unset($report);
}

function manager_repair_human_owned_cpu_conflicts(array &$state): void {
  if (empty($state['managerSeasons']) || !is_array($state['managerSeasons'])) return;
  foreach ($state['managerSeasons'] as &$season) {
    if (!is_array($season) || ($season['status'] ?? '') === 'aborted') continue;
    if (!in_array((string)($season['status'] ?? ''), ['active', 'playoff'], true)) continue;
    $gameId = manager_game_id($season);
    if ($gameId === '') continue;
    $blockedKeys = manager_game_human_team_keys($state, $gameId, [], null);
    if (!$blockedKeys) continue;
    $usedKeys = manager_season_used_team_keys($season);
    $replacements = [];

    foreach (['participants', 'cupParticipants'] as $listKey) {
      if (empty($season[$listKey]) || !is_array($season[$listKey])) continue;
      foreach ($season[$listKey] as &$participant) {
        if (!is_array($participant) || !is_cpu_manager((string)($participant['user'] ?? ''))) continue;
        $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
        if (!$team || !manager_team_key_blocked($team, $blockedKeys)) continue;
        $oldId = (string)($participant['user'] ?? '');
        if ($oldId === '') continue;
        if (!isset($replacements[$oldId])) {
          $level = clamp_int((int)($participant['divisionLevel'] ?? $season['divisionLevel'] ?? 5), 1, 5);
          $replacement = manager_replacement_cpu_participant($season, $blockedKeys, $usedKeys, $level);
          if ($replacement === null) continue;
          $replacements[$oldId] = $replacement;
          manager_mark_team_ids($usedKeys, $replacement['team']);
        }
        $participant = $replacements[$oldId];
      }
      unset($participant);
    }

    $fixtureCounts = manager_season_fixture_side_counts($season);
    $knownIds = [];
    foreach (['participants', 'cupParticipants'] as $listKey) {
      foreach (($season[$listKey] ?? []) as $participant) {
        $id = (string)($participant['user'] ?? '');
        if ($id !== '') $knownIds[$id] = true;
      }
    }

    $unusedParticipants = ['participants' => [], 'cupParticipants' => []];
    foreach (['participants', 'cupParticipants'] as $listKey) {
      foreach (($season[$listKey] ?? []) as $participant) {
        if (!is_array($participant) || !is_cpu_manager((string)($participant['user'] ?? ''))) continue;
        $id = (string)($participant['user'] ?? '');
        if ($id === '' || ($fixtureCounts[$id] ?? 0) > 0) continue;
        $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
        if (!$team || manager_team_key_blocked($team, $blockedKeys)) continue;
        $unusedParticipants[$listKey][] = $participant;
      }
    }

    $orphanIds = [];
    foreach (($season['rounds'] ?? []) as $round) {
      if (!is_array($round)) continue;
      foreach (($round['fixtures'] ?? []) as $fixture) {
        if (!is_array($fixture)) continue;
        $phase = strtolower(trim((string)($round['phase'] ?? $fixture['phase'] ?? '')));
        $targetList = $phase === 'cup' ? 'cupParticipants' : 'participants';
        foreach (['home', 'away', 'winner'] as $key) {
          $sideId = (string)($fixture[$key] ?? '');
          if ($sideId === '' || !is_cpu_manager($sideId) || isset($knownIds[$sideId])) continue;
          $sourceId = source_team_id($sideId);
          if ($sourceId !== '' && isset($blockedKeys[$sourceId])) {
            $currentTarget = $orphanIds[$sideId] ?? '';
            $orphanIds[$sideId] = ($currentTarget === 'participants' || $targetList === 'participants') ? 'participants' : 'cupParticipants';
          }
        }
      }
    }

    foreach ($orphanIds as $oldId => $listKey) {
      if (isset($replacements[$oldId])) continue;
      $replacement = array_shift($unusedParticipants[$listKey]);
      if ($replacement === null) {
        $replacement = manager_replacement_cpu_participant($season, $blockedKeys, $usedKeys, (int)($season['divisionLevel'] ?? 5));
        if ($replacement === null) continue;
        if (empty($season[$listKey]) || !is_array($season[$listKey])) $season[$listKey] = [];
        $season[$listKey][] = $replacement;
        if ($listKey === 'participants' && is_array($season['table'] ?? null)) {
          foreach (manager_table([$replacement]) as $id => $row) {
            if (!isset($season['table'][$id])) $season['table'][$id] = $row;
          }
        }
      }
      $replacements[$oldId] = $replacement;
      if (is_array($replacement['team'] ?? null)) manager_mark_team_ids($usedKeys, $replacement['team']);
    }

    if (!$replacements) continue;
    foreach ($replacements as $oldId => $replacement) {
      manager_replace_season_side_id($season, (string)$oldId, $replacement);
    }
    $season['updated'] = time();
    manager_log($state, 'repair_human_owned_cpu_team', [
      'seasonId' => (string)($season['id'] ?? ''),
      'managerGameId' => $gameId,
      'replacements' => array_map(fn($participant) => [
        'user' => (string)($participant['user'] ?? ''),
        'team' => $participant['team']['name'] ?? '',
      ], $replacements),
    ]);
  }
  unset($season);
}

function manager_side_preview(?array $state, array $season, string $id): array {
  $participant = manager_participant($season, $id);
  if ($participant !== null && is_cpu_manager($id)) $participant = scaled_cpu_manager_participant($participant);
  if ($state !== null && !is_cpu_manager($id) && isset($state['users'][$id]) && manager_global_user_matches_season($state, $season, $id)) {
    $user = $state['users'][$id];
    ensure_user_squad($user);
    ensure_manager_finance($user);
    $team = has_team($user) ? user_team($user) : ($participant['team'] ?? ['name' => strtoupper($id), 'shirt' => '#fff', 'trim' => '#111']);
    $squad = manager_apply_staff_boost_to_squad($user['squad'] ?? ($participant['squad'] ?? ['starters' => [], 'bench' => []]), $user);
    $formation = manager_user_formation($user);
  } elseif ($participant !== null && !is_cpu_manager($id) && ($user = manager_participant_user_snapshot($participant, $id)) !== null) {
    $team = has_team($user) ? user_team($user) : ($participant['team'] ?? ['name' => strtoupper($id), 'shirt' => '#fff', 'trim' => '#111']);
    $squad = manager_apply_staff_boost_to_squad($user['squad'] ?? ($participant['squad'] ?? ['starters' => [], 'bench' => []]), $user);
    $formation = manager_user_formation($user);
  } elseif ($state !== null && !is_cpu_manager($id) && isset($state['users'][$id])) {
    $user = $state['users'][$id];
    ensure_user_squad($user);
    ensure_manager_finance($user);
    $team = has_team($user) ? user_team($user) : ($participant['team'] ?? ['name' => strtoupper($id), 'shirt' => '#fff', 'trim' => '#111']);
    $squad = manager_apply_staff_boost_to_squad($user['squad'] ?? ($participant['squad'] ?? ['starters' => [], 'bench' => []]), $user);
    $formation = manager_user_formation($user);
  } else {
    $team = $participant['team'] ?? ['name' => strtoupper($id), 'shirt' => '#fff', 'trim' => '#111'];
    $squad = $participant['squad'] ?? ['starters' => [], 'bench' => []];
    $formation = valid_manager_formation((string)($participant['formation'] ?? '4-4-2'));
  }
  if (is_array($team)) $team = apply_team_flag_fields($team);
  ensure_squad_stats($squad);
  return [
    'team' => $team,
    'squad' => $squad,
    'formation' => $formation,
    'rating' => squad_skill_rating($squad),
    'strength' => squad_skill_points($squad),
    'matchStrength' => squad_match_skill_points($squad),
  ];
}

function manager_side_data(array &$state, array $season, string $id): array {
  $participant = manager_participant($season, $id);
  if ($participant !== null && is_cpu_manager($id)) $participant = scaled_cpu_manager_participant($participant);
  if (!is_cpu_manager($id) && isset($state['users'][$id]) && manager_global_user_matches_season($state, $season, $id)) {
    ensure_user_squad($state['users'][$id]);
    ensure_manager_finance($state['users'][$id]);
    $squad = manager_apply_staff_boost_to_squad($state['users'][$id]['squad'] ?? ['starters' => [], 'bench' => []], $state['users'][$id]);
    return [
      'human' => true,
      'team' => user_team($state['users'][$id]),
      'squad' => $squad,
      'formation' => manager_user_formation($state['users'][$id]),
      'rating' => squad_skill_rating($squad),
      'strength' => squad_skill_points($squad),
      'matchStrength' => squad_match_skill_points($squad),
    ];
  }
  if (!is_cpu_manager($id) && $participant !== null && ($user = manager_participant_user_snapshot($participant, $id)) !== null) {
    $squad = manager_apply_staff_boost_to_squad($user['squad'] ?? ($participant['squad'] ?? ['starters' => [], 'bench' => []]), $user);
    $team = has_team($user) ? user_team($user) : ($participant['team'] ?? ['name' => strtoupper($id), 'shirt' => '#fff', 'trim' => '#111']);
    if (is_array($team)) $team = apply_team_flag_fields($team);
    return [
      'human' => true,
      'team' => $team,
      'squad' => $squad,
      'formation' => manager_user_formation($user),
      'rating' => squad_skill_rating($squad),
      'strength' => squad_skill_points($squad),
      'matchStrength' => squad_match_skill_points($squad),
    ];
  }
  if (!is_cpu_manager($id) && isset($state['users'][$id])) {
    ensure_user_squad($state['users'][$id]);
    ensure_manager_finance($state['users'][$id]);
    $squad = manager_apply_staff_boost_to_squad($state['users'][$id]['squad'] ?? ['starters' => [], 'bench' => []], $state['users'][$id]);
    return [
      'human' => true,
      'team' => user_team($state['users'][$id]),
      'squad' => $squad,
      'formation' => manager_user_formation($state['users'][$id]),
      'rating' => squad_skill_rating($squad),
      'strength' => squad_skill_points($squad),
      'matchStrength' => squad_match_skill_points($squad),
    ];
  }
  $squad = $participant['squad'] ?? ['starters' => [], 'bench' => []];
  ensure_squad_stats($squad);
  $team = $participant['team'] ?? ['name' => strtoupper($id), 'shirt' => '#fff', 'trim' => '#111'];
  if (is_array($team)) $team = apply_team_flag_fields($team);
  ensure_squad_age_profiles($squad, $id, is_array($team) ? team_source_key($team) : '', is_array($team) ? (string)($team['name'] ?? '') : '', (string)($participant['divisionName'] ?? ''));
  return [
    'human' => false,
    'team' => $team,
    'squad' => $squad,
    'formation' => valid_manager_formation((string)($participant['formation'] ?? '4-4-2')),
    'rating' => squad_skill_rating($squad),
    'strength' => squad_skill_points($squad),
    'matchStrength' => squad_match_skill_points($squad),
  ];
}

function manager_table(array $participants): array {
  $table = [];
  foreach ($participants as $participant) {
    $name = (string)$participant['user'];
    $table[$name] = [
      'user' => $name,
      'team' => $participant['team'],
      'managerType' => $participant['managerType'] ?? 'human',
      'played' => 0,
      'won' => 0,
      'drawn' => 0,
      'lost' => 0,
      'gf' => 0,
      'ga' => 0,
      'gd' => 0,
      'points' => 0,
    ];
  }
  return $table;
}

function manager_fixtures(array $participants): array {
  $players = array_map(fn($p) => (string)$p['user'], $participants);
  if (count($players) % 2 === 1) $players[] = '__bye__';
  $n = count($players);
  $rounds = [];
  $roundNo = 1;
  $list = $players;
  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__') continue;
      if ($r % 2 === 1) [$home, $away] = [$away, $home];
      $fixtures[] = ['home' => $home, 'away' => $away, 'status' => 'pending', 'score' => null, 'phase' => 'league'];
    }
    $rounds[] = ['round' => $roundNo++, 'fixtures' => $fixtures];
    $fixed = $list[0];
    $rest = array_slice($list, 1);
    array_unshift($rest, array_pop($rest));
    $list = array_merge([$fixed], $rest);
  }
  $firstLeg = $rounds;
  foreach ($firstLeg as $round) {
    $fixtures = [];
    foreach ($round['fixtures'] as $fixture) {
      $fixtures[] = ['home' => $fixture['away'], 'away' => $fixture['home'], 'status' => 'pending', 'score' => null, 'phase' => 'league'];
    }
    $rounds[] = ['round' => $roundNo++, 'fixtures' => $fixtures];
  }
  return $rounds;
}

function manager_pending_round_index(array $season): ?int {
  foreach (($season['rounds'] ?? []) as $i => $round) {
    foreach (($round['fixtures'] ?? []) as $fixture) {
      if (in_array(($fixture['status'] ?? ''), ['pending', 'watching'], true)) return (int)$i;
    }
    if (manager_round_continue_required($season, (int)$i)) {
      $split = manager_round_ready_waiting($season, (int)$i);
      if (!empty($split['waiting'])) return (int)$i;
    }
  }
  return null;
}

function manager_round_phase(array $round): string {
  $phase = trim((string)($round['phase'] ?? ''));
  if ($phase !== '') return strtolower($phase);
  foreach (($round['fixtures'] ?? []) as $fixture) {
    $fixturePhase = trim((string)($fixture['phase'] ?? ''));
    if ($fixturePhase !== '') return strtolower($fixturePhase);
  }
  return 'league';
}

function manager_next_round_stale_response(array &$state, array $season, string $user): array {
  return [
    'managerSeason' => public_manager_season($season, $state, $user),
    'managerFinance' => public_finance($state['users'][$user]),
    'managerSquad' => public_manager_context_squad($state, $user, $season),
	    'managerMarket' => public_manager_market($state, $season, $buyer),
    'managerTransferOffers' => public_manager_transfer_offers($state, $user),
    'staleRound' => true,
  ];
}

function manager_next_round_request_is_stale(array $season, ?int $roundIndex, array $data): bool {
  $hasRoundIndex = array_key_exists('roundIndex', $data) && is_numeric($data['roundIndex']);
  if (!$hasRoundIndex) return false;
  $requestedRoundIndex = (int)$data['roundIndex'];
  if ($roundIndex === null || $requestedRoundIndex !== $roundIndex) return true;

  $round = $season['rounds'][$roundIndex] ?? null;
  if (!is_array($round)) return true;

  $requestedKey = trim((string)($data['simKey'] ?? ''));
  if ($requestedKey !== '' && $requestedKey !== manager_sim_round_key($season, $roundIndex)) return true;

  $requestedPhase = strtolower(trim((string)($data['roundPhase'] ?? '')));
  if ($requestedPhase !== '' && $requestedPhase !== manager_round_phase($round)) return true;

  if (array_key_exists('fixtureIndex', $data) && is_numeric($data['fixtureIndex'])) {
    $fixtureIndex = (int)$data['fixtureIndex'];
    $fixture = $round['fixtures'][$fixtureIndex] ?? null;
    if (!is_array($fixture) || !in_array(($fixture['status'] ?? ''), ['pending', 'watching'], true)) return true;
  }
  return false;
}

function manager_fresh_next_round_request(array $season, string $user, ?int $roundIndex): ?array {
  if ($roundIndex === null) return null;
  $entry = manager_user_round_fixture($season, $user, $roundIndex);
  if (!$entry || !in_array(($entry['fixture']['status'] ?? ''), ['pending', 'watching'], true)) return null;
  $round = $season['rounds'][$roundIndex] ?? [];
  return [
    'roundIndex' => $roundIndex,
    'roundPhase' => manager_round_phase(is_array($round) ? $round : []),
    'fixtureIndex' => (int)$entry['fixtureIndex'],
    'simKey' => manager_sim_round_key($season, $roundIndex),
  ];
}

function manager_round_continue_request_is_stale(array $season, ?int $roundIndex, array $data): bool {
  $hasRoundIndex = array_key_exists('roundIndex', $data) && is_numeric($data['roundIndex']);
  if (!$hasRoundIndex) return count(array_values($season['humanManagers'] ?? [])) > 1;
  $requestedRoundIndex = (int)$data['roundIndex'];
  if ($roundIndex === null || $requestedRoundIndex !== $roundIndex) return true;

  $round = $season['rounds'][$roundIndex] ?? null;
  if (!is_array($round)) return true;

  $requestedKey = trim((string)($data['simKey'] ?? ''));
  if ($requestedKey !== '' && $requestedKey !== manager_sim_round_key($season, $roundIndex)) return true;

  $requestedPhase = strtolower(trim((string)($data['roundPhase'] ?? '')));
  if ($requestedPhase !== '' && $requestedPhase !== manager_round_phase($round)) return true;

  return false;
}

function manager_round_log_context(array $season, int $roundIndex, ?int $fixtureIndex = null, array $fixtureOverride = []): array {
  $round = $season['rounds'][$roundIndex] ?? [];
  $fixture = $fixtureOverride;
  if (!$fixture && $fixtureIndex !== null) $fixture = $round['fixtures'][$fixtureIndex] ?? [];
  $phase = manager_round_phase(is_array($round) ? $round : []);
  if (!empty($fixture['phase'])) $phase = strtolower((string)$fixture['phase']);
  $out = [
    'seasonId' => (string)($season['id'] ?? ''),
    'roundIndex' => $roundIndex,
    'round' => $round['round'] ?? '',
    'phase' => $phase,
  ];
  if ($fixtureIndex !== null) {
    $out += [
      'fixtureIndex' => $fixtureIndex,
      'home' => (string)($fixture['home'] ?? ''),
      'away' => (string)($fixture['away'] ?? ''),
      'fixtureStatus' => (string)($fixture['status'] ?? ''),
    ];
  }
  if ($phase === 'cup') {
    $out += [
      'cupKey' => (string)($fixture['cupKey'] ?? $round['cupKey'] ?? ''),
      'cupName' => (string)($fixture['cupName'] ?? $round['cupName'] ?? 'Cup'),
      'cupStage' => (string)($fixture['cupStage'] ?? $round['cupStage'] ?? ''),
      'cupRoundNo' => (int)($fixture['cupRoundNo'] ?? $round['cupRoundNo'] ?? 0),
      'cupGroup' => (string)($fixture['cupGroup'] ?? $round['cupGroup'] ?? ''),
    ];
  }
  return $out;
}

function manager_sim_round_key(array $season, int $roundIndex): string {
  $round = $season['rounds'][$roundIndex]['round'] ?? $roundIndex;
  return (string)($season['status'] ?? 'active') . ':' . $roundIndex . ':' . (string)$round;
}

function manager_round_continue_key(array $season, int $roundIndex): string {
  $round = $season['rounds'][$roundIndex]['round'] ?? $roundIndex;
  return 'continue:' . (string)($season['id'] ?? '') . ':' . $roundIndex . ':' . (string)$round;
}

function manager_round_continue_required(array $season, int $roundIndex): bool {
  $key = manager_round_continue_key($season, $roundIndex);
  return !empty($season['roundContinueRequired'][$key]);
}

function manager_require_round_continue(array &$season, int $roundIndex): void {
  if (count(array_values($season['humanManagers'] ?? [])) <= 1) return;
  $key = manager_round_continue_key($season, $roundIndex);
  if (!isset($season['roundContinueRequired']) || !is_array($season['roundContinueRequired'])) $season['roundContinueRequired'] = [];
  $season['roundContinueRequired'][$key] = true;
}

function manager_has_round_continue(array $season, string $user, int $roundIndex): bool {
  $key = manager_round_continue_key($season, $roundIndex);
  return !empty($season['roundContinues'][$key][$user]);
}

function manager_mark_round_continue(array &$season, string $user, int $roundIndex, int $now): void {
  manager_require_round_continue($season, $roundIndex);
  $key = manager_round_continue_key($season, $roundIndex);
  if (!isset($season['roundContinues']) || !is_array($season['roundContinues'])) $season['roundContinues'] = [];
  if (!isset($season['roundContinues'][$key]) || !is_array($season['roundContinues'][$key])) $season['roundContinues'][$key] = [];
  $season['roundContinues'][$key][$user] = $now;
}

function manager_auto_mark_played_round_continues(array &$season, int $now = 0): int {
  if (count(array_values($season['humanManagers'] ?? [])) <= 1) return 0;
  if ($now <= 0) $now = time();
  $changed = 0;
  foreach (($season['rounds'] ?? []) as $roundIndex => $round) {
    $roundIndex = (int)$roundIndex;
    if (!manager_round_continue_required($season, $roundIndex)) continue;
    foreach (manager_round_human_users($season, $roundIndex) as $name) {
      if (manager_has_round_continue($season, $name, $roundIndex)) continue;
      $entry = manager_user_round_fixture($season, $name, $roundIndex);
      if (!$entry || ($entry['fixture']['status'] ?? '') !== 'played') continue;
      if (manager_fixture_result_hidden_for_viewer($entry['fixture'], $name, $now)) continue;
      manager_mark_round_continue($season, $name, $roundIndex, $now);
      $changed++;
    }
  }
  return $changed;
}

function manager_sim_fixture_key(array $season, int $roundIndex, int $fixtureIndex): string {
  $round = $season['rounds'][$roundIndex]['round'] ?? $roundIndex;
  return 'fixture:' . (string)($season['status'] ?? 'active') . ':' . $roundIndex . ':' . $fixtureIndex . ':' . (string)$round;
}

function manager_watch_fixture_key(array $season, int $roundIndex, int $fixtureIndex): string {
  return 'watch:' . manager_sim_fixture_key($season, $roundIndex, $fixtureIndex);
}

function manager_fixture_human_users(array $season, array $fixture): array {
  $sides = [(string)($fixture['home'] ?? ''), (string)($fixture['away'] ?? '')];
  $out = [];
  foreach (array_values($season['humanManagers'] ?? []) as $name) {
    $name = (string)$name;
    if ($name !== '' && in_array($name, $sides, true)) $out[] = $name;
  }
  return $out;
}

function manager_round_human_users(array $season, int $roundIndex): array {
  $found = [];
  foreach (($season['rounds'][$roundIndex]['fixtures'] ?? []) as $fixture) {
    foreach (manager_fixture_human_users($season, $fixture) as $name) $found[$name] = true;
  }
  $out = [];
  foreach (array_values($season['humanManagers'] ?? []) as $name) {
    $name = (string)$name;
    if ($name !== '' && isset($found[$name])) $out[] = $name;
  }
  return $out;
}

function manager_user_round_fixture(array $season, string $user, int $roundIndex): ?array {
  foreach (($season['rounds'][$roundIndex]['fixtures'] ?? []) as $fixtureIndex => $fixture) {
    if (($fixture['home'] ?? '') === $user || ($fixture['away'] ?? '') === $user) {
      return ['roundIndex' => $roundIndex, 'fixtureIndex' => (int)$fixtureIndex, 'fixture' => $fixture];
    }
  }
  return null;
}

function manager_requested_user_fixture(array $season, string $user, array $data): ?array {
  if (!isset($data['roundIndex']) || !is_numeric($data['roundIndex'])) return null;
  $roundIndex = (int)$data['roundIndex'];
  if (!isset($data['fixtureIndex']) || !is_numeric($data['fixtureIndex'])) {
    $entry = manager_user_round_fixture($season, $user, $roundIndex);
    if (!$entry || !in_array(($entry['fixture']['status'] ?? ''), ['pending', 'watching'], true)) return null;
    return $entry;
  }
  $fixtureIndex = (int)$data['fixtureIndex'];
  $fixture = $season['rounds'][$roundIndex]['fixtures'][$fixtureIndex] ?? null;
  if (!is_array($fixture)) return null;
  if (($fixture['home'] ?? '') !== $user && ($fixture['away'] ?? '') !== $user) return null;
  if (!in_array(($fixture['status'] ?? ''), ['pending', 'watching'], true)) return null;
  return ['roundIndex' => $roundIndex, 'fixtureIndex' => $fixtureIndex, 'fixture' => $fixture];
}

function manager_prepare_requested_user_fixture(array &$state, array &$season, string $user, array $data, int $now): ?int {
  $target = manager_requested_user_fixture($season, $user, $data);
  $roundIndex = manager_pending_round_index($season);
  if (!$target || $roundIndex === null || $roundIndex >= (int)$target['roundIndex']) return $roundIndex;
  $targetRound = (int)$target['roundIndex'];
  $guard = 0;
  while ($roundIndex !== null && $roundIndex < $targetRound && $guard++ < 40) {
    if (manager_has_watching($season)) return $roundIndex;
    if (manager_round_continue_required($season, $roundIndex)) {
      $own = manager_user_round_fixture($season, $user, $roundIndex);
      if ($own && ($own['fixture']['status'] ?? '') === 'played' && !manager_has_round_continue($season, $user, $roundIndex)) {
        if (manager_fixture_result_hidden_for_viewer($own['fixture'], $user, $now)) return $roundIndex;
        manager_mark_round_continue($season, $user, $roundIndex, $now);
      }
      $split = manager_round_ready_waiting($season, $roundIndex);
      if (!empty($split['waiting'])) return $roundIndex;
      $roundIndex = manager_pending_round_index($season);
      continue;
    }
    if (manager_round_human_users($season, $roundIndex)) return $roundIndex;
    simulate_manager_round_fixtures($state, $season, $roundIndex, false);
    $roundIndex = manager_pending_round_index($season);
    if ($roundIndex === null) {
      if (($season['status'] ?? '') === 'active' && empty($season['leagueResolved'])) start_manager_playoffs($season);
      elseif (($season['status'] ?? '') === 'playoff') advance_manager_playoffs($season);
      $roundIndex = manager_pending_round_index($season);
    }
  }
  return $roundIndex;
}

function manager_advance_without_pending_round(array &$state, array &$season): bool {
  $beforeStatus = (string)($season['status'] ?? '');
  $beforeRounds = count($season['rounds'] ?? []);
  $beforeResolved = !empty($season['leagueResolved']);
  if (($season['status'] ?? '') === 'active' && empty($season['leagueResolved'])) start_manager_playoffs($season);
  elseif (($season['status'] ?? '') === 'playoff') advance_manager_playoffs($season);
  if (!empty($season['leagueResolved']) && !manager_has_unplayed_phase($season, 'cup')) $season['status'] = 'finished';
  if (($season['status'] ?? '') === 'finished') apply_manager_progression($state, $season);
  return $beforeStatus !== (string)($season['status'] ?? '')
    || $beforeRounds !== count($season['rounds'] ?? [])
    || $beforeResolved !== !empty($season['leagueResolved']);
}

function manager_finish_cpu_only_pending_rounds(array &$state, array &$season, string $user, int $now, int $limit = 80): array {
  $simulated = 0;
  $steps = 0;
  $waiting = [];
  $blocked = '';
  $lastRoundIndex = null;
  for ($guard = 0; $guard < $limit; $guard++) {
    if (manager_has_watching($season)) {
      $blocked = 'watching';
      break;
    }
    $roundIndex = manager_pending_round_index($season);
    if ($roundIndex === null) {
      if (!manager_advance_without_pending_round($state, $season)) break;
      $steps++;
      if (($season['status'] ?? '') === 'finished') break;
      continue;
    }
    $lastRoundIndex = $roundIndex;
    if (manager_round_continue_required($season, $roundIndex)) {
      $split = manager_round_ready_waiting($season, $roundIndex);
      if (!empty($split['waiting'])) {
        $waiting = $split['waiting'];
        $blocked = 'round_continue_wait';
        break;
      }
    }
    $own = manager_user_round_fixture($season, $user, $roundIndex);
    if ($own && in_array(($own['fixture']['status'] ?? ''), ['pending', 'watching'], true)) {
      $blocked = 'user_fixture';
      break;
    }
    $roundHumans = manager_round_human_users($season, $roundIndex);
    if ($roundHumans) {
      $split = manager_round_ready_waiting($season, $roundIndex);
      $waiting = $split['waiting'];
      $blocked = 'human_round';
      break;
    }
    $count = simulate_manager_round_fixtures($state, $season, $roundIndex, false);
    if ($count <= 0) {
      $blocked = 'no_cpu_fixtures';
      break;
    }
    $simulated += $count;
    $steps++;
  }
  return [
    'simulated' => $simulated,
    'steps' => $steps,
    'waiting' => $waiting,
    'blocked' => $blocked,
    'roundIndex' => $lastRoundIndex,
    'guardLimit' => $steps >= $limit,
    'finished' => ($season['status'] ?? '') === 'finished',
  ];
}

function manager_user_round_continue_pending(array $season, string $user, int $roundIndex): bool {
  if ($user === '' || !manager_round_continue_required($season, $roundIndex)) return false;
  $entry = manager_user_round_fixture($season, $user, $roundIndex);
  if (!$entry) return false;
  if (manager_fixture_result_hidden_for_viewer($entry['fixture'], $user)) return false;
  return ($entry['fixture']['status'] ?? '') === 'played' && !manager_has_round_continue($season, $user, $roundIndex);
}

function manager_round_ready_waiting(array $season, int $roundIndex): array {
  $ready = [];
  $waiting = [];
  $requiresContinue = manager_round_continue_required($season, $roundIndex);
  foreach (manager_round_human_users($season, $roundIndex) as $name) {
    $entry = manager_user_round_fixture($season, $name, $roundIndex);
    if (!$entry) {
      $ready[] = $name;
      continue;
    }
    $fixture = $entry['fixture'];
    $status = (string)($fixture['status'] ?? 'pending');
    if ($status === 'played') {
      if ($requiresContinue && !manager_has_round_continue($season, $name, $roundIndex)) $waiting[] = $name;
      else $ready[] = $name;
      continue;
    }
    $humans = manager_fixture_human_users($season, $fixture);
    $key = manager_sim_fixture_key($season, (int)$entry['roundIndex'], (int)$entry['fixtureIndex']);
    if ($status === 'pending' && count($humans) > 1 && !empty($season['simVotes'][$key][$name])) {
      $ready[] = $name;
      continue;
    }
    $waiting[] = $name;
  }
  return ['ready' => $ready, 'waiting' => $waiting];
}

function public_manager_sim_vote(array $season, string $viewer = ''): ?array {
  $roundIndex = manager_pending_round_index($season);
  if ($roundIndex === null) return null;
  if (count(array_values($season['humanManagers'] ?? [])) <= 1) return null;
  $humans = manager_round_human_users($season, $roundIndex);
  if (!$humans) return null;
  $key = manager_sim_round_key($season, $roundIndex);
  $split = manager_round_ready_waiting($season, $roundIndex);
  $ready = $split['ready'];
  $waiting = $split['waiting'];
  if (!$waiting) return null;
  $viewerEntry = $viewer !== '' ? manager_user_round_fixture($season, $viewer, $roundIndex) : null;
  $viewerStatus = is_array($viewerEntry) ? (string)($viewerEntry['fixture']['status'] ?? '') : '';
  $continueRequired = manager_round_continue_required($season, $roundIndex);
  return [
    'key' => $key,
    'roundIndex' => $roundIndex,
    'round' => $season['rounds'][$roundIndex]['round'] ?? '',
    'ready' => $ready,
    'waiting' => $waiting,
    'readyCount' => count($ready),
    'neededCount' => count($humans),
    'mineReady' => $viewer !== '' && in_array($viewer, $ready, true),
    'continueRequired' => $continueRequired,
    'mineContinuePending' => manager_user_round_continue_pending($season, $viewer, $roundIndex),
    'mineFixtureStatus' => $viewerStatus,
    'mineRoundPlayed' => $viewerStatus === 'played',
    'passiveRoundWait' => $viewerStatus === 'played',
    'allReady' => false,
  ];
}

function public_manager_watch_vote(array $season, string $viewer = ''): ?array {
  $roundIndex = manager_pending_round_index($season);
  if ($roundIndex === null) return null;
  foreach (($season['rounds'][$roundIndex]['fixtures'] ?? []) as $fixtureIndex => $fixture) {
    if (($fixture['status'] ?? '') !== 'pending') continue;
    $humans = manager_fixture_human_users($season, $fixture);
    if (count($humans) <= 1) continue;
    $key = manager_watch_fixture_key($season, $roundIndex, (int)$fixtureIndex);
    $allVotes = isset($season['watchVotes']) && is_array($season['watchVotes']) ? $season['watchVotes'] : [];
    $votes = $allVotes[$key] ?? [];
    if (!is_array($votes)) $votes = [];
    $ready = array_values(array_filter($humans, fn($human) => !empty($votes[$human])));
    if (!$ready) continue;
    $waiting = array_values(array_filter($humans, fn($human) => empty($votes[$human])));
    if (!$waiting) return null;
    return [
      'key' => $key,
      'mode' => 'watch',
      'roundIndex' => $roundIndex,
      'fixtureIndex' => (int)$fixtureIndex,
      'round' => $season['rounds'][$roundIndex]['round'] ?? '',
      'ready' => $ready,
      'waiting' => $waiting,
      'readyCount' => count($ready),
      'neededCount' => count($humans),
      'mineReady' => $viewer !== '' && in_array($viewer, $ready, true),
      'continueRequired' => false,
      'mineContinuePending' => false,
      'allReady' => false,
    ];
  }
  return null;
}

function manager_sim_season_key(array $season): string {
  $roundIndex = manager_pending_round_index($season);
  $played = 0;
  foreach (($season['rounds'] ?? []) as $round) {
    foreach (($round['fixtures'] ?? []) as $fixture) {
      if (($fixture['status'] ?? '') === 'played') $played++;
    }
  }
  return 'season:' . (string)($season['id'] ?? '') . ':' . (string)($season['status'] ?? 'active') . ':' . (string)($roundIndex ?? 'done') . ':' . $played;
}

function public_manager_sim_season_vote(array $season, string $viewer = ''): ?array {
  if (($season['status'] ?? '') === 'finished') return null;
  $humans = array_values($season['humanManagers'] ?? []);
  if (count($humans) <= 1) return null;
  $key = manager_sim_season_key($season);
  $votes = $season['simSeasonVotes'][$key] ?? [];
  $ready = array_values(array_filter($humans, fn($name) => !empty($votes[$name])));
  $waiting = array_values(array_filter($humans, fn($name) => empty($votes[$name])));
  return [
    'key' => $key,
    'ready' => $ready,
    'waiting' => $waiting,
    'readyCount' => count($ready),
    'neededCount' => count($humans),
    'mineReady' => $viewer !== '' && !empty($votes[$viewer]),
    'allReady' => count($ready) >= count($humans),
  ];
}

function manager_base_season_year(): int {
  return 2026;
}

function manager_state_year(array $state): int {
  return max(manager_base_season_year(), (int)($state['managerYear'] ?? manager_base_season_year()));
}

function manager_season_year(array $season, ?array $state = null): int {
  $year = (int)($season['seasonYear'] ?? 0);
  if ($year <= 0 && $state !== null) $year = manager_state_year($state);
  return max(manager_base_season_year(), $year > 0 ? $year : manager_base_season_year());
}

function manager_estimated_start_balance(array $user): int {
  $finance = $user['managerFinance'] ?? [];
  $level = clamp_int((int)($user['managerDivisionLevel'] ?? 5), 1, 5);
  $stored = (int)($finance['startBalance'] ?? 0);
  if ($stored > 0) return $stored;
  return manager_finance_estimated_start_balance(is_array($finance) ? $finance : [], $level);
}

function manager_finance_estimated_start_balance(array $finance, int $level): int {
  $loanBorrowed = (int)($finance['loanBorrowed'] ?? 0);
  $activeLoan = is_array($finance['loan'] ?? null) ? $finance['loan'] : null;
  if ($loanBorrowed <= 0 && $activeLoan) $loanBorrowed = (int)($activeLoan['cashBorrowed'] ?? $activeLoan['principal'] ?? 0);
  $estimated = (int)($finance['balance'] ?? 0)
    - (int)($finance['income'] ?? 0)
    - (int)($finance['seasonAwards'] ?? 0)
    - (int)($finance['transferIncome'] ?? 0)
    - $loanBorrowed
    + (int)($finance['wages'] ?? 0)
    + (int)($finance['transferSpent'] ?? 0)
    + (int)($finance['arenaSpent'] ?? 0)
    + (int)($finance['merchSpent'] ?? 0)
    + (int)($finance['staffSpent'] ?? 0)
    + (int)($finance['loanRepaid'] ?? 0);
  return max(manager_start_balance($level), $estimated);
}

function manager_finance_counter_keys(): array {
  return ['income', 'seasonAwards', 'gateIncome', 'merchIncome', 'bonusIncome', 'cupIncome', 'transferIncome', 'loanBorrowed', 'wages', 'transferSpent', 'arenaSpent', 'merchSpent', 'staffSpent', 'scoutSpent', 'loanRepaid', 'loanInterestPaid'];
}

function manager_finance_counter_snapshot(array $finance): array {
  $keys = manager_finance_counter_keys();
  $out = [];
  foreach ($keys as $key) $out[$key] = (int)($finance[$key] ?? 0);
  return $out;
}

function manager_finance_public_start_snapshot(array $finance): array {
  $out = [];
  foreach (manager_finance_counter_keys() as $key) {
    if (array_key_exists($key, $finance)) $out[$key] = (int)$finance[$key];
  }
  return $out;
}
