<?php
declare(strict_types=1);

function manager_base_arena_capacity(int $level): int {
  return match (clamp_int($level, 1, 5)) {
    1 => 32000,
    2 => 18000,
    3 => 10500,
    4 => 6500,
    default => 3500,
  };
}

function manager_arena_expansion_option(array $finance): ?array {
  $level = clamp_int((int)($finance['arenaLevel'] ?? 0), 0, 10);
  if ($level >= 10) return null;
  $next = $level + 1;
  $seats = 500 + ($next - 1) * 250;
  $cost = manager_round_money((int)round($seats * (70 + $next * 35)), 5000);
  return [
    'level' => $next,
    'seats' => $seats,
    'cost' => $cost,
    'rounds' => $next * 5,
  ];
}

function manager_merch_boost_pct(int $level): int {
  return clamp_int($level, 0, 5) * 15;
}

function manager_merch_upgrade_option(array $finance, int $divisionLevel): ?array {
  $level = clamp_int((int)($finance['merchLevel'] ?? 0), 0, 5);
  if ($level >= 5) return null;
  $next = $level + 1;
  $base = match (clamp_int($divisionLevel, 1, 5)) {
    1 => 900000,
    2 => 300000,
    3 => 110000,
    4 => 55000,
    default => 20000,
  };
  $cost = manager_round_money((int)round($base * $next * (1 + (($next - 1) * 0.35))), 5000);
  return [
    'level' => $next,
    'cost' => $cost,
    'boostPct' => manager_merch_boost_pct($next),
    'currentBoostPct' => manager_merch_boost_pct($level),
    'maxLevel' => 5,
  ];
}

function manager_attendance_for_match(int $capacity, int $level, int $rank, int $teams, int $gf, int $ga, string $phase): int {
  $capacity = max(500, $capacity);
  $teams = max(1, $teams);
  $rank = clamp_int($rank, 1, $teams);
  $success = $teams > 1 ? ($teams - $rank) / ($teams - 1) : 0.5;
  $rate = 0.42 + $success * 0.32;
  if ($gf > $ga) $rate += 0.05;
  elseif ($gf < $ga) $rate -= 0.04;
  if ($phase === 'cup' || $phase === 'playoff') $rate += 0.08;
  $rate += mt_rand(-4, 4) / 100;
  return clamp_int((int)round($capacity * clamp_float($rate, 0.24, 0.96)), 150, $capacity);
}

function manager_match_income_breakdown(int $level, int $gf, int $ga, string $phase = 'league', int $capacity = 0, int $rank = 12, int $teams = 24, bool $homeGame = true, bool $cupWinner = false, int $merchLevel = 0): array {
  $level = clamp_int($level, 1, 5);
  [$base, $win, $draw] = match (clamp_int($level, 1, 5)) {
    1 => [760000, 430000, 140000],
    2 => [220000, 125000, 42000],
    3 => [82000, 52000, 18000],
    4 => [42000, 28500, 9000],
    default => [7500, 5000, 1400],
  };
  [$goalBonus, $swing] = match (clamp_int($level, 1, 5)) {
    1 => [35000, 90000],
    2 => [12000, 26000],
    3 => [4500, 9500],
    4 => [2500, 5200],
    default => [500, 1600],
  };
  $cupExtra = match ($level) {
    1 => 220000,
    2 => 90000,
    3 => 35000,
    4 => 16000,
    default => 8000,
  };
  $cupWinnerExtra = match ($level) {
    1 => 180000,
    2 => 70000,
    3 => 28000,
    4 => 14000,
    default => 6000,
  };
  $phase = strtolower(trim($phase));
  $isCup = $phase === 'cup' || $phase === 'playoff';
  if (!$isCup) {
    $winMultiplier = match ($level) {
      1 => 1.0,
      2 => 1.05,
      3 => 1.15,
      4 => 1.45,
      default => 2.4,
    };
    $win = (int)round($win * $winMultiplier);
  }
  $resultBonus = $gf > $ga ? $win : ($gf === $ga ? $draw : 0);
  $goalMoney = min(4, max(0, $gf)) * $goalBonus;
  $variance = mt_rand(-$swing, $swing);
  $attendance = $homeGame ? manager_attendance_for_match($capacity > 0 ? $capacity : manager_base_arena_capacity($level), $level, $rank, $teams, $gf, $ga, $phase) : 0;
  $ticketPrice = match ($level) {
    1 => 36,
    2 => 25,
    3 => 17,
    4 => 12,
    default => 7,
  };
  $gateBase = $homeGame ? ($attendance * $ticketPrice) : (int)round($base * 0.18);
  $gate = manager_round_money(max((int)round($base * ($homeGame ? 0.30 : 0.12)), $gateBase + $variance + ($homeGame && $isCup ? (int)round($cupExtra * 0.25) : 0)));
  $merchBase = max(0, (int)round($attendance * ($level <= 2 ? 3.2 : ($level <= 4 ? 1.8 : 1.1)) + max(0, $gf) * $goalBonus * 0.28 + ($gf > $ga ? $base * 0.035 : 0)));
  $merch = manager_round_money((int)round($merchBase * (1 + manager_merch_boost_pct($merchLevel) / 100)));
  $bonus = manager_round_money(max(0, $resultBonus + (int)round($goalMoney * 0.65)));
  $cup = $isCup ? manager_round_money($cupExtra + ($cupWinner ? $cupWinnerExtra : 0)) : 0;
  return [
    'attendance' => $attendance,
    'gateIncome' => $gate,
    'merchIncome' => $merch,
    'bonusIncome' => $bonus,
    'cupIncome' => $cup,
  ];
}

function manager_match_income(int $level, int $gf, int $ga, string $phase = 'league', bool $cupWinner = false): int {
  $breakdown = manager_match_income_breakdown($level, $gf, $ga, $phase, 0, 12, 24, true, $cupWinner);
  return (int)($breakdown['gateIncome'] ?? 0)
    + (int)($breakdown['merchIncome'] ?? 0)
    + (int)($breakdown['bonusIncome'] ?? 0)
    + (int)($breakdown['cupIncome'] ?? 0);
}

function manager_league_net_target_with_loan(int $gf, int $ga): int {
  if ($gf > $ga) return 25000;
  if ($gf === $ga) return 0;
  return -5000;
}

function manager_user_rank_in_season(array $season, string $user): int {
  foreach (manager_ranked_rows($season) as $index => $row) {
    if ((string)($row['user'] ?? '') === $user) return $index + 1;
  }
  return count($season['table'] ?? []) ?: 1;
}

function manager_season_finance_award(int $level, int $rank, int $teams, string $outcome): int {
  $level = clamp_int($level, 1, 5);
  $teams = max(1, $teams);
  $rank = clamp_int($rank, 1, $teams);
  $placeRate = $teams > 1 ? ($teams - $rank) / ($teams - 1) : 1.0;
  [$base, $placeMax, $promotion] = match ($level) {
    1 => [18000000, 22000000, 0],
    2 => [4500000, 5500000, 4000000],
    3 => [900000, 1200000, 2000000],
    4 => [450000, 650000, 1000000],
    default => [95000, 65000, 500000],
  };
  $award = $base + (int)round($placeMax * $placeRate);
  if ($outcome === 'promoted') $award += $promotion;
  return max(0, (int)round($award / 5000) * 5000);
}

function ensure_manager_finance(array &$user): void {
  $level = clamp_int((int)($user['managerDivisionLevel'] ?? 5), 1, 5);
  if ($level === 5 && has_team($user)) ensure_user_squad($user);
  $defaultBalance = manager_start_balance($level, $user);
  if (!isset($user['managerFinance']) || !is_array($user['managerFinance']) || (int)($user['managerFinance']['version'] ?? 0) < 2) {
    $user['managerFinance'] = [
      'version' => 2,
      'startBalance' => $defaultBalance,
      'balance' => $defaultBalance,
      'income' => 0,
      'seasonAwards' => 0,
      'gateIncome' => 0,
      'merchIncome' => 0,
      'merchLevel' => 0,
      'merchSpent' => 0,
      'bonusIncome' => 0,
      'cupIncome' => 0,
      'arenaLevel' => 0,
      'arenaCapacity' => manager_base_arena_capacity($level),
      'arenaLastAttendance' => 0,
      'arenaTotalAttendance' => 0,
      'arenaHomeMatches' => 0,
      'arenaSpent' => 0,
      'arenaBuild' => null,
	      'wages' => 0,
	      'transferSpent' => 0,
	      'transferIncome' => 0,
	      'staffSpent' => 0,
	      'hiredManager' => null,
	      'hiredScout' => null,
	      'scoutSearch' => null,
	      'scoutSpent' => 0,
	      'loan' => null,
      'lastLoan' => null,
      'loanBorrowed' => 0,
      'loanRepaid' => 0,
	      'loanInterestPaid' => 0,
	      'lastMatchFinance' => null,
	      'debtWarning' => null,
	      'pointDeductionPending' => null,
	      'pointDeductionApplied' => null,
		    ];
		  }
		  $user['managerFinance'] += ['version' => 2, 'startBalance' => 0, 'balance' => $defaultBalance, 'income' => 0, 'seasonAwards' => 0, 'gateIncome' => 0, 'merchIncome' => 0, 'merchLevel' => 0, 'merchSpent' => 0, 'bonusIncome' => 0, 'cupIncome' => 0, 'arenaLevel' => 0, 'arenaCapacity' => manager_base_arena_capacity($level), 'arenaLastAttendance' => 0, 'arenaTotalAttendance' => 0, 'arenaHomeMatches' => 0, 'arenaSpent' => 0, 'arenaBuild' => null, 'wages' => 0, 'transferSpent' => 0, 'transferIncome' => 0, 'staffSpent' => 0, 'hiredManager' => null, 'hiredScout' => null, 'scoutSearch' => null, 'scoutSpent' => 0, 'loan' => null, 'lastLoan' => null, 'loanBorrowed' => 0, 'loanRepaid' => 0, 'loanInterestPaid' => 0, 'lastMatchFinance' => null, 'debtWarning' => null, 'pointDeductionPending' => null, 'pointDeductionApplied' => null];
  if ((int)($user['managerFinance']['startBalance'] ?? 0) <= 0) {
    $user['managerFinance']['startBalance'] = manager_finance_estimated_start_balance($user['managerFinance'], $level);
  }
  if (is_array($user['managerFinance']['loan'] ?? null) && (int)($user['managerFinance']['loan']['remaining'] ?? 0) <= 0) {
    $user['managerFinance']['lastLoan'] = $user['managerFinance']['loan'] + ['paidAt' => time()];
    $user['managerFinance']['loan'] = null;
  }
  if ((int)($user['managerFinance']['arenaCapacity'] ?? 0) <= 0) $user['managerFinance']['arenaCapacity'] = manager_base_arena_capacity($level);
  $user['managerFinance']['merchLevel'] = clamp_int((int)($user['managerFinance']['merchLevel'] ?? 0), 0, 5);
  if (!is_array($user['managerFinance']['arenaBuild'] ?? null)) $user['managerFinance']['arenaBuild'] = null;
  if (!is_array($user['managerFinance']['loan'] ?? null)) $user['managerFinance']['loan'] = null;
  if (empty($user['managerDivisionLevel'])) $user['managerDivisionLevel'] = 5;
}

function manager_debt_warning_payload(int $seasonYear, int $balance, string $fromSeasonId = ''): array {
  return [
    'reason' => 'negative_balance',
    'seasonYear' => $seasonYear,
    'deadlineSeasonYear' => $seasonYear,
    'points' => 10,
    'balance' => $balance,
    'fromSeasonId' => $fromSeasonId,
    'createdAt' => time(),
  ];
}

function manager_debt_penalty_payload(int $seasonYear, int $balance, string $fromSeasonId = ''): array {
  return [
    'reason' => 'negative_balance',
    'seasonYear' => $seasonYear,
    'points' => 10,
    'balance' => $balance,
    'fromSeasonId' => $fromSeasonId,
    'createdAt' => time(),
  ];
}

function manager_public_finance_discipline(?array $payload): ?array {
  if (!$payload) return null;
  $seasonYear = (int)($payload['seasonYear'] ?? 0);
  return [
    'reason' => (string)($payload['reason'] ?? 'negative_balance'),
    'seasonYear' => $seasonYear,
    'deadlineSeasonYear' => (int)($payload['deadlineSeasonYear'] ?? $seasonYear),
    'points' => max(1, (int)($payload['points'] ?? 10)),
    'balance' => (int)($payload['balance'] ?? 0),
    'fromSeasonId' => (string)($payload['fromSeasonId'] ?? ''),
    'seasonId' => (string)($payload['seasonId'] ?? ''),
    'createdAt' => (int)($payload['createdAt'] ?? 0),
    'appliedAt' => (int)($payload['appliedAt'] ?? 0),
  ];
}

function manager_division_name(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_level_from_name(string $name, int $fallback = 5): int {
  $key = strtolower(trim($name));
  $map = [
    'premier league' => 1,
    'championship' => 2,
    'league one' => 3,
    'league 1' => 3,
    'league two' => 4,
    'league 2' => 4,
    'national league' => 5,
  ];
  return $map[$key] ?? clamp_int($fallback, 1, 5);
}

function manager_market_player_source_level(array $player, int $fallbackLevel): int {
  foreach (['sourceDivision', 'purchasedFromDivision', 'originDivisionName'] as $key) {
    $name = trim((string)($player[$key] ?? ''));
    if ($name !== '') return manager_division_level_from_name($name, $fallbackLevel);
  }
  return clamp_int($fallbackLevel, 1, 5);
}

function manager_market_division_premium_pct(int $gap): int {
  return match (clamp_int($gap, 0, 4)) {
    1 => 12,
    2 => 35,
    3 => 85,
    4 => 140,
    default => 0,
  };
}

function manager_market_division_rule(array $player, int $buyerLevel, int $basePrice): array {
  $buyerLevel = clamp_int($buyerLevel, 1, 5);
  $sourceLevel = manager_market_player_source_level($player, $buyerLevel);
  $gap = max(0, $buyerLevel - $sourceLevel);
  $premiumPct = manager_market_division_premium_pct($gap);
  $price = max(1000, $basePrice);
  if ($premiumPct > 0) $price = max($price, (int)round(($price * (1 + $premiumPct / 100)) / 1000) * 1000);
  return [
    'buyerLevel' => $buyerLevel,
    'sourceLevel' => $sourceLevel,
    'gap' => $gap,
    'premiumPct' => $premiumPct,
    'listedPrice' => max(1000, $basePrice),
    'price' => $price,
  ];
}

function manager_market_division_reject_chance(int $gap, float $offerRatio): int {
  $base = match (clamp_int($gap, 0, 4)) {
    1 => 12,
    2 => 34,
    3 => 78,
    4 => 92,
    default => 0,
  };
  if ($base <= 0) return 0;
  $min = $gap >= 3 ? 24 : ($gap === 2 ? 4 : 0);
  return clamp_int((int)round($base - max(0.0, $offerRatio - 1.0) * 45), $min, 96);
}

function public_finance(array $user): array {
  $finance = $user['managerFinance'] ?? [];
  $level = clamp_int((int)($user['managerDivisionLevel'] ?? 5), 1, 5);
  $squad = $user['squad'] ?? ['starters' => [], 'bench' => []];
  $build = is_array($finance['arenaBuild'] ?? null) ? $finance['arenaBuild'] : null;
  $homeMatches = max(0, (int)($finance['arenaHomeMatches'] ?? 0));
  $nextArena = manager_arena_expansion_option($finance);
  $merchLevel = clamp_int((int)($finance['merchLevel'] ?? 0), 0, 5);
  $nextMerch = manager_merch_upgrade_option($finance, $level);
  $loan = manager_public_loan($finance, $level);
  return [
    'startBalance' => (int)($loan['startBalance'] ?? manager_finance_estimated_start_balance($finance, $level)),
    'balance' => (int)($finance['balance'] ?? 0),
    'income' => (int)($finance['income'] ?? 0),
    'seasonAwards' => (int)($finance['seasonAwards'] ?? 0),
    'gateIncome' => (int)($finance['gateIncome'] ?? 0),
    'merchIncome' => (int)($finance['merchIncome'] ?? 0),
    'merchLevel' => $merchLevel,
    'merchMaxLevel' => 5,
    'merchBoostPct' => manager_merch_boost_pct($merchLevel),
    'merchNext' => $nextMerch,
    'merchSpent' => (int)($finance['merchSpent'] ?? 0),
    'bonusIncome' => (int)($finance['bonusIncome'] ?? 0),
    'cupIncome' => (int)($finance['cupIncome'] ?? 0),
    'arenaLevel' => clamp_int((int)($finance['arenaLevel'] ?? 0), 0, 10),
    'arenaMaxLevel' => 10,
    'arenaCapacity' => (int)($finance['arenaCapacity'] ?? manager_base_arena_capacity($level)),
    'arenaLastAttendance' => (int)($finance['arenaLastAttendance'] ?? 0),
    'arenaAverageAttendance' => $homeMatches > 0 ? (int)round((int)($finance['arenaTotalAttendance'] ?? 0) / $homeMatches) : 0,
    'arenaHomeMatches' => $homeMatches,
    'arenaSpent' => (int)($finance['arenaSpent'] ?? 0),
    'arenaBuild' => $build,
    'arenaNext' => $nextArena,
	    'wages' => (int)($finance['wages'] ?? 0),
	    'transferSpent' => (int)($finance['transferSpent'] ?? 0),
	    'transferIncome' => (int)($finance['transferIncome'] ?? 0),
	    'staffSpent' => (int)($finance['staffSpent'] ?? 0),
	    'scoutSpent' => (int)($finance['scoutSpent'] ?? 0),
	    'loanBorrowed' => (int)($finance['loanBorrowed'] ?? 0),
	    'loanRepaid' => (int)($finance['loanRepaid'] ?? 0),
	    'loanInterestPaid' => (int)($finance['loanInterestPaid'] ?? 0),
	    'lastMatchFinance' => is_array($finance['lastMatchFinance'] ?? null) ? $finance['lastMatchFinance'] : null,
	    'loan' => $loan,
	    'hiredManager' => manager_public_hired_manager($finance),
	    'staffManagers' => manager_staff_manager_catalog(),
	    'hiredScout' => manager_public_hired_scout($finance),
	    'scoutCatalog' => manager_scout_catalog(),
	    'scoutSearch' => manager_public_scout_search($finance),
	    'scoutStatOptions' => manager_scout_stat_options(),
	    'debtWarning' => manager_public_finance_discipline(is_array($finance['debtWarning'] ?? null) ? $finance['debtWarning'] : null),
	    'pointDeductionPending' => manager_public_finance_discipline(is_array($finance['pointDeductionPending'] ?? null) ? $finance['pointDeductionPending'] : null),
	    'pointDeductionApplied' => manager_public_finance_discipline(is_array($finance['pointDeductionApplied'] ?? null) ? $finance['pointDeductionApplied'] : null),
	    'weeklyWages' => squad_wages($squad),
	    'squadValue' => squad_value($squad),
    'startersValue' => squad_value($squad, 'starters'),
    'benchValue' => squad_value($squad, 'bench'),
    'divisionLevel' => $level,
    'divisionName' => manager_division_name($level),
  ];
}

function legacy_player_market_pool(): array {
  static $pool = null;
  if ($pool !== null) return $pool;
  $cache = legacy_player_name_cache();
  $teamsBySlug = [];
  foreach (training_divisions() as $division) {
    foreach (($division['teams'] ?? []) as $team) {
      $sourceId = source_team_id((string)($team['sourceId'] ?? $team['id'] ?? ''));
      foreach (legacy_team_slug_candidates($sourceId) as $slug) {
        $teamsBySlug[$slug] = [
          'teamId' => $sourceId,
          'teamName' => (string)($team['name'] ?? strtoupper($sourceId)),
          'divisionName' => (string)($division['name'] ?? ''),
          'baseRating' => (int)($team['baseRating'] ?? team_base_rating($sourceId)),
        ];
      }
    }
  }
  $pool = [];
  foreach ($cache as $slug => $players) {
    if (empty($teamsBySlug[$slug]) || !is_array($players)) continue;
    foreach ($players as $index => $player) {
      if (empty($player['name'])) continue;
      $pool[] = $teamsBySlug[$slug] + [
        'sourceSlug' => (string)$slug,
        'sourceIndex' => (int)$index,
        'name' => (string)$player['name'],
        'shirtName' => (string)($player['shirtName'] ?? $player['name']),
        'legacyRole' => (string)($player['legacyRole'] ?? ''),
        'legacyNumber' => isset($player['legacyNumber']) ? (int)$player['legacyNumber'] : null,
      ];
    }
  }
  return $pool;
}

function market_role_from_legacy(string $legacyRole, int $seed): string {
  $role = str_replace(['-', '_'], ' ', strtoupper($legacyRole));
  if (str_contains($role, 'GOAL') || $role === 'GK') return 'GK';
  if (str_contains($role, 'LEFT BACK')) return 'LB';
  if (str_contains($role, 'RIGHT BACK')) return 'RB';
  if (str_contains($role, 'CENTRE BACK') || str_contains($role, 'CENTER BACK')) return 'CB';
  if (str_contains($role, 'LEFT WING') || str_contains($role, 'LEFT MID')) return 'LM';
  if (str_contains($role, 'RIGHT WING') || str_contains($role, 'RIGHT MID')) return 'RM';
  if (str_contains($role, 'ATTACK') || str_contains($role, 'FORWARD') || $role === 'FW') return 'ST';
  if (str_contains($role, 'MID') || $role === 'MF') {
    return ['CM', 'RM', 'LM'][abs($seed) % 3];
  }
  if (str_contains($role, 'DEF') || $role === 'DF') {
    return ['CB', 'RB', 'LB'][abs($seed) % 3];
  }
  return ['CM', 'CB', 'ST', 'RM', 'LM'][abs($seed) % 5];
}

function market_player(int $index): array {
  $pool = legacy_player_market_pool();
  $source = $pool ? $pool[(int)(crc32('market-source-' . $index . '-' . time()) % count($pool))] : null;
  $seed = (int)crc32(($source['sourceSlug'] ?? 'market') . '-' . ($source['sourceIndex'] ?? $index) . '-' . time());
  $role = $source ? market_role_from_legacy((string)($source['legacyRole'] ?? ''), $seed) : ['GK', 'CB', 'RB', 'LB', 'CM', 'RM', 'LM', 'ST'][$index % 8];
  $base = $source
    ? clamp_int((int)($source['baseRating'] ?? 50) + (int)($seed % 13) - 6, 30, 92)
    : clamp_int(36 + (int)(crc32('market-' . $index . '-' . time()) % 45), 30, 88);
  $stats = [];
  foreach (player_stat_keys($role === 'GK') as $stat) {
    $stats[$stat] = stat_value($base, $role, $stat);
  }
  $player = [
    'id' => 'M' . bin2hex(random_bytes(4)),
    'name' => $source ? (string)$source['name'] : player_name('home', $index + 30, $role),
    'shirtName' => $source ? (string)$source['shirtName'] : '',
    'sourceTeam' => $source ? (string)$source['teamName'] : 'FREE AGENT',
    'sourceTeamId' => $source ? (string)$source['teamId'] : '',
    'sourceDivision' => $source ? (string)$source['divisionName'] : '',
	    'legacyNumber' => $source['legacyNumber'] ?? null,
	    'role' => $role,
	    'naturalRole' => $role,
	    'slotRole' => $role,
	    'rating' => average_stats($stats),
    'stats' => $stats,
    'homeX' => 0.5,
    'homeY' => 0.62,
    'x' => 0.5,
	    'y' => 0.62,
	  ];
  ensure_player_stats($player);
  ensure_player_age_profile($player, $source ? manager_cpu_id((string)$source['teamId']) : 'market', $source ? (string)$source['teamId'] : '', $source ? (string)$source['teamName'] : 'FREE AGENT', $source ? (string)$source['divisionName'] : '');
	  $player['price'] = player_value($player);
	  return $player;
}

function manager_market_listing_seller(array $player): string {
  return clean_name((string)($player['marketListedByUser'] ?? ''));
}

function manager_market_listing_seller_player_id(array $player): string {
  return trim((string)($player['sellerPlayerId'] ?? $player['id'] ?? ''));
}

function manager_market_listing_is_unsettled(array $player): bool {
  return manager_market_listing_seller($player) !== ''
    && array_key_exists('marketPayoutSettled', $player)
    && empty($player['marketPayoutSettled']);
}

function manager_market_listing_seller_has_player(array $state, array $player): bool {
  $seller = manager_market_listing_seller($player);
  $sellerPlayerId = manager_market_listing_seller_player_id($player);
  if ($seller === '' || $sellerPlayerId === '' || !isset($state['users'][$seller]) || !is_array($state['users'][$seller])) return false;
  return find_squad_player($state['users'][$seller]['squad'] ?? [], $sellerPlayerId) !== null;
}

function manager_market_listing_same_context(array $player, ?array $season, string $viewer = ''): bool {
  $seller = manager_market_listing_seller($player);
  if ($seller === '' || !manager_market_listing_is_unsettled($player)) return true;
  if (!is_array($season)) return $viewer === '' || $viewer === $seller;
  $humans = array_values(array_map(fn($name) => clean_name((string)$name), $season['humanManagers'] ?? []));
  if (!in_array($seller, $humans, true)) return false;
  $viewer = clean_name($viewer);
  if ($viewer !== '' && !in_array($viewer, $humans, true)) return false;
  $listingGameId = (string)($player['marketManagerGameId'] ?? '');
  $seasonGameId = manager_game_id($season);
  if ($listingGameId !== '' && $seasonGameId !== '' && $listingGameId !== $seasonGameId) return false;
  return true;
}

function manager_market_listing_is_active(array $state, array $player, ?array $season = null, string $viewer = ''): bool {
  return manager_market_listing_is_unsettled($player)
    && manager_market_listing_same_context($player, $season, $viewer)
    && manager_market_listing_seller_has_player($state, $player);
}

function manager_strip_human_market_listing_fields(array &$player): void {
  unset(
    $player['marketListedByUser'],
    $player['marketListedAt'],
    $player['marketSalePrice'],
    $player['marketPayoutSettled'],
    $player['sellerPlayerId'],
    $player['marketCpuOfferListedMatches'],
    $player['marketCpuOfferDelayMatches'],
    $player['marketCpuOfferAfterMatches'],
    $player['marketSeasonId'],
    $player['marketManagerGameId'],
    $player['marketHumanManagers']
  );
}

function manager_market_offer_available(array $state, array $offer, ?array $season = null, string $viewer = ''): bool {
  $marketPlayerId = (string)($offer['marketPlayerId'] ?? '');
  if ($marketPlayerId === '') return true;
  $seller = clean_name((string)($offer['seller'] ?? ''));
  foreach (($state['managerMarket'] ?? []) as $player) {
    if (!is_array($player) || (string)($player['id'] ?? '') !== $marketPlayerId) continue;
    if ($seller !== '' && manager_market_listing_seller($player) !== $seller) return false;
    return manager_market_listing_is_active($state, $player, $season, $viewer);
  }
  return false;
}

function manager_cleanup_stale_market_listings(array &$state, int $now = 0): int {
  if (!isset($state['managerMarket']) || !is_array($state['managerMarket'])) return 0;
  if ($now <= 0) $now = time();
  $invalidOfferMarketIds = [];
  $removed = 0;

  foreach ($state['managerMarket'] as $index => &$player) {
    if (!is_array($player)) continue;
    $seller = manager_market_listing_seller($player);
    if ($seller === '') continue;
    $marketPlayerId = (string)($player['id'] ?? '');
    if (!manager_market_listing_is_unsettled($player)) {
      if ($marketPlayerId !== '') $invalidOfferMarketIds[$marketPlayerId] = true;
      manager_strip_human_market_listing_fields($player);
      continue;
    }
    if (!manager_market_listing_seller_has_player($state, $player)) {
      if ($marketPlayerId !== '') $invalidOfferMarketIds[$marketPlayerId] = true;
      unset($state['managerMarket'][$index]);
      $removed++;
    }
  }
  unset($player);

  if ($removed > 0) $state['managerMarket'] = array_values($state['managerMarket']);
  if ($invalidOfferMarketIds && isset($state['managerTransferOffers']) && is_array($state['managerTransferOffers'])) {
    foreach ($state['managerTransferOffers'] as $offerId => $offer) {
      if (!is_array($offer) || ($offer['status'] ?? 'pending') !== 'pending') continue;
      $marketPlayerId = (string)($offer['marketPlayerId'] ?? '');
      if ($marketPlayerId === '' || empty($invalidOfferMarketIds[$marketPlayerId])) continue;
      $buyer = clean_name((string)($offer['buyer'] ?? ''));
      if ($buyer !== '' && !is_cpu_manager($buyer)) manager_add_transfer_notice($state, $buyer, 'rejected', $offer, $now);
      unset($state['managerTransferOffers'][$offerId]);
    }
  }
  return $removed;
}

function ensure_manager_market(array &$state): void {
  if (!isset($state['managerMarket']) || !is_array($state['managerMarket'])) $state['managerMarket'] = [];
  manager_cleanup_stale_market_listings($state);
  foreach ($state['managerMarket'] as &$player) {
    if (!is_array($player)) continue;
    $ownerId = clean_name((string)($player['marketListedByUser'] ?? ''));
    if ($ownerId === '') $ownerId = (string)($player['originOwnerId'] ?? $player['sourceTeamId'] ?? 'market');
    ensure_player_age_profile($player, $ownerId, (string)($player['sourceTeamId'] ?? $player['originTeamId'] ?? ''), (string)($player['sourceTeam'] ?? $player['originTeamName'] ?? ''), (string)($player['sourceDivision'] ?? $player['originDivisionName'] ?? ''));
  }
  unset($player);
  $target = MANAGER_MARKET_TARGET_PLAYERS;
  if ((int)($state['managerMarketVersion'] ?? 0) < MANAGER_MARKET_VERSION || count($state['managerMarket']) < $target) {
    $index = count($state['managerMarket']);
    $guard = 0;
    while (count($state['managerMarket']) < $target && $guard++ < $target * 3) {
      $state['managerMarket'][] = market_player($index + $guard);
    }
    $state['managerMarketVersion'] = MANAGER_MARKET_VERSION;
  }
}

function public_player(array $player): array {
  ensure_player_stats($player);
  ensure_player_age_profile($player, (string)($player['originOwnerId'] ?? $player['sourceTeamId'] ?? ''), (string)($player['originTeamId'] ?? $player['sourceTeamId'] ?? ''), (string)($player['originTeamName'] ?? $player['sourceTeam'] ?? ''), (string)($player['originDivisionName'] ?? $player['sourceDivision'] ?? ''));
  apply_player_nationality_fields($player, (string)($player['originTeamId'] ?? $player['sourceTeamId'] ?? ''), (string)($player['originTeamName'] ?? $player['sourceTeam'] ?? ''));
  $baseRating = (int)($player['rating'] ?? 50);
  $slotRole = player_slot_role($player);
  $player['baseRating'] = $baseRating;
  $player['effectiveRating'] = player_effective_rating($player);
  $player['rating'] = $player['effectiveRating'];
  $player['naturalRole'] = player_natural_role($player);
  $player['displayRole'] = player_display_role($player);
  $player['role'] = $player['naturalRole'];
  $player['slotRole'] = $slotRole;
  $player['foot'] = player_preferred_foot($player);
  $player['footBonus'] = player_foot_slot_bonus($player, $slotRole);
  $player['positionPenalty'] = player_position_penalty_percent($player, $slotRole);
  return $player;
}

function public_manager_market(array $state, ?array $season = null, string $viewer = ''): array {
  $out = [];
  $viewer = clean_name($viewer);
  $viewerLevel = $viewer !== '' && isset($state['users'][$viewer]) ? manager_user_division_level($state, $viewer) : 0;
  foreach (array_values($state['managerMarket'] ?? []) as $player) {
    if (!is_array($player)) continue;
    if (manager_is_emergency_loan_player(is_array($player) ? $player : [])) continue;
    $settledHumanMarketListing = false;
    if (manager_market_listing_is_unsettled($player)) {
      if (!manager_market_listing_is_active($state, $player, $season, $viewer)) continue;
    } elseif (manager_market_listing_seller($player) !== '') {
      $settledHumanMarketListing = true;
      manager_strip_human_market_listing_fields($player);
    }
    $sourceTeamId = (string)($player['sourceTeamId'] ?? '');
    if (!$settledHumanMarketListing && $sourceTeamId !== '' && empty($player['marketListedByUser']) && manager_team_controlled_by_human($state, $season, $sourceTeamId)) continue;
    $public = public_player($player);
    if ($viewerLevel > 0 && clean_name((string)($player['marketListedByUser'] ?? '')) !== $viewer) {
      $listedPrice = max(1000, (int)($player['price'] ?? $player['marketSalePrice'] ?? $player['value'] ?? player_value($player)));
      $rule = manager_market_division_rule($player, $viewerLevel, $listedPrice);
      if ((int)$rule['gap'] > 0) {
        $public['marketBasePrice'] = $listedPrice;
        $public['price'] = (int)$rule['price'];
        $public['marketDivisionGap'] = (int)$rule['gap'];
        $public['marketPricePremiumPct'] = (int)$rule['premiumPct'];
        $public['marketSourceDivisionLevel'] = (int)$rule['sourceLevel'];
      }
    }
    $out[] = $public;
  }
  return $out;
}

function public_squad(array $user, ?array $squadOverride = null): array {
  $squad = $squadOverride ?? ($user['squad'] ?? []);
  $out = ['starters' => [], 'bench' => [], 'formation' => manager_user_formation($user), 'formationOptions' => manager_formation_options()];
	foreach (['starters', 'bench'] as $group) {
	    foreach (($squad[$group] ?? []) as $player) {
	      $player = public_player($player);
	      $player['value'] = player_value($player);
	      $player['wage'] = player_wage($player);
      $out[$group][] = $player;
    }
  }
  return $out;
}

function public_manager_context_squad(array &$state, string $user, ?array $season = null): array {
  if (empty($state['users'][$user]) || !is_array($state['users'][$user])) return ['starters' => [], 'bench' => [], 'formation' => '4-4-2', 'formationOptions' => manager_formation_options()];
  $participant = $season !== null ? manager_participant($season, $user) : null;
  $sourceUser = null;
  if ($season !== null && $participant !== null && !manager_global_user_matches_season($state, $season, $user)) {
    $sourceUser = manager_participant_user_snapshot($participant, $user);
  }
  if (!is_array($sourceUser)) {
    ensure_user_squad($state['users'][$user]);
    ensure_manager_finance($state['users'][$user]);
    $sourceUser = $state['users'][$user];
  }
  $squad = manager_apply_staff_boost_to_squad($sourceUser['squad'] ?? ['starters' => [], 'bench' => []], $sourceUser);
  if ($season !== null && manager_participant($season, $user) !== null) {
    $side = [
      'squad' => $squad,
      'strength' => squad_skill_points($squad),
      'matchStrength' => squad_match_skill_points($squad),
    ];
    $side = manager_apply_next_form_to_side_data($season, $user, $side);
    $squad = manager_annotate_squad_injuries($side['squad'] ?? $squad, $season, $user);
    $squad = manager_annotate_squad_training($squad, $season, $user);
    $squad = manager_annotate_squad_cards($squad, $season, $user, $state);
  }
  return public_squad($sourceUser, $squad);
}

function find_squad_player(array $squad, string $playerId): ?array {
  foreach (['starters', 'bench'] as $group) {
    foreach (($squad[$group] ?? []) as $index => $player) {
      if ((string)($player['id'] ?? '') === $playerId) return ['group' => $group, 'index' => $index, 'player' => $player];
    }
  }
  return null;
}

function manager_transfer_cash_offer(array $data, int $basePrice): int {
  $cash = array_key_exists('cashOffer', $data) ? (int)$data['cashOffer'] : (array_key_exists('bidPrice', $data) ? (int)$data['bidPrice'] : $basePrice);
  return max(0, (int)round($cash));
}

function manager_player_sale_base(array $player): int {
  $saleBase = player_value($player);
  if (!empty($player['purchasePrice']) && (int)$player['purchasePrice'] < $saleBase * 0.6) {
    $saleBase = (int)$player['purchasePrice'];
  }
  return max(25000, (int)round($saleBase * 0.62 / 25000) * 25000);
}

function manager_sale_chance(int $askPrice, int $basePrice): int {
  $basePrice = max(1, $basePrice);
  $ratio = $askPrice / $basePrice;
  return clamp_int((int)round(78 - (($ratio - 1.0) * 95)), 5, 96);
}

function manager_cpu_market_offer_cash(int $askPrice, int $basePrice): int {
  $askPrice = max(1000, $askPrice);
  $basePrice = max(1, $basePrice);
  $ratio = $askPrice / $basePrice;
  if ($ratio <= 1.05) {
    [$acceptAskChance, $minPct, $maxPct] = [48, 92, 99];
  } elseif ($ratio <= 1.25) {
    [$acceptAskChance, $minPct, $maxPct] = [32, 88, 98];
  } elseif ($ratio <= 1.60) {
    [$acceptAskChance, $minPct, $maxPct] = [18, 78, 96];
  } else {
    [$acceptAskChance, $minPct, $maxPct] = [7, 60, 90];
  }
  if (mt_rand(1, 100) <= $acceptAskChance) return $askPrice;
  $offer = $askPrice * (mt_rand($minPct, $maxPct) / 100);
  return min($askPrice, max(1000, (int)round($offer / 1000) * 1000));
}

function manager_user_played_match_count(?array $season, string $user): int {
  $user = clean_name($user);
  if ($user === '' || !is_array($season)) return 0;
  $played = 0;
  foreach (($season['rounds'] ?? []) as $round) {
    foreach (($round['fixtures'] ?? []) as $fixture) {
      if (($fixture['status'] ?? '') !== 'played') continue;
      if (($fixture['home'] ?? '') === $user || ($fixture['away'] ?? '') === $user) $played++;
    }
  }
  return $played;
}

function manager_schedule_cpu_market_offer(array &$player, ?array $season, string $seller, int $minMatches = 1, int $maxMatches = 10): void {
  $minMatches = max(1, $minMatches);
  $maxMatches = max($minMatches, $maxMatches);
  $played = manager_user_played_match_count($season, $seller);
  $delay = mt_rand($minMatches, $maxMatches);
  if (!isset($player['marketCpuOfferListedMatches'])) $player['marketCpuOfferListedMatches'] = $played;
  $player['marketCpuOfferDelayMatches'] = $delay;
  $player['marketCpuOfferAfterMatches'] = $played + $delay;
}

function manager_reschedule_cpu_market_offer(array &$state, ?array $season, string $seller, string $marketPlayerId, int $minMatches = 1, int $maxMatches = 4): void {
  $seller = clean_name($seller);
  if ($seller === '' || $marketPlayerId === '' || empty($state['managerMarket']) || !is_array($state['managerMarket'])) return;
  foreach ($state['managerMarket'] as &$player) {
    if (!is_array($player) || (string)($player['id'] ?? '') !== $marketPlayerId) continue;
    if (clean_name((string)($player['marketListedByUser'] ?? '')) !== $seller) continue;
    manager_schedule_cpu_market_offer($player, $season, $seller, $minMatches, $maxMatches);
    break;
  }
  unset($player);
}

function manager_add_sold_player_to_market(array &$state, string $seller, array $sellerUser, array $player, int $price, int $now, bool $payoutSettled = true, ?array $season = null): array {
  if (!isset($state['managerMarket']) || !is_array($state['managerMarket'])) $state['managerMarket'] = [];
  ensure_player_stats($player);
  $sellerPlayerId = (string)($player['id'] ?? '');
  $team = has_team($sellerUser) ? user_team($sellerUser) : ['id' => $seller, 'name' => strtoupper($seller)];
  $division = (string)($sellerUser['managerFinance']['divisionName'] ?? manager_division_name((int)($sellerUser['managerDivisionLevel'] ?? 5)));
  ensure_player_age_profile($player, $seller, team_source_key($team), (string)($team['name'] ?? strtoupper($seller)), $division);
  $player['sourceTeam'] = (string)($team['name'] ?? strtoupper($seller));
  $player['sourceTeamId'] = (string)($team['id'] ?? $seller);
  $player['sourceDivision'] = $division;
  $player['marketListedByUser'] = $seller;
  $player['marketListedAt'] = $now;
  $player['marketSalePrice'] = $price;
  $player['marketPayoutSettled'] = $payoutSettled;
  $player['sellerPlayerId'] = $sellerPlayerId;
  if (is_array($season)) {
    $player['marketSeasonId'] = (string)($season['id'] ?? '');
    $player['marketManagerGameId'] = manager_game_id($season);
    $player['marketHumanManagers'] = array_values(array_map(fn($name) => clean_name((string)$name), $season['humanManagers'] ?? []));
  }
  $player['price'] = max(1000, $price);
  if (!$payoutSettled) manager_schedule_cpu_market_offer($player, $season, $seller, 1, 10);
  unset($player['soldTo'], $player['soldToId'], $player['soldToDivision'], $player['soldAt'], $player['injury'], $player['injuryReason'], $player['injuryRemaining'], $player['dailyForm']);

  $state['managerMarket'] = array_values(array_filter($state['managerMarket'], function ($marketPlayer) use ($seller, $sellerPlayerId) {
    if (!is_array($marketPlayer)) return false;
    if ((string)($marketPlayer['marketListedByUser'] ?? '') !== $seller) return true;
    if ($sellerPlayerId === '') return true;
    return (string)($marketPlayer['sellerPlayerId'] ?? $marketPlayer['id'] ?? '') !== $sellerPlayerId;
  }));

  $ids = [];
  foreach ($state['managerMarket'] as $marketPlayer) {
    $id = (string)($marketPlayer['id'] ?? '');
    if ($id !== '') $ids[$id] = true;
  }
  $baseId = (string)($player['id'] ?? '');
  if ($baseId === '') $baseId = 'S' . bin2hex(random_bytes(4));
  $id = $baseId;
  for ($i = 1; isset($ids[$id]); $i++) $id = $baseId . '_sale' . $i;
  $player['id'] = $id;

  array_unshift($state['managerMarket'], $player);
  return $player;
}

function manager_cpu_market_offer_exists(array $state, string $marketPlayerId): bool {
  if ($marketPlayerId === '') return true;
  foreach (($state['managerTransferOffers'] ?? []) as $offer) {
    if (!is_array($offer) || ($offer['status'] ?? 'pending') !== 'pending') continue;
    if ((string)($offer['marketPlayerId'] ?? '') !== $marketPlayerId) continue;
    if (is_cpu_manager((string)($offer['buyer'] ?? ''))) return true;
  }
  return false;
}

function manager_cpu_market_buyer(array $state, ?array $season, array $player, string $seller): ?array {
  $humanKeys = manager_human_team_keys($state, $season);
  $sellerLevel = 5;
  $candidates = [];
  if (is_array($season)) {
    $sellerParticipant = manager_participant($season, $seller);
    if (is_array($sellerParticipant)) $sellerLevel = clamp_int((int)($sellerParticipant['divisionLevel'] ?? 5), 1, 5);
    foreach (($season['participants'] ?? []) as $participant) {
      if (!is_array($participant) || !is_cpu_manager((string)($participant['user'] ?? ''))) continue;
      $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
      $teamKey = source_team_id((string)($team['sourceId'] ?? $team['id'] ?? ''));
      if ($teamKey !== '' && isset($humanKeys[$teamKey])) continue;
      $candidates[] = [
        'buyer' => (string)$participant['user'],
        'team' => $team,
        'divisionName' => (string)($participant['divisionName'] ?? manager_division_name((int)($participant['divisionLevel'] ?? $sellerLevel))),
      ];
    }
  }
  if (!$candidates) {
    $levels = array_values(array_unique([$sellerLevel, max(1, $sellerLevel - 1), min(5, $sellerLevel + 1), 5]));
    foreach ($levels as $level) {
      foreach (manager_division_teams((int)$level) as $team) {
        $teamKey = source_team_id((string)($team['sourceId'] ?? $team['id'] ?? ''));
        if ($teamKey !== '' && isset($humanKeys[$teamKey])) continue;
        $candidates[] = [
          'buyer' => manager_cpu_id((string)$team['id']),
          'team' => $team,
          'divisionName' => manager_division_name((int)$level),
        ];
      }
      if ($candidates) break;
    }
  }
  if (!$candidates) return null;
  return $candidates[array_rand($candidates)];
}

function manager_generate_cpu_market_offers(array &$state, ?array $season, int $now, string $sellerFilter = ''): int {
  if (!isset($state['managerTransferOffers']) || !is_array($state['managerTransferOffers'])) $state['managerTransferOffers'] = [];
  if (empty($state['managerMarket']) || !is_array($state['managerMarket'])) return 0;
  $created = 0;
  $sellerFilter = clean_name($sellerFilter);
  foreach ($state['managerMarket'] as &$player) {
    if (!is_array($player) || !manager_market_listing_is_unsettled($player)) continue;
    $seller = clean_name((string)$player['marketListedByUser']);
    if ($seller === '' || ($sellerFilter !== '' && $seller !== $sellerFilter)) continue;
    if (!manager_market_listing_is_active($state, $player, $season, $seller)) continue;
    if (!isset($state['users'][$seller]) || !has_team($state['users'][$seller])) continue;
    $marketPlayerId = (string)($player['id'] ?? '');
    if ($marketPlayerId === '' || manager_cpu_market_offer_exists($state, $marketPlayerId)) continue;
    if (!isset($player['marketCpuOfferAfterMatches'])) manager_schedule_cpu_market_offer($player, $season, $seller, 1, 10);
    $playedMatches = manager_user_played_match_count($season, $seller);
    if ($playedMatches < (int)($player['marketCpuOfferAfterMatches'] ?? 0)) continue;
    $basePrice = max(1000, (int)($player['price'] ?? $player['marketSalePrice'] ?? player_value($player)));
    $saleBase = manager_player_sale_base($player);
    $chance = manager_sale_chance($basePrice, $saleBase);
    if (mt_rand(1, 100) > $chance) {
      manager_log($state, 'cpu_market_transfer_no_offer', ['seller' => $seller, 'player' => $player['name'] ?? '', 'ask' => $basePrice, 'base' => $saleBase, 'chance' => $chance]);
      manager_schedule_cpu_market_offer($player, $season, $seller, 1, 4);
      continue;
    }
    $buyer = manager_cpu_market_buyer($state, $season, $player, $seller);
    if (!$buyer) continue;
    $cashOffer = manager_cpu_market_offer_cash($basePrice, $saleBase);
    $offerId = 'c' . bin2hex(random_bytes(6));
    $sellerTeam = user_team($state['users'][$seller]);
    $state['managerTransferOffers'][$offerId] = [
      'id' => $offerId,
      'status' => 'pending',
      'seasonId' => (string)($season['id'] ?? ''),
      'buyer' => (string)$buyer['buyer'],
      'seller' => $seller,
      'decisionUser' => $seller,
      'buyerTeam' => $buyer['team'],
      'sellerTeam' => $sellerTeam,
      'divisionName' => (string)($buyer['divisionName'] ?? $player['sourceDivision'] ?? ''),
      'marketPlayerId' => $marketPlayerId,
      'marketPayoutSettled' => !empty($player['marketPayoutSettled']),
      'playerId' => (string)($player['sellerPlayerId'] ?? $player['id'] ?? ''),
      'player' => $player,
      'basePrice' => $basePrice,
      'cashOffer' => $cashOffer,
      'offerPlayerId' => '',
      'offerPlayer' => null,
      'offerValue' => 0,
      'offerTotal' => $cashOffer,
      'cpuBuyer' => true,
      'created' => $now,
      'expires' => $now + 604800,
    ];
    $created++;
    manager_log($state, 'cpu_market_transfer_offer', ['offerId' => $offerId, 'buyer' => (string)$buyer['buyer'], 'seller' => $seller, 'player' => $player['name'] ?? '', 'cash' => $cashOffer, 'ask' => $basePrice]);
  }
  unset($player);
  return $created;
}

function manager_remove_market_listing(array &$state, string $user, string $playerId, int $now): array {
  $user = clean_name($user);
  $playerId = trim($playerId);
  if ($user === '' || $playerId === '') reply(['ok' => false, 'error' => 'bad_player'], 400);
  $season = current_manager_season($state, $user);
  $marketIndex = null;
  $marketPlayer = null;
  foreach (($state['managerMarket'] ?? []) as $index => $player) {
    if (!is_array($player) || (string)($player['id'] ?? '') !== $playerId) continue;
    if (clean_name((string)($player['marketListedByUser'] ?? '')) !== $user) reply(['ok' => false, 'error' => 'forbidden'], 403);
    $marketIndex = $index;
    $marketPlayer = $player;
    break;
  }
  if ($marketIndex === null || !is_array($marketPlayer)) reply(['ok' => false, 'error' => 'bad_player'], 404);
  if (!empty($marketPlayer['marketPayoutSettled'])) reply(['ok' => false, 'error' => 'listing_locked'], 409);
  array_splice($state['managerMarket'], (int)$marketIndex, 1);
  $removeOffers = [];
  foreach (($state['managerTransferOffers'] ?? []) as $offerId => $offer) {
    if (!is_array($offer) || ($offer['status'] ?? 'pending') !== 'pending') continue;
    if ((string)($offer['marketPlayerId'] ?? '') !== $playerId) continue;
    if (!is_cpu_manager((string)($offer['buyer'] ?? ''))) manager_add_transfer_notice($state, (string)($offer['buyer'] ?? ''), 'rejected', $offer, $now);
    $removeOffers[] = $offerId;
  }
  foreach ($removeOffers as $offerId) unset($state['managerTransferOffers'][$offerId]);
  manager_log($state, 'remove_market_listing', ['user' => $user, 'player' => $marketPlayer['name'] ?? '', 'playerId' => $playerId]);
  return [
    'status' => 'removed',
    '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, $user),
    'managerTransferOffers' => public_manager_transfer_offers($state, $user),
  ];
}

function manager_transfer_offer_player(array $user, string $playerId): ?array {
  $playerId = trim($playerId);
  if ($playerId === '') return null;
  $found = find_squad_player($user['squad'] ?? [], $playerId);
  if (!$found) return null;
  $player = $found['player'];
  if (manager_is_emergency_loan_player($player)) return null;
  ensure_player_stats($player);
  $player['value'] = player_value($player);
  return ['group' => $found['group'], 'index' => $found['index'], 'player' => $player, 'value' => (int)$player['value']];
}

function manager_transfer_offer_player_ids(array $data, int $maxPlayers = 3): array {
  $raw = [];
  if (isset($data['offerPlayerIds']) && is_array($data['offerPlayerIds'])) {
    $raw = $data['offerPlayerIds'];
  }
  if (!$raw && isset($data['offerPlayerId'])) {
    $raw = [(string)$data['offerPlayerId']];
  }
  $ids = [];
  foreach ($raw as $id) {
    $id = trim((string)$id);
    if ($id === '' || in_array($id, $ids, true)) continue;
    $ids[] = $id;
  }
  if (count($ids) > $maxPlayers) reply(['ok' => false, 'error' => 'too_many_offer_players', 'maxPlayers' => $maxPlayers], 409);
  return $ids;
}

function manager_transfer_offer_players(array $user, array $playerIds): array {
  $players = [];
  foreach ($playerIds as $playerId) {
    $found = manager_transfer_offer_player($user, (string)$playerId);
    if (!$found) reply(['ok' => false, 'error' => 'bad_offer_player'], 404);
    $players[] = $found;
  }
  return $players;
}

function manager_transfer_offer_players_value(array $offerPlayers): int {
  $value = 0;
  foreach ($offerPlayers as $offerPlayer) $value += (int)($offerPlayer['value'] ?? 0);
  return $value;
}

function manager_ensure_browse_trade_room(array $squad, array $offerPlayers): void {
  $bench = manager_squad_bench_count($squad);
  $outgoing = count($offerPlayers);
  if ($outgoing > $bench) reply(['ok' => false, 'error' => 'no_replacement'], 409);
  $projected = max(0, $bench - $outgoing) + 1;
  if ($projected > MANAGER_MAX_SQUAD_RESERVES) {
    reply([
      'ok' => false,
      'error' => 'bench_full',
      'bench' => $bench,
      'projectedBench' => $projected,
      'maxBench' => MANAGER_MAX_SQUAD_RESERVES,
    ], 409);
  }
}

function manager_squad_bench_count(array $squad, bool $includeEmergencyLoans = false): int {
  $bench = is_array($squad['bench'] ?? null) ? $squad['bench'] : [];
  if ($includeEmergencyLoans) return count($bench);
  return count(array_filter($bench, fn($player) => is_array($player) && !manager_is_emergency_loan_player($player)));
}

function manager_projected_bench_after_receive(array $squad, ?array $outgoingFound = null): int {
  $bench = manager_squad_bench_count($squad);
  if ($outgoingFound) {
    $group = (string)($outgoingFound['group'] ?? '');
    if ($group === 'bench') $bench--;
    elseif ($group === 'starters' && $bench > 0) $bench--;
  }
  return max(0, $bench) + 1;
}

function manager_ensure_bench_room_for_receive(array $squad, ?array $outgoingFound = null): void {
  $bench = manager_squad_bench_count($squad);
  $projected = manager_projected_bench_after_receive($squad, $outgoingFound);
  if ($projected > MANAGER_MAX_SQUAD_RESERVES) {
    reply([
      'ok' => false,
      'error' => 'bench_full',
      'bench' => $bench,
      'projectedBench' => $projected,
      'maxBench' => MANAGER_MAX_SQUAD_RESERVES,
    ], 409);
  }
}

function manager_take_transfer_offer_player(array &$user, string $playerId, string $toTeam, string $toTeamId, string $toDivision, int $now): ?array {
  $playerId = trim($playerId);
  if ($playerId === '') return null;
  ensure_user_squad($user);
  $found = find_squad_player($user['squad'] ?? [], $playerId);
  if (!$found) reply(['ok' => false, 'error' => 'bad_offer_player'], 404);
  $group = (string)$found['group'];
  $index = (int)$found['index'];
  if (manager_is_emergency_loan_player($found['player'] ?? [])) reply(['ok' => false, 'error' => 'loan_player_locked'], 409);
  if ($group === 'starters' && empty($user['squad']['bench'])) reply(['ok' => false, 'error' => 'no_replacement'], 409);
  $player = $found['player'];
  ensure_player_stats($player);
  $ownerId = clean_name((string)($user['accountName'] ?? ''));
  $ownerTeam = has_team($user) ? user_team($user) : ['id' => ($ownerId !== '' ? $ownerId : 'manager'), 'name' => strtoupper($ownerId !== '' ? $ownerId : 'manager')];
  $ownerDivision = (string)($user['managerFinance']['divisionName'] ?? manager_division_name(clamp_int((int)($user['managerDivisionLevel'] ?? 5), 1, 5)));
  ensure_player_age_profile($player, $ownerId, team_source_key($ownerTeam), (string)($ownerTeam['name'] ?? ''), $ownerDivision);
  $player['value'] = player_value($player);
  array_splice($user['squad'][$group], $index, 1);
  if ($group === 'starters') {
    $replacement = array_shift($user['squad']['bench']);
    if ($replacement) {
      $replacement['homeX'] = $player['homeX'] ?? $replacement['homeX'] ?? 0.5;
      $replacement['homeY'] = $player['homeY'] ?? $replacement['homeY'] ?? 0.5;
      $replacement['x'] = $replacement['homeX'];
      $replacement['y'] = $replacement['homeY'];
      array_splice($user['squad']['starters'], $index, 0, [$replacement]);
    }
  }
  $player['soldTo'] = $toTeam;
  $player['soldToId'] = $toTeamId;
  $player['soldToDivision'] = $toDivision;
  $player['soldAt'] = $now;
  manager_sync_emergency_loan_bench($user);
  ensure_squad_stats($user['squad']);
  return $player;
}

function manager_add_trade_player_to_target(array &$state, string $buyer, string $teamUser, array $player, int $now): void {
  if (!$player) return;
  $seasonId = (string)(current_manager_season_id($state, $buyer) ?? '');
  if ($seasonId === '' || empty($state['managerSeasons'][$seasonId]['participants'])) return;
  $buyerTeam = isset($state['users'][$buyer]) && has_team($state['users'][$buyer]) ? user_team($state['users'][$buyer]) : ['name' => strtoupper($buyer), 'id' => $buyer];
  $buyerDivision = isset($state['users'][$buyer]) ? (string)($state['users'][$buyer]['managerFinance']['divisionName'] ?? manager_division_name((int)($state['users'][$buyer]['managerDivisionLevel'] ?? 5))) : '';
  ensure_player_age_profile($player, (string)($player['originOwnerId'] ?? $buyer), (string)($player['originTeamId'] ?? team_source_key($buyerTeam)), (string)($player['originTeamName'] ?? $buyerTeam['name'] ?? strtoupper($buyer)), (string)($player['originDivisionName'] ?? $buyerDivision));
  foreach ($state['managerSeasons'][$seasonId]['participants'] as &$participant) {
    if ((string)($participant['user'] ?? '') !== $teamUser) continue;
    if (!isset($participant['squad']) || !is_array($participant['squad'])) $participant['squad'] = ['starters' => [], 'bench' => []];
    ensure_player_id_unique_for_squad($player, $participant['squad']);
    $player['sourceTeam'] = (string)($buyerTeam['name'] ?? strtoupper($buyer));
    $player['sourceTeamId'] = (string)($buyerTeam['id'] ?? $buyer);
    $player['sourceDivision'] = (string)($state['users'][$buyer]['managerFinance']['divisionName'] ?? manager_division_name((int)($state['users'][$buyer]['managerDivisionLevel'] ?? 5)));
    $player['purchasedFrom'] = $player['sourceTeam'];
    $player['purchasedFromId'] = $player['sourceTeamId'];
    $player['purchasedFromDivision'] = $player['sourceDivision'];
    $player['purchasedAt'] = $now;
    $player['purchasePrice'] = 0;
    unset($player['soldTo'], $player['soldToId'], $player['soldToDivision'], $player['soldAt']);
    $participant['squad']['bench'][] = $player;
    ensure_squad_stats($participant['squad']);
    $participant['rating'] = squad_skill_rating($participant['squad']);
    $participant['strength'] = squad_skill_points($participant['squad']);
    unset($participant);
    return;
  }
  unset($participant);
}

function manager_remove_squad_player_for_age(array &$user, string $playerId): ?array {
  $playerId = trim($playerId);
  if ($playerId === '') return null;
  ensure_user_squad($user);
  $found = find_squad_player($user['squad'] ?? [], $playerId);
  if (!$found) return null;
  $group = (string)$found['group'];
  $index = (int)$found['index'];
  $player = $found['player'];
  if (manager_is_emergency_loan_player($player)) return null;
  array_splice($user['squad'][$group], $index, 1);
  if ($group === 'starters') {
    $replacement = array_shift($user['squad']['bench']);
    if ($replacement) {
      $replacement['homeX'] = $player['homeX'] ?? $replacement['homeX'] ?? 0.5;
      $replacement['homeY'] = $player['homeY'] ?? $replacement['homeY'] ?? 0.5;
      $replacement['x'] = $replacement['homeX'];
      $replacement['y'] = $replacement['homeY'];
      array_splice($user['squad']['starters'], $index, 0, [$replacement]);
    }
  }
  manager_sync_emergency_loan_bench($user);
  ensure_squad_stats($user['squad']);
  return $player;
}

function manager_reborn_player(array $player): array {
  ensure_player_age_profile($player, (string)($player['originOwnerId'] ?? ''), (string)($player['originTeamId'] ?? ''), (string)($player['originTeamName'] ?? ''), (string)($player['originDivisionName'] ?? ''));
  $stats = is_array($player['ageBaseStats'] ?? null) ? $player['ageBaseStats'] : ($player['stats'] ?? []);
  $role = canonical_player_role((string)($player['originRole'] ?? player_natural_role($player)), player_natural_role($player));
  $reborn = [
    'id' => (string)($player['originPlayerId'] ?? $player['id'] ?? next_transfer_player_id(['starters' => [], 'bench' => []])),
    'shirtNumber' => (int)($player['shirtNumber'] ?? $player['legacyNumber'] ?? 0),
    'name' => (string)($player['originName'] ?? $player['name'] ?? 'Player'),
    'shirtName' => (string)($player['shirtName'] ?? $player['originName'] ?? $player['name'] ?? 'Player'),
    'role' => $role,
    'naturalRole' => $role,
    'slotRole' => $role,
    'rating' => (int)($player['ageBaseRating'] ?? average_stats($stats)),
    'stats' => $stats,
    'weightClass' => (string)($player['ageBaseWeightClass'] ?? $player['weightClass'] ?? random_weight_class()),
    'foot' => (string)($player['ageBaseFoot'] ?? $player['foot'] ?? ''),
    'preferredFoot' => (string)($player['ageBaseFoot'] ?? $player['foot'] ?? ''),
    'homeX' => 0.5,
    'homeY' => 0.9,
    'x' => 0.5,
    'y' => 0.9,
    'age' => 17,
    'ageProfileVersion' => 1,
    'originOwnerId' => (string)($player['originOwnerId'] ?? ''),
    'originTeamId' => (string)($player['originTeamId'] ?? ''),
    'originTeamName' => (string)($player['originTeamName'] ?? ''),
    'originDivisionName' => (string)($player['originDivisionName'] ?? ''),
    'originPlayerId' => (string)($player['originPlayerId'] ?? $player['id'] ?? ''),
    'originName' => (string)($player['originName'] ?? $player['name'] ?? 'Player'),
    'originRole' => $role,
    'ageBaseStats' => $stats,
    'ageBaseRating' => (int)($player['ageBaseRating'] ?? average_stats($stats)),
    'ageBaseFoot' => (string)($player['ageBaseFoot'] ?? $player['foot'] ?? ''),
    'ageBaseWeightClass' => (string)($player['ageBaseWeightClass'] ?? $player['weightClass'] ?? ''),
  ];
  if ($reborn['shirtNumber'] <= 0) unset($reborn['shirtNumber']);
  unset($reborn['soldTo'], $reborn['soldToId'], $reborn['soldToDivision'], $reborn['soldAt'], $reborn['purchasedFrom'], $reborn['purchasedFromId'], $reborn['purchasedFromDivision'], $reborn['purchasedAt'], $reborn['purchasePrice'], $reborn['marketListedByUser'], $reborn['marketSalePrice'], $reborn['price']);
  ensure_player_stats($reborn);
  return $reborn;
}

function manager_add_reborn_player_to_origin(array &$state, array &$season, array $retiredPlayer): ?array {
  $originOwner = clean_name((string)($retiredPlayer['originOwnerId'] ?? ''));
  $originPlayerId = (string)($retiredPlayer['originPlayerId'] ?? $retiredPlayer['id'] ?? '');
  if ($originOwner === '') return null;
  $reborn = manager_reborn_player($retiredPlayer);
  if (!is_cpu_manager($originOwner) && isset($state['users'][$originOwner]) && is_array($state['users'][$originOwner]) && has_team($state['users'][$originOwner])) {
    ensure_user_squad($state['users'][$originOwner]);
    ensure_player_id_unique_for_squad($reborn, $state['users'][$originOwner]['squad'] ?? []);
    manager_prepare_bench_for_real_receive($state['users'][$originOwner]);
    $state['users'][$originOwner]['squad']['bench'][] = $reborn;
    manager_sync_emergency_loan_bench($state['users'][$originOwner]);
    ensure_squad_stats($state['users'][$originOwner]['squad']);
    return ['origin' => $originOwner, 'playerId' => (string)($reborn['id'] ?? ''), 'playerName' => (string)($reborn['name'] ?? 'Player')];
  }
  if (!isset($state['managerBrowseSold']) || !is_array($state['managerBrowseSold'])) $state['managerBrowseSold'] = [];
  unset($state['managerBrowseSold'][manager_browse_transfer_key($originOwner, $originPlayerId)]);
  foreach ($state['managerSeasons'] as &$candidateSeason) {
    if (!is_array($candidateSeason) || manager_game_id($candidateSeason) !== manager_game_id($season)) continue;
    foreach (($candidateSeason['participants'] ?? []) as &$participant) {
      if ((string)($participant['user'] ?? '') !== $originOwner) continue;
      if (!isset($participant['squad']) || !is_array($participant['squad'])) $participant['squad'] = ['starters' => [], 'bench' => []];
      if (squad_has_player_id($participant['squad'], (string)($reborn['id'] ?? ''))) {
        unset($participant);
        break 2;
      }
      ensure_player_id_unique_for_squad($reborn, $participant['squad']);
      $participant['squad']['bench'][] = $reborn;
      ensure_squad_stats($participant['squad']);
      $participant['rating'] = squad_skill_rating($participant['squad']);
      $participant['strength'] = squad_skill_points($participant['squad']);
      unset($participant);
      break 2;
    }
    unset($participant);
  }
  unset($candidateSeason);
  return ['origin' => $originOwner, 'playerId' => (string)($reborn['id'] ?? ''), 'playerName' => (string)($reborn['name'] ?? 'Player')];
}

function manager_age_user_squad_after_season(array &$state, array &$season, string $user): array {
  $user = clean_name($user);
  if ($user === '' || empty($state['users'][$user]) || !is_array($state['users'][$user]) || !has_team($state['users'][$user])) return [];
  ensure_user_squad($state['users'][$user]);
  $team = user_team($state['users'][$user]);
  $divisionName = manager_division_name(clamp_int((int)($state['users'][$user]['managerDivisionLevel'] ?? 5), 1, 5));
  $retireIds = [];
  foreach (['starters', 'bench'] as $group) {
    foreach (($state['users'][$user]['squad'][$group] ?? []) as &$player) {
      if (manager_is_emergency_loan_player(is_array($player) ? $player : [])) continue;
      ensure_player_age_profile($player, $user, team_source_key($team), (string)($team['name'] ?? ''), $divisionName);
      $player['age'] = min(45, (int)($player['age'] ?? 17) + 1);
      if ((int)$player['age'] >= 45) $retireIds[] = (string)($player['id'] ?? '');
    }
    unset($player);
  }
  $events = [];
  foreach (array_values(array_unique(array_filter($retireIds))) as $playerId) {
    $retired = manager_remove_squad_player_for_age($state['users'][$user], $playerId);
    if (!$retired) continue;
    $reborn = manager_add_reborn_player_to_origin($state, $season, $retired);
    $events[] = [
      'retiredFrom' => $user,
      'playerId' => $playerId,
      'playerName' => (string)($retired['name'] ?? 'Player'),
      'age' => (int)($retired['age'] ?? 45),
      'reborn' => $reborn,
    ];
  }
  return $events;
}

function squad_destination_snapshot(array $user, array $player, string $group): array {
  ensure_player_roles($player);
  $starter = $group === 'starters';
  $slotRole = $starter
    ? manager_slot_role_for_starter_position($user, $player, player_slot_role($player))
    : player_natural_role($player);
  return [
    'group' => $group,
    'slotRole' => $slotRole,
    'homeX' => $player['homeX'] ?? null,
    'homeY' => $player['homeY'] ?? null,
    'x' => $player['x'] ?? null,
    'y' => $player['y'] ?? null,
  ];
}

function apply_squad_destination(array &$player, array $destination): void {
  ensure_player_roles($player);
  $natural = player_natural_role($player);
  if (($destination['group'] ?? '') === 'starters') {
    $player['slotRole'] = canonical_player_role((string)($destination['slotRole'] ?? ''), $natural);
    foreach (['homeX', 'homeY', 'x', 'y'] as $key) {
      if (isset($destination[$key]) && is_numeric($destination[$key])) $player[$key] = (float)$destination[$key];
    }
  } else {
    $player['slotRole'] = $natural;
  }
  $player['role'] = $natural;
}

function swap_squad_players(array &$user, string $firstId, string $secondId): bool {
  if ($firstId === '' || $secondId === '' || $firstId === $secondId) return false;
  ensure_user_squad($user);
  $first = find_squad_player($user['squad'] ?? [], $firstId);
  $second = find_squad_player($user['squad'] ?? [], $secondId);
  if (!$first || !$second) return false;

  $a =& $user['squad'][$first['group']][$first['index']];
  $b =& $user['squad'][$second['group']][$second['index']];
  ensure_player_roles($a);
  ensure_player_roles($b);
  $aPos = squad_destination_snapshot($user, $a, (string)$first['group']);
  $bPos = squad_destination_snapshot($user, $b, (string)$second['group']);

  [$a, $b] = [$b, $a];

  apply_squad_destination($a, $aPos);
  apply_squad_destination($b, $bPos);
  unset($a, $b);
  if (!isset($user['manager']) || !is_array($user['manager'])) $user['manager'] = [];
  $user['manager']['manualLineupOverrideAt'] = time();
  normalize_squad_starter_slots($user);
  ensure_squad_stats($user['squad']);
  return true;
}

function manager_player_goal_map(array $season, string $user): array {
  $goals = [];
  foreach (($season['scorers'] ?? []) as $scorer) {
    if ((string)($scorer['user'] ?? '') !== $user) continue;
    $goals[(string)($scorer['player'] ?? 'Player')] = (int)($scorer['goals'] ?? 0);
  }
  return $goals;
}

function public_manager_squad(array $squad, array $goals): array {
  $out = ['starters' => [], 'bench' => []];
  foreach (['starters', 'bench'] as $group) {
    foreach (($squad[$group] ?? []) as $player) {
      $player = public_player($player);
      $name = (string)($player['name'] ?? 'Player');
      $player['value'] = player_value($player);
      $player['wage'] = player_wage($player);
      $player['goals'] = (int)($goals[$name] ?? 0);
      $out[$group][] = $player;
    }
  }
  return $out;
}

function public_manager_team_finance_snapshot(array $team, array $squad, int $level, array $finance = []): array {
  $user = [
    'teamId' => (string)($team['id'] ?? $team['sourceId'] ?? ''),
    'teamName' => (string)($team['name'] ?? 'TEAM'),
    'shirt' => (string)($team['shirt'] ?? '#ffffff'),
    'trim' => (string)($team['trim'] ?? '#111111'),
    'squad' => $squad,
    'managerDivisionLevel' => clamp_int($level, 1, 5),
    'managerFinance' => $finance,
  ];
  ensure_manager_finance($user);
  return public_finance($user);
}

function public_manager_team_finance(array &$state, array $season, string $target, array $side): array {
  if (!is_cpu_manager($target) && isset($state['users'][$target]) && manager_global_user_matches_season($state, $season, $target)) {
    ensure_manager_finance($state['users'][$target]);
    return public_finance($state['users'][$target]);
  }
  $participant = manager_participant($season, $target);
  if (!is_cpu_manager($target) && $participant !== null) {
    $snapshot = manager_participant_user_snapshot($participant, $target);
    if (is_array($snapshot)) return public_finance($snapshot);
  }
  $team = is_array($side['team'] ?? null) ? $side['team'] : [];
  $squad = is_array($side['squad'] ?? null) ? $side['squad'] : ['starters' => [], 'bench' => []];
  $level = clamp_int((int)($participant['divisionLevel'] ?? $season['divisionLevel'] ?? 5), 1, 5);
  $finance = is_array($participant['managerFinance'] ?? null) ? $participant['managerFinance'] : [];
  return public_manager_team_finance_snapshot($team, $squad, $level, $finance);
}

function public_manager_record_from_rows(array $rows, string $target, string $divisionName = ''): ?array {
  foreach (array_values($rows) as $index => $row) {
    if (!is_array($row)) continue;
    if ((string)($row['user'] ?? $row['id'] ?? '') !== $target) continue;
    $row['rank'] = $index + 1;
    if ($divisionName !== '') $row['divisionName'] = $divisionName;
    return $row;
  }
  return null;
}

function public_manager_team_record(array $season, string $target): ?array {
  return public_manager_record_from_rows(manager_ranked_rows($season), $target, (string)($season['divisionName'] ?? manager_division_name((int)($season['divisionLevel'] ?? 5))));
}

function public_manager_team_details(array &$state, array $season, string $target): array {
  if (manager_participant($season, $target) === null) reply(['ok' => false, 'error' => 'bad_team'], 404);
  $side = manager_side_data($state, $season, $target);
  $side = manager_apply_next_form_to_side_data($season, $target, $side);
  $side['squad'] = manager_annotate_squad_injuries($side['squad'] ?? ['starters' => [], 'bench' => []], $season, $target);
  $side['squad'] = manager_annotate_squad_training($side['squad'], $season, $target);
  $side['squad'] = manager_annotate_squad_cards($side['squad'], $season, $target, $state);
  $goals = manager_player_goal_map($season, $target);
  return [
    'id' => $target,
    'team' => $side['team'],
    'trophies' => public_team_trophies($state, $side['team'], manager_trophy_season_ids($state, $season)),
    'managerType' => !empty($side['human']) ? 'human' : 'cpu',
    'divisionLevel' => (int)($season['divisionLevel'] ?? 5),
    'divisionName' => (string)($season['divisionName'] ?? manager_division_name((int)($season['divisionLevel'] ?? 5))),
    'record' => public_manager_team_record($season, $target),
    'rating' => (int)$side['rating'],
    'rawStrength' => (int)$side['strength'],
    'strength' => manager_display_skill((int)$side['strength']),
	    'formation' => valid_manager_formation((string)($side['formation'] ?? ((!is_cpu_manager($target) && isset($state['users'][$target])) ? manager_user_formation($state['users'][$target]) : '4-4-2'))),
    'formationOptions' => manager_formation_options(),
    'dailyForm' => public_daily_form_summary($side['squad'], $side['dailyForm'] ?? []),
    'finance' => public_manager_team_finance($state, $season, $target, $side),
	    'squad' => public_manager_squad($side['squad'], $goals),
	  ];
	}

function manager_season_chain_key(array $state, array $season): string {
  $gameId = (string)($season['managerGameId'] ?? $season['saveGameId'] ?? '');
  if ($gameId !== '') return 'game:' . $gameId;
  return 'root:' . manager_season_root_id($state, $season);
}

function manager_related_current_season_ids(array $state, ?array $season): array {
  if ($season === null) return [];
  $chainKey = manager_season_chain_key($state, $season);
  $ids = [];
  $add = function (string $id) use (&$ids, $state, $chainKey): void {
    if ($id === '' || isset($ids[$id])) return;
    $candidate = $state['managerSeasons'][$id] ?? null;
    if (!is_array($candidate) || ($candidate['status'] ?? '') === 'aborted') return;
    if (manager_season_chain_key($state, $candidate) !== $chainKey) return;
    $ids[$id] = true;
  };
  $add((string)($season['id'] ?? ''));
  foreach (($state['managerCurrentByUser'] ?? []) as $id) {
    if (is_string($id)) $add($id);
  }
  foreach (($state['managerSeasons'] ?? []) as $id => $candidate) {
    if (!is_array($candidate)) continue;
    if (($candidate['status'] ?? '') === 'aborted') continue;
    if (manager_season_chain_key($state, $candidate) !== $chainKey) continue;
    $add((string)$id);
  }
  return array_keys($ids);
}

function manager_team_keys_from_participant(array $participant): array {
  $team = is_array($participant['team'] ?? null) ? $participant['team'] : [];
  $teamId = (string)($team['id'] ?? '');
  $sourceId = (string)($team['sourceId'] ?? $teamId);
  return array_values(array_filter(array_unique([$teamId, $sourceId, source_team_id($teamId), source_team_id($sourceId)]), fn($key) => $key !== ''));
}

function public_manager_division_rows_from_season(array $state, array $season, string $viewer = ''): array {
  $participants = [];
  foreach (($season['participants'] ?? []) as $participant) {
    if (!is_array($participant)) continue;
    $id = (string)($participant['user'] ?? '');
    if ($id !== '') $participants[$id] = $participant;
  }
  $rows = public_manager_table($season, $viewer);
  foreach ($rows as &$row) {
    $id = (string)($row['user'] ?? '');
    if ($id === '') continue;
    $participant = $participants[$id] ?? [];
    $preview = manager_side_preview($state, $season, $id);
    $rawStrength = (int)($preview['strength'] ?? $participant['strength'] ?? 0);
    $form = manager_league_form_state($season, $id);
    $row['id'] = $id;
    $row['managerType'] = (string)($participant['managerType'] ?? (is_cpu_manager($id) ? 'cpu' : 'human'));
    $row['team'] = $preview['team'] ?? ($row['team'] ?? ($participant['team'] ?? ['name' => $id]));
    $row['rating'] = (int)($preview['rating'] ?? $participant['rating'] ?? 0);
    $row['rawStrength'] = $rawStrength;
    $row['strength'] = manager_display_skill($rawStrength);
    $row['divisionLevel'] = (int)($season['divisionLevel'] ?? 5);
    $row['divisionName'] = (string)($season['divisionName'] ?? manager_division_name((int)($season['divisionLevel'] ?? 5)));
    $row['mine'] = $viewer !== '' && $id === $viewer;
    $row['formTrend'] = $form['trend'] ?? '';
    $row['formBonusPct'] = (int)($form['pct'] ?? 0);
    $row['formRecent'] = $form['recent'] ?? [];
  }
  unset($row);
  return $rows;
}

function manager_shadow_apply_result(array &$rows, string $id, int $gf, int $ga): void {
  if (!isset($rows[$id])) return;
  $rows[$id]['played'] = (int)($rows[$id]['played'] ?? 0) + 1;
  $rows[$id]['gf'] = (int)($rows[$id]['gf'] ?? 0) + $gf;
  $rows[$id]['ga'] = (int)($rows[$id]['ga'] ?? 0) + $ga;
  $rows[$id]['gd'] = (int)$rows[$id]['gf'] - (int)$rows[$id]['ga'];
  if ($gf > $ga) {
    $rows[$id]['won'] = (int)($rows[$id]['won'] ?? 0) + 1;
    $rows[$id]['points'] = (int)($rows[$id]['points'] ?? 0) + 3;
  } elseif ($gf === $ga) {
    $rows[$id]['drawn'] = (int)($rows[$id]['drawn'] ?? 0) + 1;
    $rows[$id]['points'] = (int)($rows[$id]['points'] ?? 0) + 1;
  } else {
    $rows[$id]['lost'] = (int)($rows[$id]['lost'] ?? 0) + 1;
  }
}

function manager_shadow_form_state(string $seed, string $user): array {
  $recent = [];
  for ($i = 0; $i < 3; $i++) {
    $roll = manager_seeded_unit($seed . '|form|' . $user . '|' . $i);
    $recent[] = $roll < 0.42 ? 'W' : ($roll < 0.70 ? 'D' : 'L');
  }
  $joined = implode('', $recent);
  if ($joined === 'WWW') return ['trend' => 'up', 'pct' => 3, 'recent' => $recent];
  if ($joined === 'LLL') return ['trend' => 'down', 'pct' => -3, 'recent' => $recent];
  return ['trend' => '', 'pct' => 0, 'recent' => $recent];
}

function public_manager_static_division_rows(array $state, int $level, array $usedTeamKeys, string $seed = ''): array {
  $seed = $seed !== '' ? $seed : 'static-division-' . $level;
  $rowsByUser = [];
  foreach (manager_division_teams($level) as $team) {
    $teamKey = source_team_id((string)($team['sourceId'] ?? $team['id'] ?? ''));
    if ($teamKey !== '' && isset($usedTeamKeys[$teamKey])) continue;
    $participant = cpu_manager_participant($team, $level);
    $rawStrength = (int)$participant['strength'];
    $user = (string)$participant['user'];
    $form = manager_shadow_form_state($seed, $user);
    $rowsByUser[$user] = [
      'user' => (string)$participant['user'],
      'id' => (string)$participant['user'],
      'managerType' => 'cpu',
      'team' => $participant['team'],
      'rating' => (int)$participant['rating'],
      'rawStrength' => $rawStrength,
      'strength' => manager_display_skill($rawStrength),
      'divisionLevel' => $level,
      'divisionName' => manager_division_name($level),
      'played' => 0,
      'won' => 0,
      'drawn' => 0,
      'lost' => 0,
      'gf' => 0,
      'ga' => 0,
      'gd' => 0,
      'points' => 0,
      'formTrend' => $form['trend'],
      'formBonusPct' => $form['pct'],
      'formRecent' => $form['recent'],
    ];
  }
  $ids = array_keys($rowsByUser);
  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 . '|leg1|' . $home . '|' . $away, (int)$rowsByUser[$home]['rawStrength'], (int)$rowsByUser[$away]['rawStrength']);
      manager_shadow_apply_result($rowsByUser, $home, $hg, $ag);
      manager_shadow_apply_result($rowsByUser, $away, $ag, $hg);
      [$ag2, $hg2] = manager_shadow_score($seed . '|leg2|' . $away . '|' . $home, (int)$rowsByUser[$away]['rawStrength'], (int)$rowsByUser[$home]['rawStrength']);
      manager_shadow_apply_result($rowsByUser, $away, $ag2, $hg2);
      manager_shadow_apply_result($rowsByUser, $home, $hg2, $ag2);
    }
  }
  $rows = array_values($rowsByUser);
  usort($rows, function ($a, $b) {
    foreach (['points', 'gd', 'gf', 'rawStrength'] as $key) {
      $cmp = ((int)($b[$key] ?? 0)) <=> ((int)($a[$key] ?? 0));
      if ($cmp !== 0) return $cmp;
    }
    return strcasecmp((string)($a['team']['name'] ?? ''), (string)($b['team']['name'] ?? ''));
  });
  return $rows;
}

function manager_shadow_stat_candidates(array $team, int $level, array $roles, int $limit = 6): array {
  $base = (int)($team['baseRating'] ?? team_base_rating((string)($team['id'] ?? '')));
  $squad = make_named_squad('home', $base, team_source_key($team), (string)($team['name'] ?? ''));
  $players = array_values(array_merge($squad['starters'] ?? [], $squad['bench'] ?? []));
  $roleSet = array_fill_keys(array_map('strtoupper', $roles), true);
  $picked = array_values(array_filter($players, function ($player) use ($roleSet) {
    $role = strtoupper((string)($player['role'] ?? $player['naturalRole'] ?? ''));
    return isset($roleSet[$role]);
  }));
  if (count($picked) < $limit) {
    foreach ($players as $player) {
      $role = strtoupper((string)($player['role'] ?? $player['naturalRole'] ?? ''));
      if ($role === 'GK') continue;
      $id = (string)($player['id'] ?? $player['name'] ?? '');
      $exists = false;
      foreach ($picked as $candidate) {
        if ((string)($candidate['id'] ?? $candidate['name'] ?? '') === $id) {
          $exists = true;
          break;
        }
      }
      if (!$exists) $picked[] = $player;
      if (count($picked) >= $limit) break;
    }
  }
  usort($picked, fn($a, $b) => ((int)($b['rating'] ?? 0)) <=> ((int)($a['rating'] ?? 0)));
  return array_slice($picked, 0, $limit);
}

function public_manager_shadow_division_stat_tables(array $rows, int $limit, string $seed, string $label): array {
  $goalRows = [];
  $assistRows = [];
  $meta = [];
  $goalsByKey = [];
  $assistsByKey = [];
  $penaltiesByKey = [];
  $goalShares = [0.34, 0.22, 0.15, 0.10, 0.07, 0.04];
  $assistShares = [0.28, 0.21, 0.16, 0.11, 0.08, 0.05];
  foreach ($rows as $row) {
    if (!is_array($row)) continue;
    $team = is_array($row['team'] ?? null) ? $row['team'] : [];
    $user = (string)($row['user'] ?? $row['id'] ?? '');
    $teamGoals = max(0, (int)($row['gf'] ?? 0));
    if ($user === '' || !$team || $teamGoals <= 0) continue;
    $scorers = manager_shadow_stat_candidates($team, (int)($row['divisionLevel'] ?? 5), ['ST', 'LM', 'RM', 'CM'], 6);
    $assisters = manager_shadow_stat_candidates($team, (int)($row['divisionLevel'] ?? 5), ['CM', 'LM', 'RM', 'LB', 'RB'], 6);
    foreach ($scorers as $index => $player) {
      $share = $goalShares[$index] ?? 0.03;
      $jitter = manager_seeded_unit($seed . '|goals|' . $user . '|' . (string)($player['id'] ?? $player['name'] ?? $index));
      $goals = (int)round($teamGoals * $share + ($jitter * 2.6) - 0.7);
      if ($index === 0) $goals = max($goals, (int)floor($teamGoals * 0.24));
      if ($goals <= 0) continue;
      $name = (string)($player['name'] ?? 'Player');
      $key = $user . '|' . $name;
      $meta[$key] = ['user' => $user, 'team' => $team, 'teamName' => (string)($team['name'] ?? ''), 'player' => $name];
      $goalsByKey[$key] = (int)($goalsByKey[$key] ?? 0) + $goals;
      $penaltyRoll = manager_seeded_unit($seed . '|pens|' . $key);
      $penaltiesByKey[$key] = (int)($penaltiesByKey[$key] ?? 0) + min($goals, (int)floor($penaltyRoll * 3));
    }
    foreach ($assisters as $index => $player) {
      $share = $assistShares[$index] ?? 0.03;
      $jitter = manager_seeded_unit($seed . '|assists|' . $user . '|' . (string)($player['id'] ?? $player['name'] ?? $index));
      $assists = (int)round($teamGoals * $share + ($jitter * 2.2) - 0.6);
      if ($index === 0) $assists = max($assists, (int)floor($teamGoals * 0.18));
      if ($assists <= 0) continue;
      $name = (string)($player['name'] ?? 'Player');
      $key = $user . '|' . $name;
      $meta[$key] = ['user' => $user, 'team' => $team, 'teamName' => (string)($team['name'] ?? ''), 'player' => $name];
      $assistsByKey[$key] = (int)($assistsByKey[$key] ?? 0) + $assists;
    }
  }
  foreach ($meta as $key => $base) {
    $goals = (int)($goalsByKey[$key] ?? 0);
    $assists = (int)($assistsByKey[$key] ?? 0);
    $penalties = (int)($penaltiesByKey[$key] ?? 0);
    if ($goals > 0) $goalRows[] = $base + ['goals' => $goals, 'assists' => $assists, 'penalties' => $penalties];
    if ($assists > 0) $assistRows[] = $base + ['goals' => $goals, 'assists' => $assists, 'penalties' => $penalties];
  }
  manager_sort_scorer_rows($goalRows);
  manager_sort_assist_rows($assistRows);
  $context = manager_competition_context_from_fixture(['phase' => 'league']);
  $context['label'] = $label;
  return [
    'statContext' => $context,
    'topScorers' => array_slice($goalRows, 0, $limit),
    'topAssists' => array_slice($assistRows, 0, $limit),
  ];
}

function manager_related_current_season_id_for_target(array $state, array $season, string $target): ?string {
  if ($target === '') return null;
  foreach (manager_related_current_season_ids($state, $season) as $id) {
    $candidate = $state['managerSeasons'][$id] ?? null;
    if (!is_array($candidate)) continue;
    if (manager_participant($candidate, $target) !== null) return (string)$id;
  }
  return null;
}

function public_manager_divisions(?array $state = null, ?array $season = null, string $viewer = ''): array {
  $out = [];
  $divisionSeasons = [];
  $usedTeamKeys = [];
  if ($state !== null && $season !== null) {
    foreach (manager_related_current_season_ids($state, $season) as $id) {
      $candidate = $state['managerSeasons'][$id] ?? null;
      if (!is_array($candidate)) continue;
      $level = clamp_int((int)($candidate['divisionLevel'] ?? 5), 1, 5);
      $score = manager_current_season_score($state, $candidate);
      if (!isset($divisionSeasons[$level]) || $score >= (int)($divisionSeasons[$level]['score'] ?? 0)) {
        $divisionSeasons[$level] = ['season' => $candidate, 'score' => $score];
      }
      foreach (($candidate['participants'] ?? []) as $participant) {
        if (!is_array($participant)) continue;
        foreach (manager_team_keys_from_participant($participant) as $key) $usedTeamKeys[$key] = true;
      }
    }
  }
	  for ($level = 1; $level <= 5; $level++) {
	    $divisionSeason = $divisionSeasons[$level]['season'] ?? null;
	    $shadowYear = is_array($season) ? manager_season_year($season, $state) : ($state !== null ? manager_state_year($state) : null);
	    $shadowGameId = is_array($season) ? manager_game_id($season) : '';
	    $shadowSeed = ($shadowGameId !== '' ? $shadowGameId : 'manager') . '|season:' . (string)($shadowYear ?? manager_base_season_year()) . '|division:' . $level;
	    if (is_array($divisionSeason)) {
	      $teams = public_manager_division_rows_from_season($state, $divisionSeason, $viewer);
	    } elseif ($state !== null) {
	      $teams = public_manager_static_division_rows($state, $level, $usedTeamKeys, $shadowSeed);
	    } else {
	      $teams = [];
	    }
	    if (is_array($divisionSeason)) {
	      $stats = public_manager_league_stat_tables($divisionSeason, 10, manager_division_name($level));
	    } elseif ($teams) {
	      $stats = public_manager_shadow_division_stat_tables($teams, 10, $shadowSeed, manager_division_name($level));
	    } else {
	      $stats = ['statContext' => ['key' => 'league', 'phase' => 'league', 'cupKey' => '', 'label' => manager_division_name($level)], 'topScorers' => [], 'topAssists' => []];
	    }
	    $out[] = [
	      'level' => $level,
	      'id' => manager_division_id($level),
	      'name' => manager_division_name($level),
	      'seasonId' => is_array($divisionSeason) ? (string)($divisionSeason['id'] ?? '') : '',
	      'seasonYear' => is_array($divisionSeason) ? manager_season_year($divisionSeason, $state) : $shadowYear,
	      'status' => is_array($divisionSeason) ? (string)($divisionSeason['status'] ?? '') : ($teams ? 'simulated' : 'static'),
      'promotionRules' => manager_promotion_rules($level),
      'humanManagers' => is_array($divisionSeason) ? array_values($divisionSeason['humanManagers'] ?? []) : [],
      'table' => $teams,
      'teams' => $teams,
      'statContext' => $stats['statContext'],
      'topScorers' => $stats['topScorers'],
      'topAssists' => $stats['topAssists'],
      'topCupScorers' => [],
      'topCupAssists' => [],
    ];
  }
  return $out;
}

function public_manager_division_team_details(string $target, ?array $state = null, ?array $seasonContext = null): array {
  for ($level = 1; $level <= 5; $level++) {
    foreach (manager_division_teams($level) as $team) {
      $participant = cpu_manager_participant($team, $level);
      if ((string)$participant['user'] !== $target) continue;
      $rawStrength = (int)$participant['strength'];
      $record = null;
      if ($state !== null) {
        foreach (public_manager_divisions($state, $seasonContext, '') as $division) {
          if ((int)($division['level'] ?? 0) !== $level) continue;
          $record = public_manager_record_from_rows($division['teams'] ?? $division['table'] ?? [], $target, (string)($division['name'] ?? manager_division_name($level)));
          break;
        }
      }
      return [
        'id' => $target,
        'team' => $participant['team'],
        'trophies' => $state !== null ? public_team_trophies($state, $participant['team']) : [],
        'managerType' => 'cpu',
        'divisionName' => manager_division_name($level),
        'record' => $record,
        'rating' => (int)$participant['rating'],
        'rawStrength' => $rawStrength,
        'strength' => manager_display_skill($rawStrength),
        'formation' => valid_manager_formation((string)($participant['formation'] ?? '4-4-2')),
        'formationOptions' => manager_formation_options(),
        'finance' => public_manager_team_finance_snapshot($participant['team'], $participant['squad'], $level),
        'squad' => public_manager_squad($participant['squad'], []),
      ];
    }
  }
  reply(['ok' => false, 'error' => 'bad_team'], 404);
}

function manager_browse_transfer_key(string $teamUser, string $playerId): string {
  return $teamUser . ':' . $playerId;
}

function manager_browse_team_level(string $teamUser): int {
  for ($level = 1; $level <= 5; $level++) {
    foreach (manager_division_teams($level) as $team) {
      if (manager_cpu_id((string)$team['id']) === $teamUser) return $level;
    }
  }
  return 5;
}

function manager_browse_team_details(array &$state, string $target, string $user = ''): array {
  $season = current_manager_season($state, $user);
  if (!is_cpu_manager($target) && (!$season || !in_array($target, $season['humanManagers'] ?? [], true) || $target === $user)) {
    reply(['ok' => false, 'error' => 'human_transfer_locked'], 409);
  }
  $participant = $season ? manager_participant($season, $target) : null;
  if ($participant !== null) {
    $details = public_manager_team_details($state, $season, $target);
    $details['divisionName'] = (string)($participant['divisionName'] ?? manager_division_name((int)($participant['divisionLevel'] ?? 5)));
  } else {
    $details = public_manager_division_team_details($target, $state, $season);
  }

  $team = $details['team'] ?? [];
  $divisionName = (string)($details['divisionName'] ?? manager_division_name(manager_browse_team_level($target)));
  $sold = is_array($state['managerBrowseSold'] ?? null) ? $state['managerBrowseSold'] : [];
  $out = ['starters' => [], 'bench' => []];
  foreach (['starters', 'bench'] as $group) {
    foreach (($details['squad'][$group] ?? []) as $player) {
      if (manager_is_emergency_loan_player(is_array($player) ? $player : [])) continue;
      $playerId = (string)($player['id'] ?? '');
      if ($playerId === '' || !empty($sold[manager_browse_transfer_key($target, $playerId)])) continue;
      $player = public_player($player);
      $player['sourceTeam'] = (string)($team['name'] ?? strtoupper($target));
      $player['sourceTeamId'] = $target;
      $player['sourceDivision'] = $divisionName;
      $player['value'] = player_value($player);
      $player['price'] = $player['value'];
      $out[$group][] = $player;
    }
  }
  $details['squad'] = $out;
  $details['divisionName'] = $divisionName;
  $details['rawStrength'] = squad_skill_points($out);
  $details['strength'] = manager_display_skill((int)$details['rawStrength']);
  $details['rating'] = squad_skill_rating($out);
  return $details;
}

function manager_browse_find_player(array $details, string $playerId): ?array {
  foreach (['starters', 'bench'] as $group) {
    foreach (($details['squad'][$group] ?? []) as $player) {
      if ((string)($player['id'] ?? '') === $playerId) return ['group' => $group, 'player' => $player];
    }
  }
  return null;
}

function public_manager_transfer_offer(array $offer, string $viewer): array {
  $player = is_array($offer['player'] ?? null) ? public_player($offer['player']) : [];
  $offerPlayer = is_array($offer['offerPlayer'] ?? null) ? public_player($offer['offerPlayer']) : null;
  return [
    'id' => (string)($offer['id'] ?? ''),
    'status' => (string)($offer['status'] ?? 'pending'),
    'direction' => clean_name((string)($offer['decisionUser'] ?? $offer['seller'] ?? '')) === clean_name($viewer) ? 'incoming' : 'outgoing',
    'decisionUser' => clean_name((string)($offer['decisionUser'] ?? $offer['seller'] ?? '')),
    'countered' => !empty($offer['countered']),
    'counterBy' => (string)($offer['counterBy'] ?? ''),
    'counterAt' => (int)($offer['counterAt'] ?? 0),
    'buyer' => (string)($offer['buyer'] ?? ''),
    'seller' => (string)($offer['seller'] ?? ''),
    'buyerTeam' => is_array($offer['buyerTeam'] ?? null) ? $offer['buyerTeam'] : [],
    'sellerTeam' => is_array($offer['sellerTeam'] ?? null) ? $offer['sellerTeam'] : [],
    'divisionName' => (string)($offer['divisionName'] ?? ''),
    'player' => $player,
    'basePrice' => (int)($offer['basePrice'] ?? 0),
    'cashOffer' => (int)($offer['cashOffer'] ?? 0),
    'offerPlayer' => $offerPlayer,
    'offerPlayerId' => (string)($offer['offerPlayerId'] ?? ''),
    'offerValue' => (int)($offer['offerValue'] ?? 0),
    'offerTotal' => (int)($offer['offerTotal'] ?? 0),
    'created' => (int)($offer['created'] ?? 0),
    'expires' => (int)($offer['expires'] ?? 0),
  ];
}

function manager_add_transfer_notice(array &$state, string $user, string $type, array $offer, int $now): void {
  $user = clean_name($user);
  if ($user === '') return;
  if (!isset($state['managerTransferNotices']) || !is_array($state['managerTransferNotices'])) $state['managerTransferNotices'] = [];
  $id = 'tn' . bin2hex(random_bytes(6));
  $state['managerTransferNotices'][$id] = [
    'id' => $id,
    'user' => $user,
    'type' => $type,
    'buyer' => (string)($offer['buyer'] ?? ''),
    'seller' => (string)($offer['seller'] ?? ''),
    'buyerTeam' => is_array($offer['buyerTeam'] ?? null) ? $offer['buyerTeam'] : [],
    'sellerTeam' => is_array($offer['sellerTeam'] ?? null) ? $offer['sellerTeam'] : [],
    'player' => is_array($offer['player'] ?? null) ? $offer['player'] : [],
    'cashOffer' => (int)($offer['cashOffer'] ?? 0),
    'offerPlayer' => is_array($offer['offerPlayer'] ?? null) ? $offer['offerPlayer'] : null,
    'offerValue' => (int)($offer['offerValue'] ?? 0),
    'offerTotal' => (int)($offer['offerTotal'] ?? 0),
    'created' => $now,
  ];
}

function public_manager_transfer_notice(array $notice): array {
  return [
    'id' => (string)($notice['id'] ?? ''),
    'type' => (string)($notice['type'] ?? ''),
    'buyer' => (string)($notice['buyer'] ?? ''),
    'seller' => (string)($notice['seller'] ?? ''),
    'buyerTeam' => is_array($notice['buyerTeam'] ?? null) ? $notice['buyerTeam'] : [],
    'sellerTeam' => is_array($notice['sellerTeam'] ?? null) ? $notice['sellerTeam'] : [],
    'player' => is_array($notice['player'] ?? null) ? public_player($notice['player']) : [],
    'cashOffer' => (int)($notice['cashOffer'] ?? 0),
    'offerPlayer' => is_array($notice['offerPlayer'] ?? null) ? public_player($notice['offerPlayer']) : null,
    'offerValue' => (int)($notice['offerValue'] ?? 0),
    'offerTotal' => (int)($notice['offerTotal'] ?? 0),
    'created' => (int)($notice['created'] ?? 0),
  ];
}

function public_manager_transfer_offers(array $state, string $user): array {
  $out = ['incoming' => [], 'outgoing' => [], 'notices' => []];
  $user = clean_name($user);
  $season = $user !== '' ? current_manager_season($state, $user) : null;
  foreach (($state['managerTransferOffers'] ?? []) as $offer) {
    if (!is_array($offer) || ($offer['status'] ?? 'pending') !== 'pending') continue;
    if (!manager_market_offer_available($state, $offer, $season, $user)) continue;
    $buyer = clean_name((string)($offer['buyer'] ?? ''));
    $seller = clean_name((string)($offer['seller'] ?? ''));
    $decisionUser = clean_name((string)($offer['decisionUser'] ?? $seller));
    if ($decisionUser === '' || ($decisionUser !== $buyer && $decisionUser !== $seller)) $decisionUser = $seller;
    if ($user === $decisionUser) $out['incoming'][] = public_manager_transfer_offer($offer, $user);
    elseif ($user === $buyer || $user === $seller) $out['outgoing'][] = public_manager_transfer_offer($offer, $user);
  }
  foreach (($state['managerTransferNotices'] ?? []) as $notice) {
    if (!is_array($notice) || (string)($notice['user'] ?? '') !== $user) continue;
    $out['notices'][] = public_manager_transfer_notice($notice);
  }
  usort($out['incoming'], fn($a, $b) => (int)($b['created'] ?? 0) <=> (int)($a['created'] ?? 0));
  usort($out['outgoing'], fn($a, $b) => (int)($b['created'] ?? 0) <=> (int)($a['created'] ?? 0));
  usort($out['notices'], fn($a, $b) => (int)($b['created'] ?? 0) <=> (int)($a['created'] ?? 0));
  return $out;
}

function manager_add_purchased_player_to_user(array &$user, array $player, string $fromTeam, string $fromTeamId, string $fromDivision, int $cashPrice, int $now, ?array $outgoingFound = null): array {
  ensure_user_squad($user);
  manager_prepare_bench_for_real_receive($user);
  manager_ensure_bench_room_for_receive($user['squad'] ?? [], $outgoingFound);
  ensure_player_age_profile($player, (string)($player['originOwnerId'] ?? $fromTeamId), (string)($player['originTeamId'] ?? $fromTeamId), (string)($player['originTeamName'] ?? $fromTeam), (string)($player['originDivisionName'] ?? $fromDivision));
  ensure_player_id_unique_for_squad($player, $user['squad'] ?? []);
  $player['sourceTeam'] = $fromTeam;
  $player['sourceTeamId'] = $fromTeamId;
  $player['sourceDivision'] = $fromDivision;
  $player['purchasedFrom'] = $fromTeam;
  $player['purchasedFromId'] = $fromTeamId;
  $player['purchasedFromDivision'] = $fromDivision;
  $player['purchasedAt'] = $now;
  $player['purchasePrice'] = $cashPrice;
  unset(
    $player['price'],
    $player['soldTo'],
    $player['soldToId'],
    $player['soldToDivision'],
    $player['soldAt'],
    $player['marketListedByUser'],
    $player['marketListedAt'],
    $player['marketSalePrice'],
    $player['marketPayoutSettled'],
    $player['sellerPlayerId']
  );
  $user['squad']['bench'][] = $player;
  manager_sync_emergency_loan_bench($user);
  ensure_squad_stats($user['squad']);
  return $player;
}

function manager_find_user_squad_player(array $user, string $playerId): ?array {
  ensure_user_squad($user);
  return find_squad_player($user['squad'] ?? [], $playerId);
}

function manager_create_human_transfer_offer(array &$state, array $season, string $buyer, string $seller, array $details, array $player, array $data, int $now): array {
  if ($buyer === $seller || !isset($state['users'][$seller]) || !has_team($state['users'][$seller])) reply(['ok' => false, 'error' => 'human_transfer_locked'], 409);
  ensure_user_squad($state['users'][$buyer]);
  ensure_user_squad($state['users'][$seller]);
  ensure_manager_finance($state['users'][$buyer]);
  $team = $details['team'] ?? user_team($state['users'][$seller]);
  $buyerTeam = user_team($state['users'][$buyer]);
  $divisionName = (string)($details['divisionName'] ?? $season['divisionName'] ?? '');
  $basePrice = (int)($player['price'] ?? $player['value'] ?? player_value($player));
  $cashOffer = manager_transfer_cash_offer($data, $basePrice);
  if ((int)$state['users'][$buyer]['managerFinance']['balance'] < $cashOffer) reply(['ok' => false, 'error' => 'no_money'], 409);
  $offerPlayerId = trim((string)($data['offerPlayerId'] ?? ''));
  $offerPlayer = manager_transfer_offer_player($state['users'][$buyer], $offerPlayerId);
  if ($offerPlayerId !== '' && !$offerPlayer) reply(['ok' => false, 'error' => 'bad_offer_player'], 404);
  manager_ensure_bench_room_for_receive($state['users'][$buyer]['squad'] ?? [], $offerPlayer);
  $offerValue = $offerPlayer ? (int)$offerPlayer['value'] : 0;
  $offerId = 'h' . bin2hex(random_bytes(6));
  if (!isset($state['managerTransferOffers']) || !is_array($state['managerTransferOffers'])) $state['managerTransferOffers'] = [];
  $state['managerTransferOffers'][$offerId] = [
    'id' => $offerId,
    'status' => 'pending',
	    'seasonId' => (string)($season['id'] ?? ''),
	    'buyer' => $buyer,
	    'seller' => $seller,
	    'decisionUser' => $seller,
	    'buyerTeam' => $buyerTeam,
	    'sellerTeam' => $team,
    'divisionName' => $divisionName,
    'playerId' => (string)($player['id'] ?? ''),
    'player' => $player,
    'basePrice' => $basePrice,
    'cashOffer' => $cashOffer,
    'offerPlayerId' => $offerPlayerId,
    'offerPlayer' => $offerPlayer ? $offerPlayer['player'] : null,
    'offerValue' => $offerValue,
    'offerTotal' => $cashOffer + $offerValue,
    'created' => $now,
    'expires' => $now + 604800,
  ];
  manager_log($state, 'human_transfer_offer', ['offerId' => $offerId, 'buyer' => $buyer, 'seller' => $seller, 'player' => $player['name'] ?? '', 'cash' => $cashOffer, 'offerValue' => $offerValue]);
  $message = ($cashOffer + $offerValue) < $basePrice
    ? 'Low bid sent. Waiting for manager approval.'
    : 'Offer sent. Waiting for manager approval.';
  return [
    'status' => 'human_offer',
    'offerId' => $offerId,
    'player' => public_player($player),
    'team' => $team,
    'basePrice' => $basePrice,
    'cashPrice' => $cashOffer,
    'offerValue' => $offerValue,
    'offerTotal' => $cashOffer + $offerValue,
    'managerTransferOffers' => public_manager_transfer_offers($state, $buyer),
    'message' => $message,
  ];
}

function manager_create_market_transfer_offer(array &$state, ?array $season, string $buyer, array $player, array $data, int $now): array {
  $seller = clean_name((string)($player['marketListedByUser'] ?? ''));
  if ($seller === '' || $buyer === $seller || !isset($state['users'][$seller]) || !has_team($state['users'][$seller])) reply(['ok' => false, 'error' => 'human_transfer_locked'], 409);
  if (!manager_market_listing_is_active($state, $player, $season, $buyer)) reply(['ok' => false, 'error' => 'stale_listing'], 409);
  ensure_user_squad($state['users'][$buyer]);
  ensure_user_squad($state['users'][$seller]);
  ensure_manager_finance($state['users'][$buyer]);
  $sellerTeam = has_team($state['users'][$seller]) ? user_team($state['users'][$seller]) : ['id' => $seller, 'name' => strtoupper($seller)];
  $buyerTeam = user_team($state['users'][$buyer]);
  $divisionName = (string)($player['sourceDivision'] ?? $season['divisionName'] ?? '');
  $listedPrice = (int)($player['price'] ?? $player['marketSalePrice'] ?? $player['value'] ?? player_value($player));
  $marketRule = manager_market_division_rule($player, manager_user_division_level($state, $buyer), $listedPrice);
  $basePrice = (int)$marketRule['price'];
  $cashOffer = manager_transfer_cash_offer($data, $basePrice);
  if ((int)$state['users'][$buyer]['managerFinance']['balance'] < $cashOffer) reply(['ok' => false, 'error' => 'no_money'], 409);
  $offerPlayerId = trim((string)($data['offerPlayerId'] ?? ''));
  $offerPlayer = manager_transfer_offer_player($state['users'][$buyer], $offerPlayerId);
  if ($offerPlayerId !== '' && !$offerPlayer) reply(['ok' => false, 'error' => 'bad_offer_player'], 404);
  manager_ensure_bench_room_for_receive($state['users'][$buyer]['squad'] ?? [], $offerPlayer);
  $offerValue = $offerPlayer ? (int)$offerPlayer['value'] : 0;
  $offerId = 'm' . bin2hex(random_bytes(6));
  $payoutSettled = array_key_exists('marketPayoutSettled', $player) ? !empty($player['marketPayoutSettled']) : true;
  if (!isset($state['managerTransferOffers']) || !is_array($state['managerTransferOffers'])) $state['managerTransferOffers'] = [];
  $state['managerTransferOffers'][$offerId] = [
    'id' => $offerId,
    'status' => 'pending',
    'seasonId' => (string)($season['id'] ?? ''),
    'buyer' => $buyer,
    'seller' => $seller,
    'decisionUser' => $seller,
    'buyerTeam' => $buyerTeam,
    'sellerTeam' => $sellerTeam,
    'divisionName' => $divisionName,
    'marketPlayerId' => (string)($player['id'] ?? ''),
    'marketPayoutSettled' => $payoutSettled,
    'playerId' => (string)($player['sellerPlayerId'] ?? $player['id'] ?? ''),
	    'player' => $player,
	    'basePrice' => $basePrice,
	    'marketBasePrice' => (int)$marketRule['listedPrice'],
	    'marketDivisionGap' => (int)$marketRule['gap'],
	    'marketPricePremiumPct' => (int)$marketRule['premiumPct'],
	    'cashOffer' => $cashOffer,
    'offerPlayerId' => $offerPlayerId,
    'offerPlayer' => $offerPlayer ? $offerPlayer['player'] : null,
    'offerValue' => $offerValue,
    'offerTotal' => $cashOffer + $offerValue,
    'created' => $now,
    'expires' => $now + 604800,
  ];
  manager_log($state, 'market_human_transfer_offer', ['offerId' => $offerId, 'buyer' => $buyer, 'seller' => $seller, 'player' => $player['name'] ?? '', 'cash' => $cashOffer, 'offerValue' => $offerValue]);
  $message = ($cashOffer + $offerValue) < $basePrice
    ? 'Low bid sent. Waiting for manager approval.'
    : 'Offer sent. Waiting for manager approval.';
  return [
    'status' => 'human_offer',
    'offerId' => $offerId,
    'player' => public_player($player),
    'team' => $sellerTeam,
	    'basePrice' => $basePrice,
	    'marketBasePrice' => (int)$marketRule['listedPrice'],
	    'marketDivisionGap' => (int)$marketRule['gap'],
	    'marketPricePremiumPct' => (int)$marketRule['premiumPct'],
	    'cashPrice' => $cashOffer,
    'offerValue' => $offerValue,
    'offerTotal' => $cashOffer + $offerValue,
    'managerFinance' => public_finance($state['users'][$buyer]),
    'managerMarket' => public_manager_market($state, $season, $buyer),
    'managerTransferOffers' => public_manager_transfer_offers($state, $buyer),
    'message' => $message,
  ];
}

function manager_accept_cpu_market_offer(array &$state, string $user, string $offerId, int $now): array {
  $user = clean_name($user);
  if ($offerId === '' || empty($state['managerTransferOffers'][$offerId]) || !is_array($state['managerTransferOffers'][$offerId])) reply(['ok' => false, 'error' => 'bad_offer'], 404);
  $offer = $state['managerTransferOffers'][$offerId];
  $seller = clean_name((string)($offer['seller'] ?? ''));
  $buyer = clean_name((string)($offer['buyer'] ?? ''));
  if ($user === '' || $seller !== $user || !is_cpu_manager($buyer)) reply(['ok' => false, 'error' => 'bad_offer'], 404);
  if (!isset($state['users'][$seller])) reply(['ok' => false, 'error' => 'bad_offer'], 404);
  ensure_user_squad($state['users'][$seller]);
  ensure_manager_finance($state['users'][$seller]);

  $marketPlayerId = (string)($offer['marketPlayerId'] ?? '');
  $marketIndex = null;
  $marketPlayer = null;
  foreach (($state['managerMarket'] ?? []) as $index => $candidate) {
    if (!is_array($candidate) || (string)($candidate['id'] ?? '') !== $marketPlayerId) continue;
    if (clean_name((string)($candidate['marketListedByUser'] ?? '')) !== $seller) continue;
    $marketIndex = $index;
    $marketPlayer = $candidate;
    break;
  }
  if ($marketIndex === null || !is_array($marketPlayer)) reply(['ok' => false, 'error' => 'bad_player'], 404);
  if (!manager_market_listing_is_active($state, $marketPlayer, current_manager_season($state, $seller), $seller)) {
    unset($state['managerTransferOffers'][$offerId]);
    reply(['ok' => false, 'error' => 'stale_listing'], 409);
  }

  $cash = max(0, (int)($offer['cashOffer'] ?? 0));
  $payoutSettled = array_key_exists('marketPayoutSettled', $offer) ? !empty($offer['marketPayoutSettled']) : !empty($marketPlayer['marketPayoutSettled']);
  $sellerPlayerId = (string)($marketPlayer['sellerPlayerId'] ?? $offer['playerId'] ?? '');
  $sellerFound = null;
  if (!$payoutSettled) {
    if ($sellerPlayerId === '') reply(['ok' => false, 'error' => 'bad_player'], 404);
    $sellerFound = manager_find_user_squad_player($state['users'][$seller], $sellerPlayerId);
    if (!$sellerFound) reply(['ok' => false, 'error' => 'bad_player'], 404);
    if (($sellerFound['group'] ?? '') === 'starters' && empty($state['users'][$seller]['squad']['bench'])) reply(['ok' => false, 'error' => 'no_replacement'], 409);
  }

  $buyerTeam = is_array($offer['buyerTeam'] ?? null) ? $offer['buyerTeam'] : ['id' => $buyer, 'name' => strtoupper($buyer)];
  $divisionName = (string)($offer['divisionName'] ?? $marketPlayer['sourceDivision'] ?? '');
  $soldPlayer = $sellerFound
    ? manager_take_transfer_offer_player($state['users'][$seller], $sellerPlayerId, (string)($buyerTeam['name'] ?? strtoupper($buyer)), $buyer, $divisionName, $now)
    : $marketPlayer;
  if (!$payoutSettled) {
    $state['users'][$seller]['managerFinance']['balance'] += $cash;
    $state['users'][$seller]['managerFinance']['transferIncome'] += $cash;
  }

  array_splice($state['managerMarket'], (int)$marketIndex, 1);
  $removeOffers = [];
  foreach (($state['managerTransferOffers'] ?? []) as $id => $other) {
    if (!is_array($other) || ($other['status'] ?? 'pending') !== 'pending') continue;
    if ((string)($other['marketPlayerId'] ?? '') !== $marketPlayerId) continue;
    if ($id !== $offerId && !is_cpu_manager((string)($other['buyer'] ?? ''))) {
      manager_add_transfer_notice($state, (string)($other['buyer'] ?? ''), 'rejected', $other, $now);
    }
    $removeOffers[] = $id;
  }
  foreach ($removeOffers as $id) unset($state['managerTransferOffers'][$id]);
  ensure_manager_market($state);

  manager_log($state, 'cpu_market_transfer_accept', ['offerId' => $offerId, 'buyer' => $buyer, 'seller' => $seller, 'player' => $soldPlayer['name'] ?? '', 'cash' => $cash]);
  $season = current_manager_season($state, $user);
  return [
    'status' => 'accepted',
    '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, $user),
    'managerTransferOffers' => public_manager_transfer_offers($state, $user),
    'acceptedOffer' => public_player($soldPlayer),
    'cashPrice' => $cash,
    'offerValue' => 0,
  ];
}

function manager_transfer_offer_counter(array &$state, string $user, string $offerId, array $data, int $now): array {
  $user = clean_name($user);
  if ($offerId === '' || empty($state['managerTransferOffers'][$offerId]) || !is_array($state['managerTransferOffers'][$offerId])) reply(['ok' => false, 'error' => 'bad_offer'], 404);
  $offer =& $state['managerTransferOffers'][$offerId];
  if (($offer['status'] ?? 'pending') !== 'pending') reply(['ok' => false, 'error' => 'offer_closed'], 409);
  $seller = clean_name((string)($offer['seller'] ?? ''));
  $buyer = clean_name((string)($offer['buyer'] ?? ''));
  $decisionUser = clean_name((string)($offer['decisionUser'] ?? $seller));
  if ($user === '' || $user !== $seller || $decisionUser !== $seller) reply(['ok' => false, 'error' => 'forbidden'], 403);
  if ((int)($offer['expires'] ?? 0) > 0 && (int)$offer['expires'] < $now) {
    unset($state['managerTransferOffers'][$offerId]);
    unset($offer);
    reply(['ok' => false, 'error' => 'offer_expired'], 409);
  }
  if (!manager_market_offer_available($state, $offer, current_manager_season($state, $user), $user)) {
    unset($state['managerTransferOffers'][$offerId]);
    unset($offer);
    reply(['ok' => false, 'error' => 'stale_listing'], 409);
  }
  $currentCash = (int)($offer['cashOffer'] ?? 0);
  $counterCash = manager_transfer_cash_offer($data, $currentCash);
  if ($counterCash <= $currentCash) reply(['ok' => false, 'error' => 'counter_too_low', 'cashOffer' => $currentCash], 409);
  $offerValue = (int)($offer['offerValue'] ?? 0);
  $offer['cashOffer'] = $counterCash;
  $offer['offerTotal'] = $counterCash + $offerValue;
  $offer['countered'] = true;
  $offer['counterBy'] = $user;
  $offer['counterAt'] = $now;
  $offer['counterCount'] = (int)($offer['counterCount'] ?? 0) + 1;
  if (is_cpu_manager($buyer) && (string)($offer['marketPlayerId'] ?? '') !== '') {
    $marketPlayerId = (string)($offer['marketPlayerId'] ?? '');
    $basePrice = max(1, (int)($offer['basePrice'] ?? $currentCash));
    $ratio = $counterCash / $basePrice;
    $chance = clamp_int((int)round(86 - (max(0, $ratio - 1.0) * 150)), 12, 92);
    manager_log($state, 'cpu_market_transfer_counter', ['offerId' => $offerId, 'buyer' => $buyer, 'seller' => $seller, 'player' => $offer['player']['name'] ?? '', 'cash' => $counterCash, 'chance' => $chance]);
    unset($offer);
    if (mt_rand(1, 100) <= $chance) return manager_accept_cpu_market_offer($state, $user, $offerId, $now);
    manager_reschedule_cpu_market_offer($state, current_manager_season($state, $user), $user, $marketPlayerId, 1, 4);
    unset($state['managerTransferOffers'][$offerId]);
    return [
      'status' => 'counter_rejected',
      'managerSeason' => public_manager_season(current_manager_season($state, $user), $state, $user),
      'managerFinance' => public_finance($state['users'][$user]),
      'managerSquad' => public_manager_context_squad($state, $user, current_manager_season($state, $user)),
      'managerMarket' => public_manager_market($state, current_manager_season($state, $user), $user),
      'managerTransferOffers' => public_manager_transfer_offers($state, $user),
    ];
  }
  $offer['decisionUser'] = $buyer;
  manager_log($state, 'human_transfer_counter', ['offerId' => $offerId, 'buyer' => $buyer, 'seller' => $seller, 'player' => $offer['player']['name'] ?? '', 'cash' => $counterCash, 'offerValue' => $offerValue]);
  unset($offer);
  return [
    'status' => 'countered',
    'managerTransferOffers' => public_manager_transfer_offers($state, $user),
  ];
}

function manager_transfer_offer_response(array &$state, string $user, string $offerId, string $decision, int $now): array {
  $user = clean_name($user);
  if ($offerId === '' || empty($state['managerTransferOffers'][$offerId]) || !is_array($state['managerTransferOffers'][$offerId])) reply(['ok' => false, 'error' => 'bad_offer'], 404);
  $offer =& $state['managerTransferOffers'][$offerId];
  if (($offer['status'] ?? 'pending') !== 'pending') reply(['ok' => false, 'error' => 'offer_closed'], 409);
  $buyer = clean_name((string)($offer['buyer'] ?? ''));
  $seller = clean_name((string)($offer['seller'] ?? ''));
  $decisionUser = clean_name((string)($offer['decisionUser'] ?? $seller));
  if ($decisionUser === '' || ($decisionUser !== $buyer && $decisionUser !== $seller)) $decisionUser = $seller;
  if ($user === '' || $decisionUser !== $user) reply(['ok' => false, 'error' => 'forbidden'], 403);
  if ((int)($offer['expires'] ?? 0) > 0 && (int)$offer['expires'] < $now) {
    unset($state['managerTransferOffers'][$offerId]);
    unset($offer);
    reply(['ok' => false, 'error' => 'offer_expired'], 409);
  }
  if (!manager_market_offer_available($state, $offer, current_manager_season($state, $user), $user)) {
    unset($state['managerTransferOffers'][$offerId]);
    unset($offer);
    reply(['ok' => false, 'error' => 'stale_listing'], 409);
  }
  if ($decision !== 'accept') {
    $offerForNotice = $offer;
    $offer['status'] = 'rejected';
    $offer['decidedAt'] = $now;
    $offer['decidedBy'] = $user;
    $noticeUser = $user === $buyer ? $seller : $buyer;
    $noticeType = $user === $buyer ? 'counter_rejected' : 'rejected';
    if (!is_cpu_manager($noticeUser)) manager_add_transfer_notice($state, $noticeUser, $noticeType, $offerForNotice, $now);
    elseif ((string)($offerForNotice['marketPlayerId'] ?? '') !== '') manager_reschedule_cpu_market_offer($state, current_manager_season($state, $seller), $seller, (string)$offerForNotice['marketPlayerId'], 1, 4);
    unset($state['managerTransferOffers'][$offerId]);
    unset($offer);
    return ['managerTransferOffers' => public_manager_transfer_offers($state, $user), 'status' => 'rejected'];
  }

  if (is_cpu_manager($buyer) && (string)($offer['marketPlayerId'] ?? '') !== '') {
    unset($offer);
    return manager_accept_cpu_market_offer($state, $user, $offerId, $now);
  }

  if ($buyer === '' || $seller === '' || !isset($state['users'][$buyer], $state['users'][$seller])) reply(['ok' => false, 'error' => 'bad_offer'], 404);
  ensure_user_squad($state['users'][$buyer]);
  ensure_user_squad($state['users'][$seller]);
  ensure_manager_finance($state['users'][$buyer]);
  ensure_manager_finance($state['users'][$seller]);
  $cash = (int)($offer['cashOffer'] ?? 0);
  if ((int)$state['users'][$buyer]['managerFinance']['balance'] < $cash) reply(['ok' => false, 'error' => 'no_money'], 409);

  $marketPlayerId = (string)($offer['marketPlayerId'] ?? '');
  if ($marketPlayerId !== '') {
    $marketIndex = null;
    $marketPlayer = null;
    foreach (($state['managerMarket'] ?? []) as $index => $candidate) {
      if (!is_array($candidate) || (string)($candidate['id'] ?? '') !== $marketPlayerId) continue;
      if ((string)($candidate['marketListedByUser'] ?? '') !== $seller) continue;
      $marketIndex = $index;
      $marketPlayer = $candidate;
      break;
    }
    if ($marketIndex === null || !is_array($marketPlayer)) reply(['ok' => false, 'error' => 'bad_player'], 404);

    $offerPlayerId = (string)($offer['offerPlayerId'] ?? '');
    $buyerOfferFound = $offerPlayerId !== '' ? manager_find_user_squad_player($state['users'][$buyer], $offerPlayerId) : null;
    if ($offerPlayerId !== '' && !$buyerOfferFound) reply(['ok' => false, 'error' => 'bad_offer_player'], 404);

    $payoutSettled = array_key_exists('marketPayoutSettled', $offer) ? !empty($offer['marketPayoutSettled']) : (array_key_exists('marketPayoutSettled', $marketPlayer) ? !empty($marketPlayer['marketPayoutSettled']) : true);
    $sellerPlayerId = (string)($marketPlayer['sellerPlayerId'] ?? $offer['playerId'] ?? '');
    $sellerFound = null;
    if (!$payoutSettled && $sellerPlayerId !== '') {
      $sellerFound = manager_find_user_squad_player($state['users'][$seller], $sellerPlayerId);
      if (!$sellerFound) reply(['ok' => false, 'error' => 'bad_player'], 404);
      if (($sellerFound['group'] ?? '') === 'starters' && empty($state['users'][$seller]['squad']['bench']) && $offerPlayerId === '') reply(['ok' => false, 'error' => 'no_replacement'], 409);
    }
    manager_ensure_bench_room_for_receive($state['users'][$buyer]['squad'] ?? [], $buyerOfferFound);
    if ($offerPlayerId !== '') manager_ensure_bench_room_for_receive($state['users'][$seller]['squad'] ?? [], $sellerFound);

    $buyerBefore = $state['users'][$buyer]['squad'] ?? ['starters' => [], 'bench' => []];
    $sellerTeam = user_team($state['users'][$seller]);
    $buyerTeam = user_team($state['users'][$buyer]);
    $divisionName = (string)($offer['divisionName'] ?? $marketPlayer['sourceDivision'] ?? '');
    $fromTeam = (string)($marketPlayer['sourceTeam'] ?? $sellerTeam['name'] ?? strtoupper($seller));
    $salePlayer = $sellerFound ? $sellerFound['player'] : $marketPlayer;
    $boughtPlayer = manager_add_purchased_player_to_user($state['users'][$buyer], $salePlayer, $fromTeam, $seller, $divisionName, $cash, $now, $buyerOfferFound);

    $tradedPlayer = null;
    if ($offerPlayerId !== '') {
      $tradedPlayer = manager_take_transfer_offer_player($state['users'][$buyer], $offerPlayerId, (string)($sellerTeam['name'] ?? strtoupper($seller)), $seller, $divisionName, $now);
      if ($tradedPlayer) {
        manager_add_purchased_player_to_user($state['users'][$seller], $tradedPlayer, (string)($buyerTeam['name'] ?? strtoupper($buyer)), $buyer, (string)($state['users'][$buyer]['managerFinance']['divisionName'] ?? ''), 0, $now, $sellerFound);
      }
    }
    if ($sellerFound) {
      manager_take_transfer_offer_player($state['users'][$seller], $sellerPlayerId, (string)($buyerTeam['name'] ?? strtoupper($buyer)), $buyer, (string)($state['users'][$buyer]['managerFinance']['divisionName'] ?? ''), $now);
    }

    $state['users'][$buyer]['managerFinance']['balance'] -= $cash;
    $state['users'][$buyer]['managerFinance']['transferSpent'] += $cash;
    if (!$payoutSettled) {
      $state['users'][$seller]['managerFinance']['balance'] += $cash;
      $state['users'][$seller]['managerFinance']['transferIncome'] += $cash;
    }

    array_splice($state['managerMarket'], (int)$marketIndex, 1);
    ensure_manager_market($state);

    $offer['status'] = 'accepted';
    $offer['decidedAt'] = $now;
    $offer['decidedBy'] = $user;
    $offerForNotice = $offer;
    manager_add_transfer_notice($state, $user === $buyer ? $seller : $buyer, $user === $buyer ? 'counter_accepted' : 'accepted', $offerForNotice, $now);
    foreach (($state['managerTransferOffers'] ?? []) as $id => &$other) {
      if ($id === $offerId || !is_array($other) || ($other['status'] ?? 'pending') !== 'pending') continue;
      if ((string)($other['marketPlayerId'] ?? '') === $marketPlayerId) {
        if (!is_cpu_manager((string)($other['buyer'] ?? ''))) manager_add_transfer_notice($state, (string)($other['buyer'] ?? ''), 'rejected', $other, $now);
        $other['status'] = 'superseded';
        $other['decidedAt'] = $now;
      }
    }
    unset($other, $offer);
    unset($state['managerTransferOffers'][$offerId]);

    manager_log($state, 'market_human_transfer_accept', ['offerId' => $offerId, 'buyer' => $buyer, 'seller' => $seller, 'player' => $boughtPlayer['name'] ?? '', 'cash' => $cash]);
    return [
      'status' => 'accepted',
      'managerSeason' => public_manager_season(current_manager_season($state, $user), $state, $user),
      'managerFinance' => public_finance($state['users'][$user]),
      'managerSquad' => public_manager_context_squad($state, $user, current_manager_season($state, $user)),
      'managerMarket' => public_manager_market($state, current_manager_season($state, $user), $user),
      'managerTransferOffers' => public_manager_transfer_offers($state, $user),
      'acceptedOffer' => public_player($boughtPlayer),
      'cashPrice' => $cash,
      'offerValue' => $tradedPlayer ? (int)($tradedPlayer['value'] ?? player_value($tradedPlayer)) : 0,
      'skillChange' => $buyer === $user ? manager_browse_skill_change($buyerBefore, $state['users'][$buyer]['squad'] ?? []) : null,
    ];
  }

  $sellerPlayerId = (string)($offer['playerId'] ?? $offer['player']['id'] ?? '');
  $sellerFound = manager_find_user_squad_player($state['users'][$seller], $sellerPlayerId);
  if (!$sellerFound) reply(['ok' => false, 'error' => 'bad_player'], 404);
  $offerPlayerId = (string)($offer['offerPlayerId'] ?? '');
  $buyerOfferFound = $offerPlayerId !== '' ? manager_find_user_squad_player($state['users'][$buyer], $offerPlayerId) : null;
  if ($offerPlayerId !== '' && !$buyerOfferFound) reply(['ok' => false, 'error' => 'bad_offer_player'], 404);
  if (($sellerFound['group'] ?? '') === 'starters' && empty($state['users'][$seller]['squad']['bench']) && $offerPlayerId === '') reply(['ok' => false, 'error' => 'no_replacement'], 409);
  manager_ensure_bench_room_for_receive($state['users'][$buyer]['squad'] ?? [], $buyerOfferFound);
  if ($offerPlayerId !== '') manager_ensure_bench_room_for_receive($state['users'][$seller]['squad'] ?? [], $sellerFound);

  $buyerBefore = $state['users'][$buyer]['squad'] ?? ['starters' => [], 'bench' => []];
  $sellerTeam = user_team($state['users'][$seller]);
  $buyerTeam = user_team($state['users'][$buyer]);
  $divisionName = (string)($offer['divisionName'] ?? '');

  $sellerPlayer = $sellerFound['player'];
  $boughtPlayer = manager_add_purchased_player_to_user($state['users'][$buyer], $sellerPlayer, (string)($sellerTeam['name'] ?? strtoupper($seller)), $seller, $divisionName, $cash, $now, $buyerOfferFound);

  $tradedPlayer = null;
  if ($offerPlayerId !== '') {
    $tradedPlayer = manager_take_transfer_offer_player($state['users'][$buyer], $offerPlayerId, (string)($sellerTeam['name'] ?? strtoupper($seller)), $seller, $divisionName, $now);
    if ($tradedPlayer) {
      manager_add_purchased_player_to_user($state['users'][$seller], $tradedPlayer, (string)($buyerTeam['name'] ?? strtoupper($buyer)), $buyer, (string)($state['users'][$buyer]['managerFinance']['divisionName'] ?? ''), 0, $now, $sellerFound);
    }
  }
  manager_take_transfer_offer_player($state['users'][$seller], $sellerPlayerId, (string)($buyerTeam['name'] ?? strtoupper($buyer)), $buyer, (string)($state['users'][$buyer]['managerFinance']['divisionName'] ?? ''), $now);

  $state['users'][$buyer]['managerFinance']['balance'] -= $cash;
  $state['users'][$buyer]['managerFinance']['transferSpent'] += $cash;
  $state['users'][$seller]['managerFinance']['balance'] += $cash;
  $state['users'][$seller]['managerFinance']['transferIncome'] += $cash;

  $offer['status'] = 'accepted';
  $offer['decidedAt'] = $now;
  $offer['decidedBy'] = $user;
  $offerForNotice = $offer;
  manager_add_transfer_notice($state, $user === $buyer ? $seller : $buyer, $user === $buyer ? 'counter_accepted' : 'accepted', $offerForNotice, $now);
  foreach (($state['managerTransferOffers'] ?? []) as $id => &$other) {
    if ($id === $offerId || !is_array($other) || ($other['status'] ?? 'pending') !== 'pending') continue;
    if ((string)($other['seller'] ?? '') === $seller && (string)($other['playerId'] ?? '') === $sellerPlayerId) {
      manager_add_transfer_notice($state, (string)($other['buyer'] ?? ''), 'rejected', $other, $now);
      $other['status'] = 'superseded';
      $other['decidedAt'] = $now;
    }
  }
  unset($other, $offer);
  unset($state['managerTransferOffers'][$offerId]);

  manager_log($state, 'human_transfer_accept', ['offerId' => $offerId, 'buyer' => $buyer, 'seller' => $seller, 'player' => $boughtPlayer['name'] ?? '', 'cash' => $cash]);
  return [
    'status' => 'accepted',
    'managerSeason' => public_manager_season(current_manager_season($state, $user), $state, $user),
    'managerFinance' => public_finance($state['users'][$user]),
    'managerSquad' => public_manager_context_squad($state, $user, isset($season) && is_array($season) ? $season : current_manager_season($state, $user)),
    'managerTransferOffers' => public_manager_transfer_offers($state, $user),
    'acceptedOffer' => public_player($boughtPlayer),
    'cashPrice' => $cash,
    'offerValue' => $tradedPlayer ? (int)($tradedPlayer['value'] ?? player_value($tradedPlayer)) : 0,
    'skillChange' => $buyer === $user ? manager_browse_skill_change($buyerBefore, $state['users'][$buyer]['squad'] ?? []) : null,
  ];
}

function next_transfer_player_id(array $squad): string {
  $used = [];
  foreach (array_merge($squad['starters'] ?? [], $squad['bench'] ?? []) as $player) {
    $used[(string)($player['id'] ?? '')] = true;
  }
  do {
    $id = 'T' . bin2hex(random_bytes(4));
  } while (isset($used[$id]));
  return $id;
}

function remove_manager_browse_player_from_current_season(array &$state, string $teamUser, string $playerId, string $user = ''): void {
  $seasonId = (string)(current_manager_season_id($state, $user) ?? '');
  if ($seasonId === '' || empty($state['managerSeasons'][$seasonId]['participants'])) return;
  foreach ($state['managerSeasons'][$seasonId]['participants'] as &$participant) {
    if ((string)($participant['user'] ?? '') !== $teamUser) continue;
    foreach (['starters', 'bench'] as $group) {
      if (empty($participant['squad'][$group]) || !is_array($participant['squad'][$group])) continue;
      foreach ($participant['squad'][$group] as $index => $player) {
        if ((string)($player['id'] ?? '') !== $playerId) continue;
        array_splice($participant['squad'][$group], $index, 1);
        if ($group === 'starters' && !empty($participant['squad']['bench']) && is_array($participant['squad']['bench'])) {
          $replacement = array_shift($participant['squad']['bench']);
          if ($replacement) {
            foreach (['homeX', 'homeY', 'x', 'y'] as $key) {
              if (isset($player[$key])) $replacement[$key] = $player[$key];
            }
            array_splice($participant['squad']['starters'], $index, 0, [$replacement]);
          }
        }
        ensure_squad_stats($participant['squad']);
        $participant['rating'] = squad_skill_rating($participant['squad']);
        $participant['strength'] = squad_skill_points($participant['squad']);
        unset($participant);
        return;
      }
    }
  }
  unset($participant);
}

function manager_browse_skill_change(array $beforeSquad, array $afterSquad): array {
  $rawBefore = squad_skill_points($beforeSquad);
  $rawAfter = squad_skill_points($afterSquad);
  $before = manager_display_skill($rawBefore);
  $after = manager_display_skill($rawAfter);
  return [
    'before' => $before,
    'after' => $after,
    'delta' => $after - $before,
    'rawBefore' => $rawBefore,
    'rawAfter' => $rawAfter,
    'rawDelta' => $rawAfter - $rawBefore,
  ];
}

function complete_manager_browse_purchase(array &$state, string $user, string $teamUser, array $team, string $divisionName, array $player, int $price, int $now, array $offerPlayerIds = []): array {
  ensure_user_squad($state['users'][$user]);
  ensure_manager_finance($state['users'][$user]);
  if ((int)$state['users'][$user]['managerFinance']['balance'] < $price) reply(['ok' => false, 'error' => 'no_money'], 409);

  $originalId = (string)($player['id'] ?? '');
  $soldKey = manager_browse_transfer_key($teamUser, $originalId);
  if (!empty($state['managerBrowseSold'][$soldKey])) reply(['ok' => false, 'error' => 'already_sold'], 409);
  $offerPlayerIds = array_values(array_unique(array_filter(array_map(fn($id) => trim((string)$id), $offerPlayerIds), fn($id) => $id !== '')));
  if (count($offerPlayerIds) > 3) reply(['ok' => false, 'error' => 'too_many_offer_players', 'maxPlayers' => 3], 409);
  $offerPlayers = manager_transfer_offer_players($state['users'][$user], $offerPlayerIds);
  manager_ensure_browse_trade_room($state['users'][$user]['squad'] ?? [], $offerPlayers);

  $beforeSquad = $state['users'][$user]['squad'] ?? ['starters' => [], 'bench' => []];
  $state['users'][$user]['managerFinance']['balance'] -= $price;
  $state['users'][$user]['managerFinance']['transferSpent'] += $price;
  $offeredPlayers = [];
  $offerValue = 0;
  foreach ($offerPlayerIds as $offerPlayerId) {
    $offeredPlayer = manager_take_transfer_offer_player($state['users'][$user], $offerPlayerId, (string)($team['name'] ?? 'TRANSFER BROWSE'), $teamUser, $divisionName, $now);
    if (!$offeredPlayer) continue;
    $playerValue = (int)($offeredPlayer['value'] ?? player_value($offeredPlayer));
    $offerValue += $playerValue;
    $offeredPlayers[] = $offeredPlayer;
  }
  remove_manager_browse_player_from_current_season($state, $teamUser, $originalId, $user);
  foreach ($offeredPlayers as $offeredPlayer) manager_add_trade_player_to_target($state, $user, $teamUser, $offeredPlayer, $now);

  ensure_player_age_profile($player, (string)($player['originOwnerId'] ?? $teamUser), (string)($player['originTeamId'] ?? team_source_key($team)), (string)($player['originTeamName'] ?? $team['name'] ?? 'TRANSFER BROWSE'), (string)($player['originDivisionName'] ?? $divisionName));
  ensure_player_id_unique_for_squad($player, $state['users'][$user]['squad'] ?? []);
  $player['sourceTeam'] = (string)($team['name'] ?? ($player['sourceTeam'] ?? 'TRANSFER BROWSE'));
  $player['sourceTeamId'] = $teamUser;
  $player['sourceDivision'] = $divisionName;
  $player['purchasedFrom'] = $player['sourceTeam'];
  $player['purchasedFromId'] = $teamUser;
  $player['purchasedFromDivision'] = $divisionName;
  $player['purchasedAt'] = $now;
  $player['purchasePrice'] = $price;
  unset($player['price']);
  manager_prepare_bench_for_real_receive($state['users'][$user]);
  $state['users'][$user]['squad']['bench'][] = $player;
  manager_sync_emergency_loan_bench($state['users'][$user]);
  ensure_squad_stats($state['users'][$user]['squad']);

  if (!isset($state['managerBrowseSold']) || !is_array($state['managerBrowseSold'])) $state['managerBrowseSold'] = [];
	  $state['managerBrowseSold'][$soldKey] = [
    'teamUser' => $teamUser,
    'playerId' => $originalId,
    'buyer' => $user,
    'price' => $price,
    'offerValue' => $offerValue,
    'offerPlayer' => $offeredPlayers ? [
      'id' => (string)($offeredPlayers[0]['id'] ?? ''),
      'name' => (string)($offeredPlayers[0]['name'] ?? ''),
      'value' => (int)($offeredPlayers[0]['value'] ?? player_value($offeredPlayers[0])),
    ] : null,
    'offerPlayers' => array_map(fn($offeredPlayer) => [
      'id' => (string)($offeredPlayer['id'] ?? ''),
      'name' => (string)($offeredPlayer['name'] ?? ''),
      'value' => (int)($offeredPlayer['value'] ?? player_value($offeredPlayer)),
    ], $offeredPlayers),
    'at' => $now,
  ];
  $boughtPlayer = public_player($player);
  $boughtPlayer['value'] = player_value($boughtPlayer);
  return [
    'managerSeason' => public_manager_season(current_manager_season($state, $user), $state, $user),
    'managerFinance' => public_finance($state['users'][$user]),
    'managerSquad' => public_manager_context_squad($state, $user, isset($season) && is_array($season) ? $season : current_manager_season($state, $user)),
    'boughtPlayer' => $boughtPlayer,
    'cashPrice' => $price,
    'offerValue' => $offerValue,
    'offerPlayerCount' => count($offeredPlayers),
    'offerPlayers' => array_map(fn($offeredPlayer) => public_player($offeredPlayer), $offeredPlayers),
    'offerTotal' => $price + $offerValue,
    'skillChange' => manager_browse_skill_change($beforeSquad, $state['users'][$user]['squad'] ?? []),
  ];
}

function manager_browse_counter_price(int $basePrice, int $buyerLevel, int $targetLevel, int $rating): int {
  $gap = max(0, $buyerLevel - $targetLevel);
  $multiplier = mt_rand(118, 172) / 100 + $gap * 0.16 + max(0, $rating - 75) * 0.015;
  $multiplier = clamp_float($multiplier, 1.12, 3.25);
  return max($basePrice + 25000, (int)(round(($basePrice * $multiplier) / 25000) * 25000));
}
