0
(0)
<?php


/**
 * TP FUN — VERSION "funsite" (Shortcode + Mémoire + ROI combos + Admin) — FULL
 * Shortcode : [tp_funsite_analyzer bankroll="500" stake_pct="1" bookmaker="4" retention="32"]
 *
 * ✅ FREEZE “cartes” : dès qu’il y a ≥ 3 matchs affichés, on fige la liste du jour (les matchs ne bougent plus)
 * ✅ FREEZE Top3 & Cote10 : basés sur les cartes figées, et conservés même si matchs terminés
 * ✅ ROI J-1 / J-7 / J-32 : calculé PAR COMBINÉ (Top3 = 1 bet, Cote10 = 1 bet)
 * ✅ Cote10 : si total < 10 => on n’affiche rien (et on stocke vide)
 * ✅ Mode admin : override statut + cote, save AJAX
 * ✅ UI : Bilan ROI + Admin en accordéon FERMÉ par défaut
 * ✅ FIX CSS : noms d’équipes toujours en BLANC (override thème)
 */

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

/* Polyfills temps (au cas où) */
if (!defined('MINUTE_IN_SECONDS')) define('MINUTE_IN_SECONDS', 60);
if (!defined('HOUR_IN_SECONDS'))   define('HOUR_IN_SECONDS', 3600);

/* ───────────────── CONFIG (funsite) ───────────────── */
if (!defined('TP_FUNSITE_APISPORTS_KEY'))  define('TP_FUNSITE_APISPORTS_KEY', '54064c5d72f6a5f59487a5309de88efe');
if (!defined('TP_FUNSITE_APISPORTS_HOST')) define('TP_FUNSITE_APISPORTS_HOST', 'v3.football.api-sports.io');
if (!defined('TP_FUNSITE_SEASON'))         define('TP_FUNSITE_SEASON', '2025');
if (!defined('TP_FUNSITE_TZ'))             define('TP_FUNSITE_TZ', 'Europe/Paris');
if (!defined('TP_FUNSITE_DEFAULT_BOOKMAKER')) define('TP_FUNSITE_DEFAULT_BOOKMAKER', 4);
if (!defined('TP_FUNSITE_DEFAULT_BANKROLL'))  define('TP_FUNSITE_DEFAULT_BANKROLL', 500);
if (!defined('TP_FUNSITE_DEFAULT_STAKE_PCT')) define('TP_FUNSITE_DEFAULT_STAKE_PCT', 1);
if (!defined('TP_FUNSITE_DEFAULT_RETENTION')) define('TP_FUNSITE_DEFAULT_RETENTION', 32);

/* IMPORTANT : ligues */
if (!defined('TP_FUNSITE_LEAGUE_IDS')) define('TP_FUNSITE_LEAGUE_IDS',
  '2, 3, 39, 40, 61, 62, 78, 79,
      88, 94, 95, 106, 119, 135, 136, 140, 141, 144, 172, 179, 188, 197, 203, 207, 210, 218, 271, 283, 286, 307, 318, 332, 345, 373, 848'
);

if (!defined('TP_FUNSITE_CACHE_15MIN')) define('TP_FUNSITE_CACHE_15MIN', 15 * MINUTE_IN_SECONDS);
if (!defined('TP_FUNSITE_CACHE_12H'))   define('TP_FUNSITE_CACHE_12H', 12 * HOUR_IN_SECONDS);

if (!defined('TP_FUNSITE_BTTTS_LIVE_VALIDATE'))   define('TP_FUNSITE_BTTTS_LIVE_VALIDATE', true);
if (!defined('TP_FUNSITE_VICTORY_LIVE_VALIDATE')) define('TP_FUNSITE_VICTORY_LIVE_VALIDATE', true);

/* ───────────────── API helper (funsite_) ───────────────── */
if (!function_exists('funsite_api_get')) {
  function funsite_api_get(string $endpoint, array $args = [], string $cache_key = null, int $ttl = 0) {
    // Si ton helper global existe, on l’utilise (en gardant NOS caches "funsite_")
    if (function_exists('tp_foot_api_get')) {
      return tp_foot_api_get($endpoint, $args, $cache_key, $ttl);
    }

    if ($cache_key) {
      $cached = get_transient($cache_key);
      if ($cached !== false) return $cached;
    }

    $url = add_query_arg($args, 'https://' . TP_FUNSITE_APISPORTS_HOST . '/' . ltrim($endpoint, '/'));
    $headers = ['x-apisports-key' => TP_FUNSITE_APISPORTS_KEY];

    $res = wp_remote_get($url, ['headers'=>$headers,'timeout'=>20]);
    if (is_wp_error($res)) return $res;

    $code = (int) wp_remote_retrieve_response_code($res);
    if ($code < 200 || $code >= 300) return new WP_Error('apisports_http', 'HTTP '.$code.' for '.$url);

    $data = json_decode(wp_remote_retrieve_body($res), true);
    if (json_last_error() !== JSON_ERROR_NONE) return new WP_Error('apisports_json', 'Invalid JSON for '.$url);

    if ($cache_key && $ttl > 0) set_transient($cache_key, $data, $ttl);
    return $data;
  }
}

/* ───────────────── Time helpers (funsite_) ───────────────── */
if (!function_exists('funsite_tz')) {
  function funsite_tz(): DateTimeZone { return new DateTimeZone(TP_FUNSITE_TZ); }
}
if (!function_exists('funsite_today_ymd')) {
  function funsite_today_ymd(): string { return (new DateTime('now', funsite_tz()))->format('Ymd'); }
}
if (!function_exists('funsite_ymd_days_ago')) {
  function funsite_ymd_days_ago(int $days): string {
    $dt = new DateTime('now', funsite_tz());
    if ($days > 0) $dt->modify('-'.$days.' day');
    return $dt->format('Ymd');
  }
}

/* ───────────────── Snapshots fixture (funsite_) ───────────────── */
if (!function_exists('funsite_status_short')) {
  function funsite_status_short(array $m): string {
    return strtoupper(trim((string)($m['fixture']['status']['short'] ?? '')));
  }
}
if (!function_exists('funsite_snap_key')) {
  function funsite_snap_key(int $fixture_id): string { return 'funsite_snap_'.$fixture_id; }
}
if (!function_exists('funsite_snap_load')) {
  function funsite_snap_load(int $fixture_id) {
    $snap = get_option(funsite_snap_key($fixture_id), null);
    if (!is_array($snap)) return null;

    // TTL 16h
    $ttl = 16 * HOUR_IN_SECONDS;
    $created = (int)($snap['created_at'] ?? 0);
    if ($created && (time() - $created) > $ttl) {
      delete_option(funsite_snap_key($fixture_id));
      return null;
    }
    return $snap;
  }
}
if (!function_exists('funsite_snap_save')) {
  function funsite_snap_save(int $fixture_id, array $payload) {
    $payload['created_at'] = time();
    $k = funsite_snap_key($fixture_id);
    if (get_option($k, null) === null) add_option($k, $payload, '', false);
    else update_option($k, $payload, false);
  }
}

/* ───────────────── Mémoire quotidienne (funsite_) ───────────────── */
if (!function_exists('funsite_day_key')) {
  function funsite_day_key(string $ymd): string { return 'funsite_day_'.$ymd; }
}
if (!function_exists('funsite_day_index_key')) {
  function funsite_day_index_key(): string { return 'funsite_day_index'; }
}
if (!function_exists('funsite_day_load')) {
  function funsite_day_load(string $ymd) {
    $v = get_option(funsite_day_key($ymd), null);
    return is_array($v) ? $v : null;
  }
}
if (!function_exists('funsite_day_save')) {
  function funsite_day_save(string $ymd, array $payload) {
    $payload['ymd'] = $ymd;
    $payload['created_at'] = time();
    $k = funsite_day_key($ymd);

    if (get_option($k, null) === null) add_option($k, $payload, '', false);
    else update_option($k, $payload, false);

    $idx_k = funsite_day_index_key();
    $idx = get_option($idx_k, []);
    if (!is_array($idx)) $idx = [];
    if (!in_array($ymd, $idx, true)) {
      $idx[] = $ymd;
      sort($idx);
      update_option($idx_k, $idx, false);
    }
  }
}
if (!function_exists('funsite_cleanup_history')) {
  function funsite_cleanup_history(int $keep_days) {
    $keep_days = max(1, (int)$keep_days);
    $idx_k = funsite_day_index_key();
    $idx = get_option($idx_k, []);
    if (!is_array($idx) || empty($idx)) return;

    $tz = funsite_tz();
    $cut = new DateTime('now', $tz);
    $cut->modify('-'.$keep_days.' day');
    $cut_ts = $cut->getTimestamp();

    $new = [];
    foreach ($idx as $ymd) {
      $dt = DateTime::createFromFormat('Ymd', (string)$ymd, $tz);
      $ts = $dt ? $dt->getTimestamp() : 0;
      if ($ts && $ts >= $cut_ts) $new[] = $ymd;
      else delete_option(funsite_day_key((string)$ymd));
    }
    update_option($idx_k, $new, false);
  }
}

/* ───────────────── Fetch fixture by id (funsite_) ───────────────── */
if (!function_exists('funsite_get_fixture_by_id')) {
  function funsite_get_fixture_by_id(int $fixture_id) {
    if ($fixture_id <= 0) return null;
    $ck = "funsite_fix_byid_{$fixture_id}";
    $res = funsite_api_get('fixtures', ['id'=>$fixture_id], $ck, 6 * HOUR_IN_SECONDS);
    if (is_wp_error($res)) return null;
    $arr = $res['response'] ?? [];
    return (is_array($arr) && !empty($arr)) ? ($arr[0] ?? null) : null;
  }
}
if (!function_exists('funsite_is_void_status')) {
  function funsite_is_void_status(string $short): bool {
    $short = strtoupper(trim($short));
    return in_array($short, ['PST','CANC','ABD','SUSP','INT','TBD','WO','AWD'], true);
  }
}

/* ───────────────── Fixtures window (funsite_) ───────────────── */
if (!function_exists('funsite_get_matches_window')) {
  function funsite_get_matches_window($league_ids, string $season) {

    if (is_string($league_ids)) $league_ids = array_filter(array_map('trim', explode(',', $league_ids)));
    elseif (!is_array($league_ids)) $league_ids = [$league_ids];

    $tz = funsite_tz();
    $window_start = (new DateTime('today 00:00:00', $tz))->getTimestamp();
    $window_end   = (new DateTime('tomorrow 10:00:00', $tz))->getTimestamp();

    $dates = [
      wp_date('Y-m-d', $window_start),
      wp_date('Y-m-d', (new DateTime('tomorrow', $tz))->getTimestamp()),
    ];

    $by_fixture = [];
    foreach ($dates as $date) {
      foreach ($league_ids as $lg) {
        $ck = "funsite_fix_{$lg}_{$season}_{$date}";
        $res = funsite_api_get('fixtures', ['league'=>$lg,'season'=>$season,'date'=>$date], $ck, TP_FUNSITE_CACHE_15MIN);
        if (is_wp_error($res)) continue;

        foreach (($res['response'] ?? []) as $m) {
          $ts = isset($m['fixture']['timestamp']) ? (int)$m['fixture']['timestamp'] : strtotime($m['fixture']['date'] ?? '');
          if (!$ts) continue;
          if ($ts < $window_start || $ts >= $window_end) continue;

          $fid = (int)($m['fixture']['id'] ?? 0);
          if ($fid) $by_fixture[$fid] = $m;
        }
      }
    }

    $list = array_values($by_fixture);
    usort($list, function($a, $b){
      $tsa = $a['fixture']['timestamp'] ?? strtotime($a['fixture']['date'] ?? '0');
      $tsb = $b['fixture']['timestamp'] ?? strtotime($b['fixture']['date'] ?? '0');
      if ($tsa === $tsb) return ($a['fixture']['id'] ?? 0) <=> ($b['fixture']['id'] ?? 0);
      return $tsa <=> $tsb;
    });

    return $list;
  }
}

/* ───────────────── Stats & model (funsite_) ───────────────── */
if (!function_exists('funsite_get_team_stats')) {
  function funsite_get_team_stats(int $team_id, string $season, int $league_id) {
    $ck = "funsite_teamstats_{$league_id}_{$season}_{$team_id}";
    $data = funsite_api_get('teams/statistics', ['season'=>$season,'team'=>$team_id,'league'=>$league_id], $ck, TP_FUNSITE_CACHE_12H);
    return is_wp_error($data) ? $data : $data;
  }
}
if (!function_exists('funsite_get_last5_goals')) {
  function funsite_get_last5_goals(int $team_id, int $league_id, string $season) {
    $ck = "funsite_last5_goals_{$league_id}_{$season}_{$team_id}";
    $res = funsite_api_get('fixtures', ['team'=>$team_id,'league'=>$league_id,'season'=>$season,'last'=>5], $ck, TP_FUNSITE_CACHE_12H);
    if (is_wp_error($res)) return ['goals_for'=>0,'goals_against'=>0,'matches_with_goals'=>0,'matches_conceded_goals'=>0,'response'=>[]];

    $fixtures = $res['response'] ?? [];
    $gf=$ga=$scored=$conceded=0;
    foreach ($fixtures as $m) {
      $isHome = (($m['teams']['home']['id'] ?? null) == $team_id);
      $for = $isHome ? (int)($m['goals']['home'] ?? 0) : (int)($m['goals']['away'] ?? 0);
      $agt = $isHome ? (int)($m['goals']['away'] ?? 0) : (int)($m['goals']['home'] ?? 0);
      $gf += $for; $ga += $agt;
      if ($for>0) $scored++;
      if ($agt>0) $conceded++;
    }
    return ['goals_for'=>$gf,'goals_against'=>$ga,'matches_with_goals'=>$scored,'matches_conceded_goals'=>$conceded,'response'=>$fixtures];
  }
}
if (!function_exists('funsite_get_last5_points')) {
  function funsite_get_last5_points(int $team_id, int $league_id, string $season) {
    $ck = "funsite_last5_pts_{$league_id}_{$season}_{$team_id}";
    $res = funsite_api_get('fixtures', ['team'=>$team_id,'league'=>$league_id,'season'=>$season,'last'=>5], $ck, TP_FUNSITE_CACHE_12H);
    if (is_wp_error($res)) return 0;

    $pts = 0;
    foreach (($res['response'] ?? []) as $m) {
      $isHome = (($m['teams']['home']['id'] ?? null) == $team_id);
      $homeW = (bool)($m['teams']['home']['winner'] ?? false);
      $awayW = (bool)($m['teams']['away']['winner'] ?? false);
      if ($isHome) $pts += $homeW ? 3 : ((!$awayW) ? 1 : 0);
      else        $pts += $awayW ? 3 : ((!$homeW) ? 1 : 0);
    }
    return $pts;
  }
}
if (!function_exists('funsite_get_standings')) {
  function funsite_get_standings(int $league_id, string $season) {
    $ck = "funsite_standings_{$league_id}_{$season}";
    $data = funsite_api_get('standings', ['league'=>$league_id,'season'=>$season], $ck, TP_FUNSITE_CACHE_12H);
    return is_wp_error($data) ? [] : ($data['response'] ?? []);
  }
}
if (!function_exists('funsite_get_team_category')) {
  function funsite_get_team_category(int $team_id, array $standings) {
    $rows = $standings[0]['league']['standings'][0] ?? [];
    if (empty($rows)) return 'Non classé';
    $n = count($rows);
    $q = max(1, (int)ceil($n/4));
    foreach ($rows as $row) {
      if (($row['team']['id'] ?? null) == $team_id) {
        $rank = (int)($row['rank'] ?? 0);
        if ($rank <= $q) return '1';
        if ($rank <= 2*$q) return '2';
        if ($rank <= 3*$q) return '3';
        return '4';
      }
    }
    return 'Non classé';
  }
}
if (!function_exists('funsite_cat_multiplier')) {
  function funsite_cat_multiplier($self, $opp) {
    $table = [
      '1' => ['1'=>1.00,'2'=>1.10,'3'=>1.20,'4'=>1.30],
      '2' => ['1'=>0.90,'2'=>1.00,'3'=>1.05,'4'=>1.10],
      '3' => ['1'=>0.80,'2'=>0.95,'3'=>1.00,'4'=>1.05],
      '4' => ['1'=>0.70,'2'=>0.90,'3'=>0.95,'4'=>1.00],
    ];
    return $table[$self][$opp] ?? 1.00;
  }
}
if (!function_exists('funsite_margin_over_threshold')) {
  function funsite_margin_over_threshold(float $diff, bool $is_home_winner): float {
    return $is_home_winner ? ($diff - 1.50) : ((-1.0 * $diff) - 1.60);
  }
}
if (!function_exists('funsite_conf_from_margin')) {
  function funsite_conf_from_margin(float $m, float $cap): int {
    if ($m < 0.00) return 0;
    if ($m >= $cap) return 95;
    if ($m >= 1.00) return 90;
    if ($m >= 0.80) return 88;
    if ($m >= 0.60) return 85;
    if ($m >= 0.40) return 81;
    if ($m >= 0.20) return 75;
    return 60;
  }
}

/* ───────────────── Build prediction (funsite_) ───────────────── */
if (!function_exists('funsite_build_stats_and_predictions')) {
  function funsite_build_stats_and_predictions(array $match, string $season) {
    $league_id  = (int)($match['league']['id'] ?? 0);
    $team1_id   = (int)($match['teams']['home']['id'] ?? 0);
    $team2_id   = (int)($match['teams']['away']['id'] ?? 0);
    $team1_name = (string)($match['teams']['home']['name'] ?? '');
    $team2_name = (string)($match['teams']['away']['name'] ?? '');

    if (!$league_id || !$team1_id || !$team2_id) return [];

    $t1 = funsite_get_team_stats($team1_id, $season, $league_id);
    $t2 = funsite_get_team_stats($team2_id, $season, $league_id);
    $stand = funsite_get_standings($league_id, $season);

    if (is_wp_error($t1) || empty($t1['response'])) return [];
    if (is_wp_error($t2) || empty($t2['response'])) return [];

    $cat1 = funsite_get_team_category($team1_id, $stand);
    $cat2 = funsite_get_team_category($team2_id, $stand);

    $p1 = (int)($t1['response']['fixtures']['played']['total'] ?? 0);
    $p2 = (int)($t2['response']['fixtures']['played']['total'] ?? 0);
    $hp1 = (int)($t1['response']['fixtures']['played']['home'] ?? 0);
    $ap2 = (int)($t2['response']['fixtures']['played']['away'] ?? 0);
    $hw1 = (int)($t1['response']['fixtures']['wins']['home'] ?? 0);
    $hd1 = (int)($t1['response']['fixtures']['draws']['home'] ?? 0);
    $aw2 = (int)($t2['response']['fixtures']['wins']['away'] ?? 0);
    $ad2 = (int)($t2['response']['fixtures']['draws']['away'] ?? 0);

    $ppg1_home = $hp1 ? (($hw1*3 + $hd1)/$hp1) : 0;
    $ppg2_away = $ap2 ? (($aw2*3 + $ad2)/$ap2) : 0;
    if ($ppg1_home == 0 || $ppg2_away == 0) return [];

    $l5_1g = funsite_get_last5_goals($team1_id, $league_id, $season);
    $l5_2g = funsite_get_last5_goals($team2_id, $league_id, $season);
    $l5_1p = funsite_get_last5_points($team1_id, $league_id, $season)/5;
    $l5_2p = funsite_get_last5_points($team2_id, $league_id, $season)/5;

    $g1_season = $p1 ? ((int)($t1['response']['goals']['for']['total']['total'] ?? 0) / $p1) : 0;
    $g2_season = $p2 ? ((int)($t2['response']['goals']['for']['total']['total'] ?? 0) / $p2) : 0;
    $g1_home   = $hp1 ? ((int)($t1['response']['goals']['for']['total']['home'] ?? 0) / $hp1) : 0;
    $g2_away   = $ap2 ? ((int)($t2['response']['goals']['for']['total']['away'] ?? 0) / $ap2) : 0;

    $ga1_season= $p1 ? ((int)($t1['response']['goals']['against']['total']['total'] ?? 0) / $p1) : 0;
    $ga2_season= $p2 ? ((int)($t2['response']['goals']['against']['total']['total'] ?? 0) / $p2) : 0;
    $ga1_home  = $hp1 ? ((int)($t1['response']['goals']['against']['total']['home'] ?? 0) / $hp1) : 0;
    $ga2_away  = $ap2 ? ((int)($t2['response']['goals']['against']['total']['away'] ?? 0) / $ap2) : 0;

    $t1_total = ((($g1_season + $g1_home + ($l5_1g['goals_for']/5))/3)
              + (($ga2_season + $ga2_away + ($l5_2g['goals_against']/5))/3)) / 2;
    $t2_total = ((($g2_season + $g2_away + ($l5_2g['goals_for']/5))/3)
              + (($ga1_season + $ga1_home + ($l5_1g['goals_against']/5))/3)) / 2;

    $t1_adj = $t1_total * funsite_cat_multiplier($cat1, $cat2);
    $t2_adj = $t2_total * funsite_cat_multiplier($cat2, $cat1);

    // BTTS paliers
    $btts_eval = (function($t1, $t2, $l5_1g, $l5_2g, $t1_total, $t2_total){
      $paliers = [
        ['conf'=>91,'min_goals'=>1.55,'sc'=>[90,90,80],'conc'=>[65,65,60]],
        ['conf'=>90,'min_goals'=>1.50,'sc'=>[90,90,80],'conc'=>[65,65,60]],
        ['conf'=>89,'min_goals'=>1.45,'sc'=>[84,88,80],'conc'=>[65,65,60]],
        ['conf'=>88,'min_goals'=>1.40,'sc'=>[78,85,80],'conc'=>[65,65,60]],
        ['conf'=>87,'min_goals'=>1.30,'sc'=>[78,82,80],'conc'=>[63,63,60]],
        ['conf'=>86,'min_goals'=>1.20,'sc'=>[75,80,80],'conc'=>[60,60,60]],
      ];

      $hp1 = max(1,(int)($t1['response']['fixtures']['played']['home'] ?? 0));
      $ap2 = max(1,(int)($t2['response']['fixtures']['played']['away'] ?? 0));
      $p1t = max(1,(int)($t1['response']['fixtures']['played']['total'] ?? 0));
      $p2t = max(1,(int)($t2['response']['fixtures']['played']['total'] ?? 0));

      $failed_home = (int)($t1['response']['failed_to_score']['home'] ?? 0);
      $failed_away = (int)($t2['response']['failed_to_score']['away'] ?? 0);
      $clean_home  = (int)($t1['response']['clean_sheet']['home'] ?? 0);
      $clean_away  = (int)($t2['response']['clean_sheet']['away'] ?? 0);
      $clean_total_home = (int)($t1['response']['clean_sheet']['total'] ?? 0);
      $clean_total_away = (int)($t2['response']['clean_sheet']['total'] ?? 0);

      $pct_home_sc_saison = round((($p1t - $failed_home) / $p1t) * 100, 0);
      $pct_away_sc_saison = round((($p2t - $failed_away) / $p2t) * 100, 0);
      $pct_home_sc_dom    = round((($hp1 - $failed_home) / $hp1) * 100, 0);
      $pct_away_sc_ext    = round((($ap2 - $failed_away) / $ap2) * 100, 0);

      $pct_home_conc_saison = round((($p1t - $clean_total_home) / $p1t) * 100, 0);
      $pct_away_conc_saison = round((($p2t - $clean_total_away) / $p2t) * 100, 0);
      $pct_home_conc_dom    = round((($hp1 - $clean_home) / $hp1) * 100, 0);
      $pct_away_conc_ext    = round((($ap2 - $clean_away) / $ap2) * 100, 0);

      $cnt1 = max(1, count($l5_1g['response'] ?? []));
      $cnt2 = max(1, count($l5_2g['response'] ?? []));
      $pct_l5_home_sc   = round(($l5_1g['matches_with_goals']     / $cnt1) * 100, 0);
      $pct_l5_away_sc   = round(($l5_2g['matches_with_goals']     / $cnt2) * 100, 0);
      $pct_l5_home_conc = round(($l5_1g['matches_conceded_goals'] / $cnt1) * 100, 0);
      $pct_l5_away_conc = round(($l5_2g['matches_conceded_goals'] / $cnt2) * 100, 0);

      foreach ($paliers as $p) {
        $mg = $p['min_goals'];
        if ($t1_total >= $mg && $t2_total >= $mg) {
          $ok_sc =
            ($pct_home_sc_saison >= $p['sc'][0] && $pct_away_sc_saison >= $p['sc'][0]) &&
            ($pct_home_sc_dom    >= $p['sc'][1] && $pct_away_sc_ext    >= $p['sc'][1]) &&
            ($pct_l5_home_sc     >= $p['sc'][2] && $pct_l5_away_sc     >= $p['sc'][2]);

          $ok_conc =
            ($pct_home_conc_saison >= $p['conc'][0] && $pct_away_conc_saison >= $p['conc'][0]) &&
            ($pct_home_conc_dom    >= $p['conc'][1] && $pct_away_conc_ext    >= $p['conc'][1]) &&
            ($pct_l5_home_conc     >= $p['conc'][2] && $pct_l5_away_conc     >= $p['conc'][2]);

          if ($ok_sc && $ok_conc) return ['btts'=>'OUI','conf'=>$p['conf']];
        }
      }
      return ['btts'=>'NON','conf'=>70];
    })($t1,$t2,$l5_1g,$l5_2g,$t1_total,$t2_total);

    $btts = $btts_eval['btts'];
    $conf_btts = (int)$btts_eval['conf'];

    // Victoire
    $home_adv = ($ppg1_home + ($l5_1p ?? 0))/2;
    $away_dis = ($ppg2_away + ($l5_2p ?? 0))/2;
    $diff     = $home_adv - $away_dis;

    $victory  = '';
    if ($diff >= 1.5 && $t1_adj > $t2_adj) $victory = $team1_name;
    elseif ($diff <= -1.6 && $t2_adj > $t1_adj) $victory = $team2_name;

    $vic_conf = 0;
    if ($victory !== '') {
      $is_home_w = (strcasecmp($victory, $team1_name) === 0);
      $margin = funsite_margin_over_threshold($diff, $is_home_w);
      $cap = $is_home_w ? 1.40 : 1.50;
      $base = funsite_conf_from_margin($margin, $cap);
      if ($base !== 0) {
        $adj_edge = $is_home_w ? max(0.0, $t1_adj - $t2_adj) : max(0.0, $t2_adj - $t1_adj);
        $edge_bonus = ($adj_edge >= 0.4) ? 5 : (($adj_edge >= 0.25) ? 3 : (($adj_edge >= 0.1) ? 1 : 0));
        $c_self = (int)(($is_home_w ? $cat1 : $cat2) ?: 0);
        $c_opp  = (int)(($is_home_w ? $cat2 : $cat1) ?: 0);
        $cat_bonus = ($c_self && $c_opp && ($c_opp - $c_self) >= 2) ? 4 : (($c_opp - $c_self) === 1 ? 2 : 0);
        $venue_bonus = ($is_home_w && ($home_adv > $away_dis)) ? 2 : 0;
        $safety_malus = (abs($home_adv - $away_dis) < 0.10) ? 3 : 0;
        $vic_conf = max(60, min(95, $base + $edge_bonus + $cat_bonus + $venue_bonus - $safety_malus));
      }
    }

    return [
      'teams' => ['home'=>$team1_name,'away'=>$team2_name],
      'league_id' => $league_id,
      'pred' => [
        'diff' => $diff,
        'btts' => ['conseil'=>$btts,'confiance'=>$conf_btts],
        'victoire' => $victory,
        'victoire_conf' => (int)$vic_conf,
      ],
    ];
  }
}

/* ───────────────── Verdict (funsite_) ───────────────── */
if (!function_exists('funsite_eval_verdict')) {
  function funsite_eval_verdict(array $m, bool $has_btts, bool $has_victory, string $vict_name) : array {
    $stat = strtoupper($m['fixture']['status']['short'] ?? '');
    $is_final = in_array($stat, ['FT','AET','PEN','AWD','WO'], true);

    $gh = isset($m['goals']['home']) ? (int)$m['goals']['home'] : null;
    $ga = isset($m['goals']['away']) ? (int)$m['goals']['away'] : null;
    $score_txt = ($gh!==null && $ga!==null) ? ($gh.' - '.$ga) : '—';

    $live_statuses = ['1H','2H','HT','ET','LIVE','BT','INT','P'];
    $is_live = in_array($stat, $live_statuses, true);

    if (!$is_final || $gh===null || $ga===null) {

      if ($has_btts && !$has_victory && TP_FUNSITE_BTTTS_LIVE_VALIDATE && $is_live && $gh>0 && $ga>0) {
        return ['state'=>'won','score'=>$score_txt,'reason'=>'BTTS (live)'];
      }

      if ($has_victory && !$has_btts && TP_FUNSITE_VICTORY_LIVE_VALIDATE && $is_live) {
        $home = $m['teams']['home']['name'] ?? '';
        $away = $m['teams']['away']['name'] ?? '';
        $diff = $gh - $ga;
        $lead = null;
        if (strcasecmp($vict_name, $home) === 0) $lead = $diff;
        elseif (strcasecmp($vict_name, $away) === 0) $lead = -$diff;

        if ($lead !== null && $lead >= 2) return ['state'=>'won','score'=>$score_txt,'reason'=>'Victoire (live +2)'];
      }

      return ['state'=>'pending','score'=>$score_txt,'reason'=>'Match en cours/à venir'];
    }

    if (funsite_is_void_status($stat)) {
      return ['state'=>'void','score'=>$score_txt,'reason'=>'Annulé/Interrompu'];
    }

    $btts_ok = ($gh > 0 && $ga > 0);

    $home = $m['teams']['home']['name'] ?? '';
    $away = $m['teams']['away']['name'] ?? '';
    $homeW = (bool)($m['teams']['home']['winner'] ?? false);
    $awayW = (bool)($m['teams']['away']['winner'] ?? false);

    $vict_ok = true;
    if ($has_victory && $vict_name !== '') {
      if (strcasecmp($vict_name, $home) === 0)       $vict_ok = $homeW;
      elseif (strcasecmp($vict_name, $away) === 0)   $vict_ok = $awayW;
      else                                           $vict_ok = false;
    }

    $won = false;
    if ($has_btts && $has_victory)      $won = ($btts_ok && $vict_ok);
    elseif ($has_victory && !$has_btts) $won = $vict_ok;
    elseif ($has_btts && !$has_victory) $won = $btts_ok;

    return [
      'state'  => $won ? 'won' : 'lost',
      'score'  => $score_txt,
      'reason' => ($has_btts && $has_victory ? 'BTTS+Win' : ($has_victory ? 'Win' : 'BTTS')),
    ];
  }
}

/* ───────────────── Odds bundle (funsite_) + FALLBACK ───────────────── */
if (!function_exists('funsite_get_odds_bundle')) {
  function funsite_get_odds_bundle(int $fixture_id, string $home_name, string $away_name, int $bookmaker_id) {
    $bundle_key = "funsite_odds_bundle_FT_v3_{$fixture_id}_{$bookmaker_id}";
    if (($cached = get_transient($bundle_key)) !== false) return $cached;

    $raw_key = "funsite_odds_raw_{$fixture_id}_{$bookmaker_id}";
    $data = funsite_api_get('odds', ['fixture'=>$fixture_id,'bookmaker'=>$bookmaker_id], $raw_key, 10 * MINUTE_IN_SECONDS);
    if (is_wp_error($data)) return [];

    $is_half_time = function(string $s): bool {
      $s = strtolower($s);
      return (strpos($s,'1st half')!==false || strpos($s,'first half')!==false ||
              strpos($s,'2nd half')!==false || strpos($s,'second half')!==false ||
              strpos($s,'half time')!==false || strpos($s,'halftime')!==false ||
              preg_match('/\b(ht|1st|2nd)\b/i', $s) === 1 ||
              strpos($s,'period')!==false || strpos($s,'half')!==false);
    };
    $is_full_time_1x2 = function(string $name) use ($is_half_time): bool {
      $n = strtolower($name);
      if ($is_half_time($n)) return false;
      return (strpos($n,'match winner')!==false || strpos($n,'full time result')!==false || strpos($n,'1x2')!==false ||
              (strpos($n,'result')!==false && strpos($n,'both')===false && strpos($n,'btts')===false));
    };
    $is_full_time_btts = function(string $name) use ($is_half_time): bool {
      $n = strtolower($name);
      if ($is_half_time($n)) return false;
      return (strpos($n,'both teams')!==false || strpos($n,'btts')!==false);
    };
    $is_full_time_result_btts = function(string $name) use ($is_half_time, $is_full_time_1x2): bool {
      $n = strtolower($name);
      if ($is_half_time($n)) return false;
      return (( $is_full_time_1x2($n) || strpos($n,'winner')!==false || strpos($n,'result')!==false || strpos($n,'win')!==false )
              && (strpos($n,'both teams')!==false || strpos($n,'btts')!==false));
    };

    $norm = function($s) { return strtolower(trim(preg_replace('/\s+/', ' ', (string)$s))); };

    $out = ['btts_yes'=>null,'win_home'=>null,'win_away'=>null,'win_btts_home'=>null,'win_btts_away'=>null];

    // 1) bookmaker demandé
    foreach (($data['response'] ?? []) as $row) {
      foreach (($row['bookmakers'] ?? []) as $bm) {
        if ((int)$bm['id'] !== (int)$bookmaker_id) continue;

        foreach (($bm['bets'] ?? []) as $bet) {
          $bet_name = (string)($bet['name'] ?? '');

          if ($is_full_time_1x2($bet_name)) {
            foreach (($bet['values'] ?? []) as $v) {
              $val_n = $norm($v['value'] ?? ''); $odd = $v['odd'] ?? null;
              if ($odd === null) continue;

              if ($val_n==='home' || $val_n==='1' || strpos($val_n,'home team')!==false ||
                  ($home_name && stripos($v['value'],$home_name)!==false)) $out['win_home'] = $odd;

              if ($val_n==='away' || $val_n==='2' || strpos($val_n,'away team')!==false ||
                  ($away_name && stripos($v['value'],$away_name)!==false)) $out['win_away'] = $odd;
            }
          }

          if ($is_full_time_btts($bet_name)) {
            foreach (($bet['values'] ?? []) as $v) {
              if ($norm($v['value'] ?? '') === 'yes') $out['btts_yes'] = $v['odd'] ?? null;
            }
          }

          if ($is_full_time_result_btts($bet_name)) {
            foreach (($bet['values'] ?? []) as $v) {
              $val = $norm($v['value'] ?? ''); $odd = $v['odd'] ?? null;
              if ($odd === null) continue;
              if (strpos($val, 'yes') === false) continue;

              $is_home = (strpos($val,'home')!==false || preg_match('#(^|[ /])1($|[ /])#', $val) || ($home_name && stripos($val, $home_name) !== false));
              $is_away = (strpos($val,'away')!==false || preg_match('#(^|[ /])2($|[ /])#', $val) || ($away_name && stripos($val, $away_name) !== false));
              if ($is_home) $out['win_btts_home'] = $odd;
              elseif ($is_away) $out['win_btts_away'] = $odd;
            }
          }
        }
      }
    }

    // 2) FALLBACK : si rien trouvé, on tente tous les bookmakers présents
    $empty = ($out['btts_yes']===null && $out['win_home']===null && $out['win_away']===null && $out['win_btts_home']===null && $out['win_btts_away']===null);
    if ($empty) {
      foreach (($data['response'] ?? []) as $row) {
        foreach (($row['bookmakers'] ?? []) as $bm) {
          foreach (($bm['bets'] ?? []) as $bet) {
            $bet_name = (string)($bet['name'] ?? '');

            if ($is_full_time_1x2($bet_name)) {
              foreach (($bet['values'] ?? []) as $v) {
                $val_n = $norm($v['value'] ?? ''); $odd = $v['odd'] ?? null;
                if ($odd === null) continue;

                if ($out['win_home']===null && ($val_n==='home' || $val_n==='1' || ($home_name && stripos($v['value'],$home_name)!==false))) $out['win_home'] = $odd;
                if ($out['win_away']===null && ($val_n==='away' || $val_n==='2' || ($away_name && stripos($v['value'],$away_name)!==false))) $out['win_away'] = $odd;
              }
            }

            if ($out['btts_yes']===null && $is_full_time_btts($bet_name)) {
              foreach (($bet['values'] ?? []) as $v) {
                if ($norm($v['value'] ?? '') === 'yes') $out['btts_yes'] = $v['odd'] ?? null;
              }
            }

            if (($out['win_btts_home']===null || $out['win_btts_away']===null) && $is_full_time_result_btts($bet_name)) {
              foreach (($bet['values'] ?? []) as $v) {
                $val = $norm($v['value'] ?? ''); $odd = $v['odd'] ?? null;
                if ($odd === null) continue;
                if (strpos($val, 'yes') === false) continue;

                $is_home = (strpos($val,'home')!==false || preg_match('#(^|[ /])1($|[ /])#', $val) || ($home_name && stripos($val, $home_name) !== false));
                $is_away = (strpos($val,'away')!==false || preg_match('#(^|[ /])2($|[ /])#', $val) || ($away_name && stripos($val, $away_name) !== false));
                if ($is_home && $out['win_btts_home']===null) $out['win_btts_home'] = $odd;
                if ($is_away && $out['win_btts_away']===null) $out['win_btts_away'] = $odd;
              }
            }
          }
        }
      }
    }

    set_transient($bundle_key, $out, 10 * MINUTE_IN_SECONDS);
    return $out;
  }
}

/* ───────────────── CSS (funsite_) ───────────────── */
if (!function_exists('funsite_render_styles')) {
  function funsite_render_styles() { ?>
    <style>
      .funsite-wrap{max-width:1150px;margin:0 auto;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
      .funsite-grid{display:grid;grid-template-columns:1fr;gap:16px}
      @media(min-width:980px){.funsite-grid{grid-template-columns:1fr 1fr}}
      .funsite-card{background:linear-gradient(180deg,#0b1830,#071225);color:#fff;border:1px solid rgba(255,255,255,.08);
        padding:18px;border-radius:16px;box-shadow:0 12px 30px rgba(0,0,0,.25)}
      .funsite-row{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}
      .funsite-teams{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
      .funsite-logo{width:28px;height:28px;border-radius:6px;object-fit:contain;background:rgba(255,255,255,.06);padding:3px}
      .funsite-title{margin:0;font-size:15px;font-weight:900;letter-spacing:.2px}
      .funsite-meta{margin:6px 0 0;color:rgba(255,255,255,.72);font-size:12.5px}
      .funsite-tag{display:inline-flex;align-items:center;gap:6px;background:rgba(0,195,255,.12);border:1px solid rgba(0,195,255,.30);
        color:#bfefff;border-radius:999px;padding:6px 12px;font-size:12px;margin-top:10px;font-weight:800}
      .funsite-odd{margin:10px 0 0;font-size:14px}
      .funsite-odd strong{color:#e7f7ff}
      .funsite-badges{display:flex;gap:8px;flex-wrap:wrap}
      .funsite-badge{display:inline-flex;align-items:center;gap:8px;border-radius:999px;padding:7px 12px;font-size:12px;border:1px solid transparent;font-weight:900}
      .funsite-badge--won{background:rgba(22,163,74,.16);border-color:rgba(22,163,74,.45);color:#d8ffe6}
      .funsite-badge--lost{background:rgba(220,38,38,.16);border-color:rgba(220,38,38,.45);color:#ffe0e0}
      .funsite-badge--pend{background:rgba(59,130,246,.14);border-color:rgba(59,130,246,.35);color:#dbeafe}
      .funsite-badge--void{background:rgba(148,163,184,.16);border-color:rgba(148,163,184,.35);color:#f1f5f9}
      .funsite-block{margin-top:18px;border-radius:16px;padding:16px;border:1px solid rgba(255,255,255,.10);box-shadow:0 10px 24px rgba(0,0,0,.22)}
      .funsite-block--gold{background:linear-gradient(135deg,#ffd86b,#ffb703);color:#0a0a0a}
      .funsite-block--blue{background:linear-gradient(135deg,#0b2a55,#0a1b3a);color:#fff}
      .funsite-block h3{margin:0 0 8px;font-size:17px;font-weight:950}
      .funsite-block p{margin:0 0 10px;font-size:14px;opacity:.92}
      .funsite-block ul{margin:0;padding-left:18px}
      .funsite-block li{margin:6px 0;font-size:14px}
      .funsite-pill{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 12px;font-size:12px;border:1px solid transparent;font-weight:950}
      .funsite-pill--pos{background:rgba(22,163,74,.16);border-color:rgba(22,163,74,.45);color:#d8ffe6}
      .funsite-pill--neg{background:rgba(220,38,38,.16);border-color:rgba(220,38,38,.45);color:#ffe0e0}
      .funsite-pill--neu{background:rgba(59,130,246,.14);border-color:rgba(59,130,246,.35);color:#dbeafe}
      .funsite-table{width:100%;border-collapse:collapse;margin-top:8px;font-size:13.5px}
      .funsite-table th,.funsite-table td{padding:10px;border-bottom:1px solid rgba(255,255,255,.08);text-align:left;vertical-align:middle}
      .funsite-table th{color:#bfefff;font-weight:950}
      .funsite-admin{margin-top:0;background:transparent;border:0;border-radius:0;padding:0;color:#fff;box-shadow:none}
      .funsite-admin h3{margin:0 0 10px;font-size:16px;color:#ffd86b}
      .funsite-admin .funsite-controls{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
      .funsite-admin select,.funsite-admin input{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.14);color:#fff;
        border-radius:10px;padding:8px 10px;font-size:13px;outline:none}
      .funsite-btn{background:#00c3ff;color:#041019;border:0;border-radius:12px;padding:9px 12px;font-weight:950;cursor:pointer}
      .funsite-btn:hover{filter:brightness(1.05)}
      .funsite-save{white-space:nowrap}
      .funsite-note{margin-top:10px;color:rgba(255,255,255,.75);font-size:12.5px}
      .funsite-toast{margin-top:10px;padding:10px 12px;border-radius:12px;display:none}
      .funsite-toast.ok{display:block;background:rgba(22,163,74,.14);border:1px solid rgba(22,163,74,.35)}
      .funsite-toast.err{display:block;background:rgba(220,38,38,.14);border:1px solid rgba(220,38,38,.35)}
      .funsite-badge-inline{font-size:12px;opacity:.9}

      /* Accordéons (fermés par défaut) */
      .funsite-fold{margin-top:16px;border-radius:16px;overflow:hidden;border:1px solid rgba(255,255,255,.10);
        background:linear-gradient(180deg,#091428,#071225);box-shadow:0 12px 30px rgba(0,0,0,.25)}
      .funsite-fold > summary{cursor:pointer;list-style:none;padding:14px 16px;font-weight:950;color:#7de3ff}
      .funsite-fold > summary::-webkit-details-marker{display:none}
      .funsite-fold[open] > summary{border-bottom:1px solid rgba(255,255,255,.08)}
      .funsite-fold .funsite-fold-inner{padding:16px}

      /* FIX OVERRIDE THÈME (titres équipes en noir) */
      .funsite-card .funsite-title{color:#fff !important;}
      .funsite-card .funsite-meta{color:rgba(255,255,255,.72) !important;}
      .funsite-card .funsite-odd{color:#fff !important;}
      .funsite-card .funsite-tag{color:#bfefff !important;}
    </style>
  <?php }
}

/* ───────────────── Render one card (funsite_) ───────────────── */
if (!function_exists('funsite_display_card')) {
  function funsite_display_card(array $m, string $label, array $show_lines, array $verdict) {
    $utc = $m['fixture']['date'] ?? null;
    if (!$utc) return;

    $dt = new DateTime($utc, new DateTimeZone('UTC'));
    $dt->setTimezone(funsite_tz());
    $when = $dt->format('d/m/Y, H:i');

    $home = $m['teams']['home']['name'] ?? '';
    $away = $m['teams']['away']['name'] ?? '';
    $hlogo= $m['teams']['home']['logo'] ?? '';
    $alogo= $m['teams']['away']['logo'] ?? '';
    $league= $m['league']['name'] ?? '';
    $country=$m['league']['country'] ?? '';

    $cls = 'funsite-badge funsite-badge--pend';
    $icon = '⏳';
    if (($verdict['state'] ?? '') === 'won') { $cls='funsite-badge funsite-badge--won'; $icon='✅'; }
    elseif (($verdict['state'] ?? '') === 'lost') { $cls='funsite-badge funsite-badge--lost'; $icon='❌'; }
    elseif (($verdict['state'] ?? '') === 'void') { $cls='funsite-badge funsite-badge--void'; $icon='↩️'; }

    echo '<div class="funsite-card">';
      echo '<div class="funsite-row">';
        echo '<div class="funsite-teams">';
          if ($hlogo) echo '<img class="funsite-logo" src="'.esc_url($hlogo).'" alt="">';
          echo '<h4 class="funsite-title">'.esc_html($home).'</h4>';
          echo '<span style="opacity:.9;font-weight:950">–</span>';
          if ($alogo) echo '<img class="funsite-logo" src="'.esc_url($alogo).'" alt="">';
          echo '<h4 class="funsite-title">'.esc_html($away).'</h4>';
        echo '</div>';

        echo '<div class="funsite-badges">';
          echo '<span class="'.$cls.'"><strong>'.$icon.' Score</strong> : '.esc_html($verdict['score'] ?? '—').'</span>';
          if (!empty($verdict['reason']) && stripos((string)$verdict['reason'], 'live') !== false) {
            echo '<span class="funsite-badge funsite-badge--won"><strong>LIVE</strong> payé</span>';
          }
        echo '</div>';
      echo '</div>';

      echo '<div class="funsite-meta">📅 '.esc_html($when).'   •   🏆 '.esc_html($league).($country ? ' — '.esc_html($country) : '').'</div>';
      echo '<div class="funsite-tag">🎯 '.esc_html($label).'</div>';

      foreach ($show_lines as $line) {
        if (!$line) continue;
        [$caption, $odd] = $line;
        if ($odd === null || $odd === '') continue;
        $odd_fmt = number_format((float)$odd, 2, ',', ' ');
        echo '<div class="funsite-odd"><strong>'.$caption.'</strong> : '.esc_html($odd_fmt).'</div>';
      }
    echo '</div>';
  }
}

/* ───────────────── Pick evaluation with admin override (funsite_) ───────────────── */
if (!function_exists('funsite_pick_eval_state')) {
  function funsite_pick_eval_state(array $pick): array {
    $manual = strtolower(trim((string)($pick['manual_status'] ?? 'auto')));
    if ($manual && $manual !== 'auto') {
      if ($manual === 'win')  return ['state'=>'won','score'=>'—','reason'=>'Admin'];
      if ($manual === 'loss') return ['state'=>'lost','score'=>'—','reason'=>'Admin'];
      if ($manual === 'void') return ['state'=>'void','score'=>'—','reason'=>'Admin'];
      if ($manual === 'pending') return ['state'=>'pending','score'=>'—','reason'=>'Admin'];
    }

    $fid = (int)($pick['fixture_id'] ?? 0);
    if ($fid <= 0) return ['state'=>'pending','score'=>'—','reason'=>'ID manquant'];

    $m = funsite_get_fixture_by_id($fid);
    if (!is_array($m)) return ['state'=>'pending','score'=>'—','reason'=>'Fixture indisponible'];

    $flags = (array)($pick['flags'] ?? []);
    $has_btts = !empty($flags['has_btts']);
    $has_vic  = !empty($flags['has_victory']);
    $vict_name= (string)($flags['vict_name'] ?? '');

    return funsite_eval_verdict($m, (bool)$has_btts, (bool)$has_vic, $vict_name);
  }
}

/* ───────────────── Combo evaluation & ROI (funsite_) ─────────────────
   - Void sur une sélection : l’odd de cette sélection est neutralisée (=1),
     et le combiné peut quand même gagner.
   - Si tout est void => combiné void (remboursé)
   - Si au moins 1 pending => combiné pending
*/
if (!function_exists('funsite_combo_result_and_odd')) {
  function funsite_combo_result_and_odd(array $picks): array {
    $hasPending = false;
    $hasLoss = false;
    $anyNonVoid = false;
    $odd = 1.0;

    foreach ($picks as $p) {
      $state = (string)(funsite_pick_eval_state($p)['state'] ?? 'pending');
      if ($state === 'pending') { $hasPending = true; continue; }
      if ($state === 'lost') { $hasLoss = true; continue; }
      if ($state === 'void') { continue; }

      $anyNonVoid = true;
      $useOdd = 0.0;
      if (isset($p['manual_odd']) && $p['manual_odd'] !== '' && is_numeric($p['manual_odd'])) $useOdd = (float)$p['manual_odd'];
      else $useOdd = (float)($p['odd'] ?? 0);

      if ($useOdd > 1.01) $odd *= $useOdd;
    }

    if ($hasPending) return ['state'=>'pending','odd'=>1.0];
    if ($hasLoss)    return ['state'=>'lost','odd'=>max(1.0,$odd)];
    if (!$anyNonVoid) return ['state'=>'void','odd'=>1.0];
    return ['state'=>'won','odd'=>max(1.0,$odd)];
  }
}

if (!function_exists('funsite_calc_roi_period')) {
  function funsite_calc_roi_period(array $days, float $bankroll, float $stake_pct): array {
    $stake = round($bankroll * ($stake_pct/100), 2);

    $profit = 0.0;
    $stake_used = 0.0;
    $w=$l=$v=$p=0;

    foreach ($days as $day) {
      if (!is_array($day)) continue;

      // Top3 combiné (1 bet)
      if (!empty($day['top3']) && is_array($day['top3'])) {
        $r = funsite_combo_result_and_odd($day['top3']);
        if ($r['state']==='pending') $p++;
        elseif ($r['state']==='void') $v++;
        else {
          $stake_used += $stake;
          if ($r['state']==='won') { $w++; $profit += $stake * ((float)$r['odd'] - 1.0); }
          else { $l++; $profit -= $stake; }
        }
      }

      // Cote10 combiné (1 bet) — uniquement si validé >=10
      if (!empty($day['combo10']) && is_array($day['combo10'] ?? null) && !empty($day['combo_total']) && (float)$day['combo_total'] >= 10.0) {
        $r = funsite_combo_result_and_odd($day['combo10']);
        if ($r['state']==='pending') $p++;
        elseif ($r['state']==='void') $v++;
        else {
          $stake_used += $stake;
          if ($r['state']==='won') { $w++; $profit += $stake * ((float)$r['odd'] - 1.0); }
          else { $l++; $profit -= $stake; }
        }
      }
    }

    $roi = ($stake_used > 0) ? round(($profit / $stake_used) * 100, 2) : 0.0;

    return [
      'stake_unit'=>$stake,
      'stake_used'=>$stake_used,
      'profit'=>$profit,
      'roi'=>$roi,
      'w'=>$w,'l'=>$l,'v'=>$v,'p'=>$p
    ];
  }
}

/* ───────────────── Admin AJAX (funsite_) ───────────────── */
if (!function_exists('funsite_admin_ajax_save')) {
  function funsite_admin_ajax_save() {
    if (!current_user_can('manage_options')) wp_send_json_error(['msg'=>'forbidden'], 403);

    $nonce = (string)($_POST['nonce'] ?? '');
    if (!wp_verify_nonce($nonce, 'funsite_admin_nonce')) wp_send_json_error(['msg'=>'bad_nonce'], 403);

    $ymd = preg_replace('/[^0-9]/', '', (string)($_POST['ymd'] ?? ''));
    $block = (string)($_POST['block'] ?? 'top3'); // top3|combo10
    $fixture_id = (int)($_POST['fixture_id'] ?? 0);
    $type = (string)($_POST['type'] ?? '');
    $manual_status = strtolower(trim((string)($_POST['manual_status'] ?? 'auto')));
    $manual_odd_raw = (string)($_POST['manual_odd'] ?? '');

    if (!$ymd || !$fixture_id || !in_array($block, ['top3','combo10'], true)) {
      wp_send_json_error(['msg'=>'bad_params'], 400);
    }
    if (!in_array($manual_status, ['auto','win','loss','void','pending'], true)) $manual_status = 'auto';

    $manual_odd = '';
    if ($manual_odd_raw !== '' && is_numeric($manual_odd_raw)) {
      $manual_odd = (string)round((float)$manual_odd_raw, 3);
    }

    $day = funsite_day_load($ymd);
    if (!is_array($day)) wp_send_json_error(['msg'=>'day_not_found'], 404);

    $arr = $day[$block] ?? [];
    if (!is_array($arr)) $arr = [];

    $found = false;
    foreach ($arr as &$p) {
      if ((int)($p['fixture_id'] ?? 0) === $fixture_id && ((string)($p['type'] ?? '') === $type)) {
        $p['manual_status'] = $manual_status;
        $p['manual_odd'] = $manual_odd;
        $found = true;
        break;
      }
    }
    unset($p);

    if (!$found) wp_send_json_error(['msg'=>'pick_not_found'], 404);

    $day[$block] = $arr;

    // recalcul totals (visuel)
    $recalc_total = function(array $picks): float {
      $tot = 1.0;
      foreach ($picks as $p) {
        $useOdd = 0.0;
        if (isset($p['manual_odd']) && $p['manual_odd'] !== '' && is_numeric($p['manual_odd'])) $useOdd = (float)$p['manual_odd'];
        else $useOdd = (float)($p['odd'] ?? 0);
        if ($useOdd > 1.01) $tot *= $useOdd;
      }
      return $tot;
    };
    if ($block === 'top3') $day['top3_total'] = $recalc_total($day['top3'] ?? []);
    if ($block === 'combo10') $day['combo_total'] = $recalc_total($day['combo10'] ?? []);

    funsite_day_save($ymd, $day);

    wp_send_json_success(['msg'=>'saved','ymd'=>$ymd,'block'=>$block]);
  }
}
if (!function_exists('funsite_register_admin_ajax')) {
  function funsite_register_admin_ajax() {
    add_action('wp_ajax_funsite_save_pick', 'funsite_admin_ajax_save');
  }
  funsite_register_admin_ajax();
}

/* ───────────────── Main render (funsite_) ───────────────── */
if (!function_exists('funsite_render_analyzer')) {
  function funsite_render_analyzer($atts = []) {

    $atts = shortcode_atts([
      'bankroll'  => (string)TP_FUNSITE_DEFAULT_BANKROLL,
      'stake_pct' => (string)TP_FUNSITE_DEFAULT_STAKE_PCT,
      'bookmaker' => (string)TP_FUNSITE_DEFAULT_BOOKMAKER,
      'retention' => (string)TP_FUNSITE_DEFAULT_RETENTION,
      'league_ids'=> (string)TP_FUNSITE_LEAGUE_IDS,
      'season'    => (string)TP_FUNSITE_SEASON,
    ], $atts, 'tp_funsite_analyzer');

    $bankroll = max(1, (float)$atts['bankroll']);
    $stake_pct = max(0.1, (float)$atts['stake_pct']);
    $bookmaker = max(1, (int)$atts['bookmaker']);
    $retention = max(7, (int)$atts['retention']);
    $season = (string)$atts['season'];
    $league_ids = (string)$atts['league_ids'];

    funsite_cleanup_history($retention);

    $today = funsite_today_ymd();
    $stored = funsite_day_load($today);

    ob_start();
    echo '<div class="funsite-wrap">';
    funsite_render_styles();

    /* ───────── 1) CARTES FIGÉES DU JOUR (si existantes et >=3) ───────── */
    $cards = [];
    $frozen_cards = (is_array($stored) && !empty($stored['cards']) && is_array($stored['cards']) && count($stored['cards']) >= 3);

    if ($frozen_cards) {
      foreach ($stored['cards'] as $sc) {
        $fid = (int)($sc['fixture_id'] ?? 0);
        if (!$fid) continue;
        $m = funsite_get_fixture_by_id($fid);
        if (!is_array($m)) continue;

        $flags = (array)($sc['flags'] ?? []);
        $verdict = funsite_eval_verdict($m, !empty($flags['has_btts']), !empty($flags['has_victory']), (string)($flags['vict_name'] ?? ''));

        $cards[] = [
          'match'     => $m,
          'label'     => (string)($sc['label'] ?? ''),
          'show_lines'=> (array)($sc['show_lines'] ?? []),
          'verdict'   => $verdict,
          'conf'      => (int)($sc['conf'] ?? 0),
          'type'      => (string)($sc['type'] ?? 'S'),
          'flags'     => $flags,
          'picked_odd'=> (float)($sc['picked_odd'] ?? 0),
        ];
      }
    }

    /* ───────── 2) Sinon, on calcule (et on FREEZE dès qu’il y a ≥3 cartes) ───────── */
    if (!$frozen_cards) {
      $matches = funsite_get_matches_window($league_ids, $season);

      if (empty($matches)) {
        echo '<div class="funsite-fold"><summary>Analyseur Fun</summary><div class="funsite-fold-inner"><p>Aucun match trouvé.</p></div></div>';
        echo '</div>';
        return ob_get_clean();
      }

      foreach ($matches as $m) {
        $fid = (int)($m['fixture']['id'] ?? 0);
        if (!$fid) continue;

        $stat = funsite_status_short($m);
        $is_started = !in_array($stat, ['NS','TBD','PST','CANC'], true);

        // si match démarré : snapshot fixture si présent
        if ($is_started) {
          $snap = funsite_snap_load($fid);
          if (is_array($snap) && !empty($snap['label'])) {
            $flags = (array)($snap['flags'] ?? []);
            $verdict = funsite_eval_verdict($m, !empty($flags['has_btts']), !empty($flags['has_victory']), (string)($flags['vict_name'] ?? ''));
            $cards[] = [
              'match'=>$m,
              'label'=>(string)$snap['label'],
              'show_lines'=>(array)($snap['show_lines'] ?? []),
              'verdict'=>$verdict,
              'conf'=>(int)($snap['conf'] ?? 0),
              'type'=>(string)($snap['type'] ?? 'S'),
              'flags'=>$flags,
              'picked_odd'=>(float)($snap['picked_odd'] ?? 0),
            ];
            continue;
          }
        }

        // avant match : on calcule
        $data = funsite_build_stats_and_predictions($m, $season);
        if (empty($data)) continue;

        $btts_ok = (($data['pred']['btts']['conseil'] ?? 'NON') === 'OUI') && ((int)($data['pred']['btts']['confiance'] ?? 0) >= 85);

        $vict_name = trim((string)($data['pred']['victoire'] ?? ''));
        $diff_ok = (abs((float)($data['pred']['diff'] ?? 0)) >= 0.6);
        $vict_ok = ($vict_name !== '' && $diff_ok);

        if (!$btts_ok && !$vict_ok) continue;

        $home = (string)($m['teams']['home']['name'] ?? '');
        $away = (string)($m['teams']['away']['name'] ?? '');

        $vic_pct = (int)($data['pred']['victoire_conf'] ?? 0);
        $btts_pct = (int)($data['pred']['btts']['confiance'] ?? 0);

        $vic_txt  = $vict_name . ($vic_pct ? ' ('.$vic_pct.'%)' : '');
        $btts_txt = 'Les 2 équipes marquent ('.$btts_pct.'%)';
        $label = ($btts_ok && $vict_ok) ? ('Victoire : '.$vic_txt.' + '.$btts_txt) : ($vict_ok ? ('Victoire : '.$vic_txt) : $btts_txt);

        $odds = funsite_get_odds_bundle($fid, $home, $away, $bookmaker);
        $show_lines = [];
        $picked_odd = 0.0;

        if ($btts_ok && $vict_ok) {
          $is_home_w = (strcasecmp($vict_name, $home) === 0);
          $combined = $is_home_w ? ($odds['win_btts_home'] ?? null) : ($odds['win_btts_away'] ?? null);
          if ($combined) { $show_lines[]=['💰 Cote Victoire + BTTS', $combined]; $picked_odd=(float)$combined; }
          else {
            $win = $is_home_w ? ($odds['win_home'] ?? null) : ($odds['win_away'] ?? null);
            $btts = $odds['btts_yes'] ?? null;
            if ($win)  $show_lines[]=['💰 Cote Victoire', $win];
            if ($btts) $show_lines[]=['💰 Cote BTTS', $btts];
            if ($win && $btts) { $picked_odd=(float)$win*(float)$btts; $show_lines[]=['🧮 Cote combinée (V×BTTS)', $picked_odd]; }
            else $picked_odd=(float)($win ?? $btts ?? 0);
          }
        } elseif ($vict_ok) {
          $is_home_w = (strcasecmp($vict_name, $home) === 0);
          $win = $is_home_w ? ($odds['win_home'] ?? null) : ($odds['win_away'] ?? null);
          $show_lines[]=['💰 Cote Victoire', $win];
          $picked_odd = (float)($win ?? 0);
        } else {
          $btts = $odds['btts_yes'] ?? null;
          $show_lines[]=['💰 Cote BTTS', $btts];
          $picked_odd = (float)($btts ?? 0);
        }

        $flags = [
          'has_btts'    => $btts_ok,
          'has_victory' => $vict_ok,
          'vict_name'   => $vict_name,
        ];

        $type = ($btts_ok && $vict_ok) ? 'VB' : ($vict_ok ? 'V' : 'B');
        $conf = ($btts_ok && $vict_ok) ? max($vic_pct,$btts_pct) : ($vict_ok ? $vic_pct : $btts_pct);
        $verdict = funsite_eval_verdict($m, $btts_ok, $vict_ok, $vict_name);

        // snapshot fixture si match “started”
        if ($is_started && empty(funsite_snap_load($fid))) {
          funsite_snap_save($fid, [
            'label'=>$label,'show_lines'=>$show_lines,'flags'=>$flags,'conf'=>$conf,'type'=>$type,'picked_odd'=>$picked_odd,
          ]);
        }

        $cards[] = [
          'match'=>$m,'label'=>$label,'show_lines'=>$show_lines,'verdict'=>$verdict,
          'conf'=>$conf,'type'=>$type,'flags'=>$flags,'picked_odd'=>$picked_odd
        ];
      }

      if (count($cards) >= 3) {
        // FREEZE du jour : on stocke les cartes affichées (et elles ne bougeront plus)
        $cards_store = [];
        foreach ($cards as $c) {
          $fid = (int)($c['match']['fixture']['id'] ?? 0);
          if (!$fid) continue;
          $cards_store[] = [
            'fixture_id' => $fid,
            'label'      => (string)($c['label'] ?? ''),
            'show_lines' => (array)($c['show_lines'] ?? []),
            'flags'      => (array)($c['flags'] ?? []),
            'conf'       => (int)($c['conf'] ?? 0),
            'type'       => (string)($c['type'] ?? 'S'),
            'picked_odd' => (float)($c['picked_odd'] ?? 0),
          ];
        }

        $stored = is_array($stored) ? $stored : [];
        $stored['cards'] = $cards_store;
        $stored['cards_frozen'] = true;
        funsite_day_save($today, $stored);
      }
    }

    if (empty($cards)) {
      echo '<div class="funsite-fold"><summary>Analyseur Fun</summary><div class="funsite-fold-inner"><p>Aucun match ne correspond aux critères.</p></div></div>';
      echo '</div>';
      return ob_get_clean();
    }

    echo '<div class="funsite-grid">';
    foreach ($cards as $c) funsite_display_card($c['match'],$c['label'],$c['show_lines'],$c['verdict']);
    echo '</div>';

    /* ───────── 3) TOP3 + COTE10 : sur cartes FIGÉES (et on mémorise une seule fois) ───────── */
    $stored = funsite_day_load($today);
    if (!is_array($stored)) $stored = [];

    $best = [];
    foreach ($cards as $c) {
      $stat = strtoupper(trim((string)($c['match']['fixture']['status']['short'] ?? '')));
      if (funsite_is_void_status($stat)) continue; // on exclut juste annulé/abandonné

      $odd = (float)($c['picked_odd'] ?? 0);
      if ($odd >= 1.20) {
        $best[] = ['card'=>$c, 'conf'=>(int)$c['conf'], 'odd'=>$odd, 'type'=>$c['type']];
      }
    }

    usort($best, function($a,$b){
      if ($a['conf'] === $b['conf']) {
        $prio = ['VB'=>3,'V'=>2,'B'=>1];
        $ta = $prio[$a['type']] ?? 0;
        $tb = $prio[$b['type']] ?? 0;
        if ($ta === $tb) return $b['odd'] <=> $a['odd'];
        return $tb <=> $ta;
      }
      return $b['conf'] <=> $a['conf'];
    });

    $norm_pick = function(array $row){
      $m = (array)($row['card']['match'] ?? []);
      $fid = (int)($m['fixture']['id'] ?? 0);
      $home = (string)($m['teams']['home']['name'] ?? '');
      $away = (string)($m['teams']['away']['name'] ?? '');
      $label = (string)($row['card']['label'] ?? '');
      $flags = (array)($row['card']['flags'] ?? []);
      return [
        'fixture_id'=>$fid,'home'=>$home,'away'=>$away,'label'=>$label,
        'odd'=>(float)($row['odd'] ?? 0),'conf'=>(int)($row['conf'] ?? 0),'type'=>(string)($row['type'] ?? ''),
        'flags'=>$flags,
        'manual_status'=>'auto','manual_odd'=>''
      ];
    };

    // On ne (re)construit que si pas déjà stocké aujourd’hui
    if (empty($stored['top3']) && empty($stored['combo10'])) {
      $top3_raw = array_slice($best, 0, 3);
      $top3 = []; $top3_total = 1.0;
      foreach ($top3_raw as $r) {
        $p = $norm_pick($r);
        if ($p['fixture_id'] && $p['odd'] > 1.01) { $top3[] = $p; $top3_total *= $p['odd']; }
      }

      // Cote10 : on vise >= 10 sinon on stocke vide
      $combo_target = 10.0;
      $combo = []; $combo_total = 1.0;
      foreach ($best as $r) {
        if ($combo_total >= $combo_target) break;
        $p = $norm_pick($r);
        if ($p['fixture_id'] && $p['odd'] > 1.01) { $combo[] = $p; $combo_total *= $p['odd']; }
      }
      if ($combo_total < 10.0) { $combo = []; $combo_total = 1.0; }

      $stored['top3'] = $top3;
      $stored['top3_total'] = $top3_total;
      $stored['combo10'] = $combo;
      $stored['combo_total'] = $combo_total;

      funsite_day_save($today, $stored);
      $stored = funsite_day_load($today);
      if (!is_array($stored)) $stored = [];
    }

    $top3 = (array)($stored['top3'] ?? []);
    $top3_total = (float)($stored['top3_total'] ?? 1.0);

    $combo10 = (array)($stored['combo10'] ?? []);
    $combo_total = (float)($stored['combo_total'] ?? 1.0);

    $render_pick_line = function(array $p){
      $v = funsite_pick_eval_state($p);
      $st = (string)($v['state'] ?? 'pending');
      $score = (string)($v['score'] ?? '—');

      $ico = '⏳';
      if ($st==='won') $ico='✅';
      elseif ($st==='lost') $ico='❌';
      elseif ($st==='void') $ico='↩️';

      $odd = (isset($p['manual_odd']) && $p['manual_odd']!=='' && is_numeric($p['manual_odd'])) ? (float)$p['manual_odd'] : (float)($p['odd'] ?? 0);
      $odd_fmt = $odd ? number_format($odd,2,',',' ') : '—';

      echo '<li>'.$ico.' <strong>'.esc_html(($p['home'] ?? '').' – '.($p['away'] ?? '')).'</strong> → '.esc_html($p['label'] ?? '').' <span class="funsite-badge-inline">(cote '.$odd_fmt.')</span>';
      if ($score && $score !== '—') echo ' <span class="funsite-badge-inline">• Score '.$score.'</span>';
      echo '</li>';
    };

    if (!empty($top3)) {
      echo '<div class="funsite-block funsite-block--gold">';
      echo '<h3>💎 Sélection Top 3 Confiances</h3>';
      echo '<p>'.esc_html(wp_date('d/m')).' – cote totale ≈ <strong>'.esc_html(number_format($top3_total,2,',',' ')).'</strong></p>';
      echo '<ul>';
      foreach ($top3 as $p) $render_pick_line($p);
      echo '</ul>';
      echo '</div>';
    }

    if (!empty($combo10) && $combo_total >= 10.0) {
      echo '<div class="funsite-block funsite-block--blue">';
      echo '<h3>💥 Combiné Fun du Jour – Objectif Cote 10+</h3>';
      echo '<p>'.esc_html(wp_date('d/m')).' – cote totale <strong>'.esc_html(number_format($combo_total,2,',',' ')).'</strong></p>';
      echo '<ul>';
      foreach ($combo10 as $p) $render_pick_line($p);
      echo '</ul>';
      echo '</div>';
    }

    /* ───────── 4) ROI (accordéon fermé) ───────── */
    $load_days = function(int $days){
      $out = [];
      for ($i=1; $i<=$days; $i++) {
        $ymd = funsite_ymd_days_ago($i);
        $d = funsite_day_load($ymd);
        if (is_array($d)) $out[] = $d;
      }
      return $out;
    };

    $r1  = funsite_calc_roi_period($load_days(1),  $bankroll, $stake_pct);
    $r7  = funsite_calc_roi_period($load_days(7),  $bankroll, $stake_pct);
    $r32 = funsite_calc_roi_period($load_days(32), $bankroll, $stake_pct);

    $render_roi_row = function(string $label, array $r){
      $profit = (float)$r['profit'];
      $roi = (float)$r['roi'];
      $profit_fmt = number_format($profit,2,',',' ').' €';
      $roi_fmt = number_format($roi,2,',',' ').'%';
      $pcls = ($profit>0)?'funsite-pill funsite-pill--pos':(($profit<0)?'funsite-pill funsite-pill--neg':'funsite-pill funsite-pill--neu');
      $rcls = ($roi>0)?'funsite-pill funsite-pill--pos':(($roi<0)?'funsite-pill funsite-pill--neg':'funsite-pill funsite-pill--neu');

      echo '<tr>';
      echo '<td><strong>'.esc_html($label).'</strong></td>';
      echo '<td><span class="'.$pcls.'">'.esc_html($profit_fmt).'</span></td>';
      echo '<td><span class="'.$rcls.'">'.esc_html($roi_fmt).'</span></td>';
      echo '<td>'.esc_html((int)$r['w']).' / '.esc_html((int)$r['l']).'</td>';
      echo '<td>'.esc_html((int)$r['v']).'</td>';
      echo '<td>'.esc_html((int)$r['p']).'</td>';
      echo '</tr>';
    };

    echo '<details class="funsite-fold">';
    echo '<summary>📈 Bilans ROI (combinés) — J-1 / J-7 / J-32</summary>';
    echo '<div class="funsite-fold-inner">';
      echo '<p style="margin:0 0 10px;color:rgba(255,255,255,.72);font-size:12.5px">Mise fixe par combiné : <strong>'.esc_html(number_format((float)$r1['stake_unit'],2,',',' ')).'€</strong> ('.esc_html($stake_pct).'% de '.esc_html($bankroll).'€). Void/pending exclus du ROI.</p>';
      echo '<table class="funsite-table"><thead><tr><th>Période</th><th>Profit</th><th>ROI</th><th>W / L</th><th>Void</th><th>Pending</th></tr></thead><tbody>';
      $render_roi_row('J-1', $r1);
      $render_roi_row('J-7', $r7);
      $render_roi_row('J-32', $r32);
      echo '</tbody></table>';
    echo '</div>';
    echo '</details>';

    /* ───────── 5) ADMIN (accordéon fermé) ───────── */
    if (current_user_can('manage_options')) {
      $idx = get_option(funsite_day_index_key(), []);
      if (!is_array($idx)) $idx = [];
      $idx = array_reverse($idx);

      $sel = (string)($_GET['funsite_day'] ?? $today);
      $sel = preg_replace('/[^0-9]/', '', $sel);
      if (!$sel) $sel = $today;

      $day = funsite_day_load($sel);
      $nonce = wp_create_nonce('funsite_admin_nonce');

      echo '<details class="funsite-fold">';
      echo '<summary>🛠️ Mode ADMIN — Overrides (statut / cote)</summary>';
      echo '<div class="funsite-fold-inner">';
        echo '<div class="funsite-admin" id="funsite-admin">';

        echo '<div class="funsite-controls">';
        echo '<form method="get" style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">';
        foreach ($_GET as $k=>$v) {
          if ($k==='funsite_day') continue;
          echo '<input type="hidden" name="'.esc_attr($k).'" value="'.esc_attr($v).'">';
        }
        echo '<label style="font-size:13px;opacity:.9">Journée :</label>';
        echo '<select name="funsite_day">';
        echo '<option value="'.esc_attr($today).'" '.selected($sel,$today,false).'>Aujourd’hui ('.$today.')</option>';
        foreach ($idx as $ymd) {
          echo '<option value="'.esc_attr($ymd).'" '.selected($sel,(string)$ymd,false).'>'.$ymd.'</option>';
        }
        echo '</select>';
        echo '<button class="funsite-btn" type="submit">Charger</button>';
        echo '</form>';
        echo '</div>';

        if (!is_array($day)) {
          echo '<div class="funsite-note">Aucune mémoire trouvée pour cette journée.</div>';
        } else {
          $render_admin_table = function(string $block, string $title, array $picks) use ($sel, $nonce) {
            if (empty($picks)) return;

            echo '<div style="margin-top:14px">';
            echo '<h4 style="margin:0 0 8px;font-size:14px;color:#bfefff;font-weight:950">'.esc_html($title).'</h4>';
            echo '<table class="funsite-table">';
            echo '<thead><tr><th>Match</th><th>Pick</th><th>Cote</th><th>Statut</th><th>Action</th></tr></thead><tbody>';

            foreach ($picks as $p) {
              $fid = (int)($p['fixture_id'] ?? 0);
              $type = (string)($p['type'] ?? '');
              $match = (string)($p['home'] ?? '').' – '.(string)($p['away'] ?? '');
              $label = (string)($p['label'] ?? '');
              $odd = (isset($p['manual_odd']) && $p['manual_odd']!=='') ? $p['manual_odd'] : (string)($p['odd'] ?? '');
              $st = (string)($p['manual_status'] ?? 'auto');

              echo '<tr data-block="'.esc_attr($block).'" data-ymd="'.esc_attr($sel).'" data-fid="'.esc_attr($fid).'" data-type="'.esc_attr($type).'">';
              echo '<td><strong>'.esc_html($match).'</strong><div style="opacity:.75;font-size:12px">#'.$fid.' • '.$type.'</div></td>';
              echo '<td>'.esc_html($label).'</td>';
              echo '<td><input class="funsite-inp-odd" type="text" value="'.esc_attr($odd).'" style="width:90px"></td>';
              echo '<td>';
                echo '<select class="funsite-inp-status">';
                  $opts = ['auto'=>'Auto','win'=>'Gagné','loss'=>'Perdu','void'=>'Remboursé','pending'=>'En attente'];
                  foreach ($opts as $k=>$lab) echo '<option value="'.esc_attr($k).'" '.selected($st,$k,false).'>'.esc_html($lab).'</option>';
                echo '</select>';
              echo '</td>';
              echo '<td><button class="funsite-btn funsite-save" type="button">Enregistrer</button></td>';
              echo '</tr>';
            }

            echo '</tbody></table>';
            echo '</div>';

            echo '<div class="funsite-note">Astuce : mets "Auto" pour revenir au verdict API. "Remboursé" neutralise la sélection dans le combiné (odd = 1).</div>';
            echo '<div class="funsite-toast" id="funsite-toast"></div>';

            static $js_done = false;
            if (!$js_done) {
              $js_done = true;
              ?>
              <script>
                (function(){
                  const root = document.getElementById('funsite-admin');
                  if(!root) return;

                  const toast = document.getElementById('funsite-toast');
                  const showToast = (ok, msg) => {
                    if(!toast) return;
                    toast.className = 'funsite-toast ' + (ok ? 'ok' : 'err');
                    toast.textContent = msg;
                  };

                  root.addEventListener('click', async (e) => {
                    const btn = e.target.closest('.funsite-save');
                    if(!btn) return;

                    const tr = btn.closest('tr');
                    if(!tr) return;

                    btn.disabled = true;
                    btn.textContent = '...';

                    const block = tr.getAttribute('data-block');
                    const ymd = tr.getAttribute('data-ymd');
                    const fid = tr.getAttribute('data-fid');
                    const type = tr.getAttribute('data-type');
                    const odd = tr.querySelector('.funsite-inp-odd')?.value ?? '';
                    const st = tr.querySelector('.funsite-inp-status')?.value ?? 'auto';

                    const fd = new FormData();
                    fd.append('action','funsite_save_pick');
                    fd.append('nonce', <?php echo json_encode($nonce); ?>);
                    fd.append('block', block);
                    fd.append('ymd', ymd);
                    fd.append('fixture_id', fid);
                    fd.append('type', type);
                    fd.append('manual_odd', odd);
                    fd.append('manual_status', st);

                    try{
                      const res = await fetch('<?php echo esc_url(admin_url('admin-ajax.php')); ?>', { method:'POST', body: fd });
                      const json = await res.json();
                      if(json && json.success){
                        showToast(true, '✅ Sauvegardé. Recharge la page pour voir le ROI mis à jour.');
                      } else {
                        showToast(false, '❌ Erreur : ' + ((json && json.data && json.data.msg) ? json.data.msg : 'unknown'));
                      }
                    } catch(err){
                      showToast(false, '❌ Erreur réseau');
                    } finally {
                      btn.disabled = false;
                      btn.textContent = 'Enregistrer';
                    }
                  });
                })();
              </script>
              <?php
            }
          };

          $render_admin_table('top3', '💎 Top 3 (override)', (array)($day['top3'] ?? []));
          $render_admin_table('combo10', '💥 Cote 10+ (override)', (array)($day['combo10'] ?? []));
        }

        echo '</div>'; // admin
      echo '</div>'; // fold inner
      echo '</details>';
    }

    echo '</div>'; // wrap
    return ob_get_clean();
  }
}

/* ───────────────── Shortcode registration (funsite_) ───────────────── */
if (!function_exists('funsite_register_shortcode')) {
  function funsite_register_shortcode() {
    add_shortcode('tp_funsite_analyzer', 'funsite_render_analyzer');
  }
  add_action('init', 'funsite_register_shortcode');
}

Êtes-vous satisfait des analyses ?

Cliquez sur une étoile pour la noter !

Note Moyenne 0 / 5. Vote 0

Aucun vote pour l'instant ! Soyez le premier à noter cet article.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *