"""Código compartido: API SofaScore, helpers, vistas. Importado por app.py y pages/."""
import streamlit as st
import requests
from curl_cffi import requests as curl_requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from mplsoccer import Pitch
from datetime import datetime, date
import json
import os
import time



SOFASCORE_BASE = "https://www.sofascore.com/api/v1"

# ── Paleta corporativa DCL ─────────────────────────────────────────────────
DCL_PRIMARIO        = "#F39C12"  # Naranja-dorado del logo
DCL_PRIMARIO_OSCURO = "#D68910"
DCL_NEGRO           = "#1A1A1A"  # Texto / cabeceras
DCL_PLATA           = "#95A5A6"  # Acento secundario
DCL_GRIS_CLARO      = "#F5F5F5"
DCL_BORDE           = "#E5E5E5"


def _inyectar_css_global():
    """Inyecta CSS de marca compatible con modo día/noche.

    Solo definimos colores donde tenga sentido marca (dorado en cabeceras,
    bordes de acento). El resto se hereda de Streamlit y adapta automáticamente
    al tema seleccionado por el usuario.
    """
    st.markdown(
        f"""
        <style>
        /* ── Cabeceras de tablas HTML en DORADO (alto contraste en ambos modos) ── */
        thead tr {{
            background: {DCL_PRIMARIO} !important;
            color: {DCL_NEGRO} !important;
        }}
        thead tr th {{
            border-bottom: 3px solid {DCL_NEGRO} !important;
            font-weight: 700 !important;
            letter-spacing: 0.3px;
        }}

        /* ── Acentos dorados en cabeceras de Streamlit ── */
        h1 {{ border-bottom: 3px solid {DCL_PRIMARIO}; padding-bottom: 6px; }}
        h2, h3, h4 {{ border-left: 4px solid {DCL_PRIMARIO}; padding-left: 10px; }}

        /* ── Hover de botones en dorado oscuro ── */
        .stButton > button:hover {{
            border-color: {DCL_PRIMARIO_OSCURO} !important;
            color: {DCL_PRIMARIO_OSCURO} !important;
        }}
        .stButton > button:active,
        .stButton > button:focus:not(:active) {{
            background: {DCL_PRIMARIO} !important;
            color: {DCL_NEGRO} !important;
            border-color: {DCL_PRIMARIO_OSCURO} !important;
        }}

        /* ── Tabs y divider en dorado ── */
        [data-baseweb="tab-list"] [aria-selected="true"] {{
            color: {DCL_PRIMARIO} !important;
            border-bottom-color: {DCL_PRIMARIO} !important;
        }}

        /* ── Logo DCL en la cabecera del sidebar ── */
        .dcl-logo-top {{
            padding: 4px 8px 14px;
            text-align: center;
            border-bottom: 1px solid {DCL_BORDE};
            margin-bottom: 8px;
        }}
        .dcl-logo-top img {{
            max-width: 80px;
            opacity: 0.95;
            transition: opacity 0.2s;
        }}
        .dcl-logo-top img:hover {{ opacity: 1; }}

        /* ── Scrollbar con tono dorado ── */
        ::-webkit-scrollbar {{ width: 10px; height: 10px; }}
        ::-webkit-scrollbar-thumb {{
            background: {DCL_PLATA};
            border-radius: 4px;
        }}
        ::-webkit-scrollbar-thumb:hover {{ background: {DCL_PRIMARIO}; }}
        </style>
        """,
        unsafe_allow_html=True,
    )


def _cargar_placeholder_local():
    """Lee player.svg del proyecto y lo devuelve como data URL para usarlo en <img>."""
    import base64
    try:
        with open("player.svg", "rb") as f:
            b64 = base64.b64encode(f.read()).decode("ascii")
        return f"data:image/svg+xml;base64,{b64}"
    except Exception:
        # Fallback al SVG remoto por si el archivo local falla
        return "https://www.sofascore.com/static/images/placeholders/player.svg"


PHOTO_PLACEHOLDER = _cargar_placeholder_local()


def _cargar_logo_dcl():
    """Busca el logo DCL en la raíz del proyecto y lo devuelve como data URL.

    Acepta varios nombres y formatos por orden de preferencia.
    Devuelve None si no encuentra ninguno.
    """
    import base64
    candidatos = [
        ("dcl_logo.png", "image/png"),
        ("dcl_logo.svg", "image/svg+xml"),
        ("dcl_logo.jpg", "image/jpeg"),
        ("dcl_logo.jpeg", "image/jpeg"),
        ("dcl_logo.webp", "image/webp"),
        ("logo.png", "image/png"),  # fallback con nombre genérico
        ("logo.svg", "image/svg+xml"),
    ]
    for nombre, mime in candidatos:
        if os.path.exists(nombre):
            try:
                with open(nombre, "rb") as f:
                    b64 = base64.b64encode(f.read()).decode("ascii")
                return f"data:{mime};base64,{b64}"
            except Exception:
                continue
    return None


def _check_auth():
    """Bloquea el acceso a la app hasta que el usuario introduzca credenciales válidas.

    Las credenciales se leen de ``st.secrets["auth"]["users"]`` (tabla usuario→contraseña).
    Configurar en ``.streamlit/secrets.toml`` para local y en el dashboard de Streamlit
    Cloud para producción. Si no hay tabla auth.users, la app NO arranca (fail-secure).

    Una vez autenticado, ``st.session_state.authenticated`` queda en True y
    ``st.session_state.usuario`` guarda el nombre. Llama desde app.py al inicio.
    """
    # Logout vía query param (?logout=1) — disparado desde el botón en la cabecera
    try:
        if st.query_params.get("logout") == "1":
            for k in ("authenticated", "usuario"):
                st.session_state.pop(k, None)
            st.query_params.clear()
            st.rerun()
    except Exception:
        pass

    if st.session_state.get("authenticated"):
        return

    # Mientras no haya autenticación: ocultar sidebar y centrar el formulario
    st.markdown(
        """
        <style>
        section[data-testid="stSidebar"] { display: none !important; }
        [data-testid="stSidebarCollapseButton"] { display: none !important; }
        .block-container { max-width: 420px !important; padding-top: 4rem !important; }
        </style>
        """,
        unsafe_allow_html=True,
    )

    st.markdown(
        f'<h1 style="text-align:center;border-bottom:3px solid {DCL_PRIMARIO};'
        f'padding-bottom:8px">🔐 DCL Campograma</h1>',
        unsafe_allow_html=True,
    )
    st.caption("Acceso restringido. Introduce tus credenciales para continuar.")

    with st.form("login_form", clear_on_submit=False):
        usuario = st.text_input("Usuario", autocomplete="username")
        password = st.text_input("Contraseña", type="password", autocomplete="current-password")
        submit = st.form_submit_button("Entrar", use_container_width=True)

    if submit:
        try:
            users = dict(st.secrets["auth"]["users"])
        except Exception:
            users = {}

        if not users:
            st.error(
                "⚠️ No hay usuarios configurados. Pide al administrador que añada "
                "credenciales en `.streamlit/secrets.toml` o en el dashboard de Streamlit Cloud."
            )
        elif usuario in users and str(users[usuario]) == password:
            st.session_state.authenticated = True
            st.session_state.usuario = usuario
            st.rerun()
        else:
            st.error("Usuario o contraseña incorrectos.")

    st.stop()


def render_user_header():
    """Inserta usuario actual + botón "Cerrar sesión" pegado al menú Deploy de Streamlit.

    Se usa `position: fixed` y un `<a href="?logout=1">` para disparar el logout vía
    query param (manejado en `_check_auth`). Llamar después de `_check_auth()`.
    """
    if not st.session_state.get("authenticated"):
        return
    usuario = st.session_state.get("usuario", "—")
    st.markdown(
        f"""
        <style>
        .dcl-user-header {{
            position: fixed;
            top: 0.55rem;
            right: 4.5rem;
            z-index: 999991;
            display: flex;
            align-items: center;
            gap: 10px;
            height: 2rem;
            font-family: "Source Sans Pro", sans-serif;
        }}
        .dcl-user-name {{
            font-size: 13px;
            font-weight: 600;
            color: var(--text-color, {DCL_NEGRO});
            opacity: 0.85;
        }}
        .dcl-logout-btn {{
            background: transparent;
            color: var(--text-color, {DCL_NEGRO}) !important;
            border: 1px solid {DCL_BORDE};
            border-radius: 6px;
            padding: 4px 12px;
            font-size: 13px;
            font-weight: 500;
            text-decoration: none !important;
            transition: all 0.15s;
            line-height: 1.3;
        }}
        .dcl-logout-btn:hover {{
            background: {DCL_PRIMARIO};
            color: {DCL_NEGRO} !important;
            border-color: {DCL_PRIMARIO};
        }}
        /* En pantallas estrechas, ocultamos el nombre y dejamos solo el botón */
        @media (max-width: 600px) {{
            .dcl-user-name {{ display: none; }}
        }}
        </style>
        <div class="dcl-user-header">
            <span class="dcl-user-name">👤 {usuario}</span>
            <a href="?logout=1" class="dcl-logout-btn" target="_self">Cerrar sesión</a>
        </div>
        """,
        unsafe_allow_html=True,
    )


def render_logo_sidebar():
    """Pinta el logo DCL en la cabecera del sidebar.

    Si el archivo del logo no existe, no hace nada (silencioso).
    """
    logo = _cargar_logo_dcl()
    if not logo:
        return
    st.sidebar.markdown(
        f'<div class="dcl-logo-top">'
        f'<img src="{logo}" alt="DCL">'
        f'</div>',
        unsafe_allow_html=True,
    )


FOTOS_DIR = ".cache_fotos"
FOTOS_TTL_SEGUNDOS = 30 * 24 * 3600  # 30 días


def _path_foto(player_id, ext):
    return os.path.join(FOTOS_DIR, f"{player_id}{ext}")


def _foto_local_existente(player_id):
    """Si ya tenemos la foto descargada y no expiró, devuelve (path, ext). Si no, None."""
    if not os.path.isdir(FOTOS_DIR):
        return None
    for ext in (".png", ".jpg", ".jpeg", ".webp"):
        path = _path_foto(player_id, ext)
        if os.path.exists(path):
            edad = time.time() - os.path.getmtime(path)
            if edad < FOTOS_TTL_SEGUNDOS:
                return path, ext
    return None


def _bytes_a_data_url(contenido, ext):
    import base64
    mime = "image/jpeg" if ext in (".jpg", ".jpeg") else f"image/{ext[1:]}"
    b64 = base64.b64encode(contenido).decode("ascii")
    return f"data:{mime};base64,{b64}"


@st.cache_data(ttl=24 * 3600)
def _resolver_foto(player_id):
    """Devuelve la foto del jugador como data URL.
    1) Si está cacheada en disco → la lee
    2) Si no, la descarga vía _SESSION (curl_cffi con auth) y la guarda
    3) Si falla o no existe (404), devuelve el placeholder local
    """
    # 1) Cache en disco
    info = _foto_local_existente(player_id)
    if info is not None:
        path, ext = info
        try:
            with open(path, "rb") as f:
                return _bytes_a_data_url(f.read(), ext)
        except Exception:
            pass

    # 2) Descargar
    try:
        r = _SESSION.get(f"{SOFASCORE_BASE}/player/{player_id}/image", timeout=10)
        if r.status_code != 200 or len(r.content) < 500:
            return PHOTO_PLACEHOLDER

        ct = (r.headers.get("content-type") or "").lower()
        if "png" in ct:
            ext = ".png"
        elif "jpeg" in ct or "jpg" in ct:
            ext = ".jpg"
        elif "webp" in ct:
            ext = ".webp"
        else:
            ext = ".png"

        try:
            os.makedirs(FOTOS_DIR, exist_ok=True)
            with open(_path_foto(player_id, ext), "wb") as f:
                f.write(r.content)
        except Exception:
            pass  # si falla escribir, devolvemos data URL igualmente

        return _bytes_a_data_url(r.content, ext)
    except Exception:
        return PHOTO_PLACEHOLDER

def _cargar_cookie_sofascore():
    """Carga la cookie de SofaScore desde:
       1) st.secrets["sofascore"]["cookie"]  (uso local con .streamlit/secrets.toml o Streamlit Cloud)
       2) Variable de entorno SOFASCORE_COOKIE  (Docker, GitHub Actions, etc.)
       3) Cadena vacía  (la app puede arrancar pero las llamadas a la API darán 403).
    """
    try:
        return st.secrets["sofascore"]["cookie"]
    except Exception:
        pass
    return os.environ.get("SOFASCORE_COOKIE", "")


_COOKIE = _cargar_cookie_sofascore()

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
    "Accept": "*/*",
    "Accept-Language": "es-ES,es;q=0.9",
    "Referer": "https://www.sofascore.com/es-la/football/player/pablo-saenz/1175743",
    "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"',
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": '"Windows"',
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "same-origin",
    "Priority": "u=1, i",
    "Cache-Control": "max-age=0",
    "x-requested-with": "85520b",
    "Cookie": _COOKIE,
}

_SESSION = curl_requests.Session(impersonate="chrome")
_SESSION.headers.update(HEADERS)

CSV_PATH = "jugadores.csv"

# ── Stats de temporada ─────────────────────────────────────────────────────────
STATS_GRUPOS = {
    "General": [
        ("appearances",                  "Partidos",              "{}"),
        ("matchesStarted",               "Titular",               "{}"),
        ("minutesPlayed",                "Minutos",               "{}"),
        ("rating",                       "Valoración",            "{:.2f}"),
        ("yellowCards",                  "T. Amarillas",          "{}"),
        ("redCards",                     "T. Rojas",              "{}"),
    ],
    "Ataque": [
        ("goals",                        "⚽ Goles",                 "{}"),
        ("assists",                      "👟 Asistencias",           "{}"),
        ("goalsAssistsSum",              "G+A",                   "{}"),
        ("goalConversionPercentage",     "Conversión goles %",    "{:.1f}%"),
        ("totalShots",                   "Tiros totales",         "{}"),
        ("shotsOnTarget",                "Tiros a puerta",        "{}"),
        ("shotsFromInsideTheBox",        "Tiros interior",        "{}"),
        ("shotsFromOutsideTheBox",       "Tiros exterior",        "{}"),
        ("goalsFromInsideTheBox",        "⚽ Goles interior",        "{}"),
        ("headedGoals",                  "⚽ Goles cab.",            "{}"),
        ("leftFootGoals",                "Pie izq.",              "{}"),
        ("rightFootGoals",               "Pie der.",              "{}"),
        ("hitWoodwork",                  "Postes",                "{}"),
        ("bigChancesCreated",            "GCC (Grandes Chances Creadas)",  "{}"),
        ("bigChancesMissed",             "GCF (Grandes Chances Falladas)", "{}"),
        ("offsides",                     "Fueras de juego",       "{}"),
        ("wasFouled",                    "Faltas recibidas",      "{}"),
    ],
    "Pases": [
        ("accuratePasses",               "Pases precisos",        "{}"),
        ("totalPasses",                  "Pases totales",         "{}"),
        ("accuratePassesPercentage",     "% Pases",               "{:.1f}%"),
        ("keyPasses",                    "Pases clave",           "{}"),
        ("accurateOppositionHalfPasses", "Pases campo rival",     "{}"),
        ("accurateFinalThirdPasses",     "Pases último tercio",   "{}"),
        ("accurateLongBalls",            "Bal. largos prec.",     "{}"),
        ("accurateLongBallsPercentage",  "% Bal. largos",         "{:.1f}%"),
        ("accurateCrosses",              "Centros prec.",         "{}"),
        ("accurateCrossesPercentage",    "% Centros",             "{:.1f}%"),
        ("passToAssist",                 "Pases previos asist.",  "{}"),
    ],
    "Defensa": [
        ("tackles",                      "Entradas",              "{}"),
        ("tacklesWon",                   "Entradas ganadas",      "{}"),
        ("tacklesWonPercentage",         "% Entradas ganadas",    "{:.1f}%"),
        ("interceptions",                "Interceptaciones",      "{}"),
        ("clearances",                   "Despejes",              "{}"),
        ("blockedShots",                 "Tiros bloqueados",      "{}"),
        ("dribbledPast",                 "Regateado",             "{}"),
        ("fouls",                        "Faltas cometidas",      "{}"),
        ("errorLeadToGoal",              "Errores → gol",         "{}"),
    ],
    "Duelos": [
        ("totalDuelsWon",                "Duelos ganados",        "{}"),
        ("totalDuelsWonPercentage",      "% Duelos",              "{:.1f}%"),
        ("groundDuelsWon",               "Duelos suelo",          "{}"),
        ("groundDuelsWonPercentage",     "% Duelos suelo",        "{:.1f}%"),
        ("aerialDuelsWon",               "Duelos aéreos",         "{}"),
        ("aerialDuelsWonPercentage",     "% Duelos aéreos",       "{:.1f}%"),
    ],
    "Posesión": [
        ("successfulDribbles",           "Regates",               "{}"),
        ("successfulDribblesPercentage", "% Regates",             "{:.1f}%"),
        ("touches",                      "Toques",                "{}"),
        ("ballRecovery",                 "Recuperaciones",        "{}"),
        ("possessionLost",               "Pérdidas",              "{}"),
        ("possessionWonAttThird",        "Posesión rec. ataque",  "{}"),
        ("dispossessed",                 "Robado",                "{}"),
    ],
}

# ── Stats de partido individual ────────────────────────────────────────────────
MATCH_STATS_GRUPOS = {
    "General": [
        ("minutesPlayed",          "Minutos",           "{}"),
        ("rating",                 "Valoración",        "{:.1f}"),
        ("touches",                "Toques",            "{}"),
        ("possessionLostCtrl",     "Pérdidas",          "{}"),
    ],
    "Ataque": [
        ("goals",                  "⚽ Goles",             "{}"),
        ("goalAssist",             "👟 Asistencias",       "{}"),
        ("totalShots",             "Tiros",             "{}"),
        ("onTargetScoringAttempt", "A puerta",          "{}"),
        ("hitWoodwork",            "Poste/Larguero",    "{}"),
        ("ballCarriesCount",       "Conducciones",      "{}"),
    ],
    "Pases": [
        ("accuratePass",           "Pases precisos",    "{}"),
        ("totalPass",              "Pases totales",     "{}"),
        ("totalLongBalls",         "Balones largos",    "{}"),
        ("totalCross",             "Centros",           "{}"),
        ("goalAssist",             "👟 Asistencias",       "{}"),
    ],
    "Defensa": [
        ("totalTackle",            "Entradas",          "{}"),
        ("wonTackle",              "Entradas ganadas",  "{}"),
        ("duelWon",                "Duelos ganados",    "{}"),
        ("duelLost",               "Duelos perdidos",   "{}"),
        ("ballRecovery",           "Recuperaciones",    "{}"),
    ],
}

ALL_SEASON_CLAVES = [c for g in STATS_GRUPOS.values() for c, _, _ in g]

PITCH_BG = "#1a472a"
COLORES_TIRO = {
    "goal":  "#00e676",
    "miss":  "#ff1744",
    "save":  "#ffa726",
    "block": "#78909c",
}


# ── Carga de jugadores desde CSV ──────────────────────────────────────────────

def guardar_campos_jugador(player_id, cambios, path=CSV_PATH):
    """Actualiza filas del CSV para el jugador indicado.

    Args:
        player_id: int. El player_id que se debe modificar (debe existir en el CSV).
        cambios: dict {columna: nuevo_valor}. Las columnas deben ser editables
                 (notas, lesion, fecha_vuelta, posicion_campo, nombre_agencia).
        path: ruta del CSV.

    Returns True si se guardó. Tras guardar conviene invalidar la cache de
    `cargar_csv` con st.cache_data.clear() o relanzar la app.
    """
    columnas_editables = {"nombre_agencia", "notas", "posicion_campo", "lesion", "fecha_vuelta"}
    cambios = {k: v for k, v in cambios.items() if k in columnas_editables}
    if not cambios:
        return False

    try:
        df = pd.read_csv(path, dtype={"player_id": int})
        # Asegura que existen las columnas (algunas pueden no estar si el CSV es antiguo)
        for col in columnas_editables:
            if col not in df.columns:
                df[col] = ""
        df = df.fillna("")

        mask = df["player_id"] == int(player_id)
        if not mask.any():
            return False
        for col, val in cambios.items():
            df.loc[mask, col] = "" if val is None else str(val)

        df.to_csv(path, index=False)
        return True
    except Exception as e:
        st.error(f"No se pudo guardar el CSV: {e}")
        return False


def cargar_csv(path=CSV_PATH):
    """Lee el CSV y devuelve un dict {player_id: {nombre_agencia, notas, posicion_campo}}."""
    try:
        df = pd.read_csv(path, dtype={"player_id": int})
        df["nombre_agencia"] = df.get("nombre_agencia", pd.Series(dtype=str)).fillna("")
        df["notas"] = df.get("notas", pd.Series(dtype=str)).fillna("")
        df["posicion_campo"] = df.get("posicion_campo", pd.Series(dtype=str)).fillna("")
        df["lesion"] = df.get("lesion", pd.Series(dtype=str)).fillna("")
        df["fecha_vuelta"] = df.get("fecha_vuelta", pd.Series(dtype=str)).fillna("")
        return {
            int(row.player_id): {
                "nombre_agencia": row.nombre_agencia.strip(),
                "notas": row.notas.strip(),
                "posicion_campo": row.posicion_campo.strip(),
                "lesion": row.lesion.strip(),
                "fecha_vuelta": row.fecha_vuelta.strip(),
            }
            for row in df.itertuples()
        }
    except FileNotFoundError:
        return {}
    except Exception as e:
        st.error(f"Error leyendo {path}: {e}")
        return {}


# ── Helpers ────────────────────────────────────────────────────────────────────

def _ts_from_str(s):
    """Convierte 'YYYY-MM-DD' a timestamp Unix; devuelve None si falla."""
    try:
        from datetime import datetime as _dt
        return int(_dt.strptime(s, "%Y-%m-%d").timestamp())
    except Exception:
        return None


def calcular_edad(ts):
    if not ts:
        return None
    nac = datetime.fromtimestamp(ts).date()
    hoy = date.today()
    return hoy.year - nac.year - ((hoy.month, hoy.day) < (nac.month, nac.day))


def clasificar_edad(edad):
    if edad is None:
        return "Desconocida"
    if edad < 19:
        return "Sub-19 (Juvenil)"
    if edad < 23:
        return "Sub-23"
    return "23+"


def fmt_val(valor, fmt):
    try:
        return fmt.format(valor)
    except Exception:
        return str(valor)


def color_rating(rating):
    """Devuelve (color_fondo, color_texto) según el rating."""
    if rating is None:
        return "#cccccc", "#666666"
    if rating >= 8.0:
        return "#1a7a1a", "#ffffff"   # verde oscuro — excepcional
    if rating >= 7.5:
        return "#4caf50", "#ffffff"   # verde
    if rating >= 7.0:
        return "#8bc34a", "#ffffff"   # verde claro
    if rating >= 6.5:
        return "#ffc107", "#333333"   # amarillo
    if rating >= 6.0:
        return "#ff9800", "#ffffff"   # naranja
    return "#f44336", "#ffffff"       # rojo


def badge_rating(label, rating):
    if rating is None:
        return ""
    bg, fg = color_rating(rating)
    return (
        f'<span style="background:{bg};color:{fg};padding:2px 7px;border-radius:4px;'
        f'font-size:0.82em;font-weight:700;margin-right:4px">{label} {rating:.2f}</span>'
    )


def _rank_en_top(valor, valores_ordenados):
    """Devuelve (posicion, total) del jugador en el top liga (1 = mejor).
    En empates se devuelve la mejor posición posible (la más alta).
    Devuelve (None, total) si el valor está por debajo del top."""
    if not valores_ordenados or not isinstance(valor, (int, float)):
        return None, 0
    total = len(valores_ordenados)
    if valor < valores_ordenados[0]:
        return None, total  # fuera del top
    # Cuántos valores son estrictamente mayores → ese +1 es la mejor posición posible
    n_mayores = sum(1 for v in valores_ordenados if v > valor)
    return n_mayores + 1, total


def _color_rank(pos, total):
    """Color verde→rojo según la posición. Reusamos la paleta de color_rating."""
    if not pos or not total:
        return "#cccccc", "#666666"
    pct = (pos - 1) / max(total - 1, 1)  # 0.0 = mejor, 1.0 = peor del top
    if pct <= 0.20:
        return "#1a7a1a", "#ffffff"  # verde oscuro
    if pct <= 0.40:
        return "#4caf50", "#ffffff"  # verde
    if pct <= 0.60:
        return "#8bc34a", "#ffffff"  # verde claro
    if pct <= 0.80:
        return "#ffc107", "#333333"  # amarillo
    return "#ff9800", "#ffffff"      # naranja


def _badge_rank(pos, total):
    """Badge con la posición en el top liga (#X). Coloreado según rank."""
    if pos is None:
        return '<span style="color:#bbb;font-size:11px">fuera top</span>'
    bg, fg = _color_rank(pos, total)
    return (
        f'<span style="background:{bg};color:{fg};padding:2px 9px;border-radius:10px;'
        f'font-size:11px;font-weight:700">#{pos}</span>'
    )


def tabla_stats_html(campos, stats, liga_data=None):
    """Genera tabla de stats. Si se pasa liga_data ({key: {avg, values}}), añade
    columnas comparativas: media de liga (top jugadores) y percentil del jugador."""
    liga_data = liga_data or {}
    filas = ""
    idx = 0
    for clave, etiqueta, fmt in campos:
        if clave not in stats:
            continue
        bg = "#f8f9fa" if idx % 2 == 0 else "#ffffff"
        val = stats[clave]

        comp_html = ""
        info = liga_data.get(clave)
        if isinstance(info, dict) and isinstance(val, (int, float)):
            avg = info.get("avg")
            valores = info.get("values") or []
            pos, total = _rank_en_top(val, valores)
            comp_html = (
                f'<td style="padding:7px 14px;color:#888;text-align:right;font-size:12px">{fmt_val(avg, fmt)}</td>'
                f'<td style="padding:7px 10px;text-align:center;width:100px">{_badge_rank(pos, total)}</td>'
            )
        elif liga_data:
            comp_html = (
                '<td style="padding:7px 14px;color:#bbb;text-align:right;font-size:12px">—</td>'
                '<td style="padding:7px 10px;color:#bbb;text-align:center;font-size:12px;width:100px">—</td>'
            )

        # Si la stat es "rating", la pintamos como badge con la escala de color
        if clave == "rating" and isinstance(val, (int, float)):
            r_bg, r_fg = color_rating(val)
            valor_html = (
                f'<span style="background:{r_bg};color:{r_fg};padding:2px 8px;border-radius:4px;'
                f'font-weight:700;display:inline-block;min-width:36px;text-align:center">'
                f'{fmt_val(val, fmt)}</span>'
            )
        else:
            valor_html = fmt_val(val, fmt)

        filas += (
            f'<tr style="background:{bg}">'
            f'<td style="padding:7px 14px;color:#555">{etiqueta}</td>'
            f'<td style="padding:7px 14px;font-weight:700;text-align:right">{valor_html}</td>'
            f'{comp_html}'
            f"</tr>"
        )
        idx += 1
    if not filas:
        return ""

    cabecera = ""
    if liga_data:
        cabecera = (
            '<tr style="background:#1A1A1A;color:white;font-size:11px;text-transform:uppercase;letter-spacing:0.5px">'
            '<th style="padding:6px 14px;text-align:left">Estadística</th>'
            '<th style="padding:6px 14px;text-align:right">Jugador</th>'
            '<th style="padding:6px 14px;text-align:right">Media top liga</th>'
            '<th style="padding:6px 10px;text-align:center">Top 50</th>'
            '</tr>'
        )

    return (
        '<table style="width:100%;border-collapse:collapse;border:1px solid #e0e0e0;'
        'border-radius:8px;overflow:hidden;margin-bottom:10px">'
        + cabecera
        + filas
        + "</table>"
    )


# ── API fetch ──────────────────────────────────────────────────────────────────

def _get(url, _reintentos=3, _pausa=1.5):
    import time
    for intento in range(_reintentos):
        try:
            r = _SESSION.get(url, timeout=10)
            if r.status_code == 200:
                return r.json()
            if r.status_code == 403 and intento < _reintentos - 1:
                time.sleep(_pausa)
                continue
        except Exception:
            if intento < _reintentos - 1:
                time.sleep(_pausa)
                continue
        return {}
    return {}


def obtener_datos_jugador(player_id):
    import time
    url = f"{SOFASCORE_BASE}/player/{player_id}"
    r = None
    for intento in range(3):
        try:
            r = _SESSION.get(url, timeout=10)
            if r.status_code == 200:
                break
            if r.status_code == 403 and intento < 2:
                time.sleep(1.5)
        except Exception as e:
            if intento < 2:
                time.sleep(1.5)
            else:
                return {"id": player_id, "error": str(e)}
    if r is None or r.status_code != 200:
        return {"id": player_id, "error": f"HTTP {getattr(r, 'status_code', '?')}"}
    d = r.json().get("player", {})
    team = d.get("team", {})
    nac_ts = d.get("dateOfBirthTimestamp") or _ts_from_str(d.get("dateOfBirth", ""))
    return {
        "id": player_id,
        "nombre": d.get("name", "—"),
        "equipo": team.get("name", "—"),
        "equipo_id": team.get("id"),
        "posicion": d.get("position", "—"),
        "edad": calcular_edad(nac_ts),
        "fecha_nacimiento_ts": nac_ts,
        "nacionalidad": d.get("country", {}).get("name", "—"),
        "foto": _resolver_foto(player_id),
        "sofascore_url": f"https://www.sofascore.com/player/{player_id}",
    }


def extraer_stats_temporada(raw):
    seasons = raw.get("seasons", [])
    if not seasons:
        return {}, "—", "—", None, None
    ligas = [s for s in seasons if s.get("uniqueTournament", {}).get("competitionType") == "domestic-league"]
    candidatos = sorted(ligas or seasons, key=lambda s: s.get("startYear", 0), reverse=True)
    entrada = candidatos[0]
    data = entrada.get("statistics", {})
    stats = {c: data[c] for c in ALL_SEASON_CLAVES if c in data}
    tournament_id = entrada.get("uniqueTournament", {}).get("id")
    season_id = entrada.get("season", {}).get("id")
    return stats, entrada.get("uniqueTournament", {}).get("name", "—"), entrada.get("year", "—"), tournament_id, season_id


def _ultimo_rating_desde_season(ratings):
    """Extrae el último rating de la lista seasonRatings (ya ordenada más reciente primero).
    Devuelve (rating, fecha_str, rival, event_id)."""
    if not ratings:
        return None, None, None, None
    r = ratings[0]
    ts = r.get("startTimestamp", 0)
    fecha = datetime.fromtimestamp(ts).strftime("%d/%m/%Y") if ts else ""
    rival = r.get("opponent", {}).get("name", "") if isinstance(r.get("opponent"), dict) else ""
    event_id = r.get("eventId")
    return r.get("rating"), fecha, rival, event_id


@st.cache_data(ttl=3600)
def cargar_jugadores(ids_y_meta):
    """ids_y_meta: tuple de (player_id, nombre_agencia, notas)"""
    jugadores, errores = [], []
    for pid, nombre_agencia, notas in ids_y_meta:
        datos = obtener_datos_jugador(pid)
        if datos and "error" not in datos:
            raw_stats = _get(f"{SOFASCORE_BASE}/player/{pid}/statistics")
            _, comp, temp, tid, sid = extraer_stats_temporada(raw_stats)
            stats = cargar_stats_temporada(pid, tid, sid) if tid and sid else {}
            ratings = cargar_ratings_temporada(pid, tid, sid) if tid and sid else []
            ultimo_rating, ultimo_fecha, ultimo_rival, ultimo_event_id = _ultimo_rating_desde_season(ratings)

            # Goles y asistencias del último partido (1 llamada extra, cacheada)
            ultimo_goles = 0
            ultimo_asist = 0
            if ultimo_event_id:
                match_stats = cargar_stats_evento(ultimo_event_id, pid)
                if isinstance(match_stats, dict):
                    ultimo_goles = match_stats.get("goals") or 0
                    ultimo_asist = match_stats.get("goalAssist") or 0

            datos.update({
                "stats": stats,
                "competicion": comp,
                "temporada": temp,
                "tournament_id": tid,
                "season_id": sid,
                "categoria": clasificar_edad(datos["edad"]),
                "ultimo_rating": ultimo_rating,
                "ultimo_fecha": ultimo_fecha,
                "ultimo_rival": ultimo_rival,
                "ultimo_event_id": ultimo_event_id,
                "ultimo_goles": int(ultimo_goles),
                "ultimo_asistencias": int(ultimo_asist),
                "nombre_agencia": nombre_agencia,
                "notas": notas,
            })
            jugadores.append(datos)
        elif datos:
            errores.append((pid, datos["error"]))
    return jugadores, errores


@st.cache_data(ttl=3600)
def cargar_eventos(player_id, max_paginas=20):
    """Pagina de 0 en adelante (más antiguo → más reciente) hasta agotar páginas."""
    eventos, stats_map, bench_map = [], {}, {}
    for pag in range(max_paginas):
        data = _get(f"{SOFASCORE_BASE}/player/{player_id}/events/last/{pag}")
        if not data:
            break
        eventos.extend(data.get("events", []))
        stats_map.update(data.get("statisticsMap", {}))
        bench_map.update(data.get("onBenchMap", {}))
        if not data.get("hasNextPage", False):
            break
    # Ordenar de más reciente a más antiguo para que toda la app sea consistente
    eventos.sort(key=lambda e: e.get("startTimestamp", 0), reverse=True)
    return eventos, stats_map, bench_map


@st.cache_data(ttl=3600)
def cargar_stats_temporada(player_id, tournament_id, season_id):
    """Stats completas del jugador para la competición y temporada indicadas."""
    data = _get(
        f"{SOFASCORE_BASE}/player/{player_id}"
        f"/unique-tournament/{tournament_id}/season/{season_id}/statistics/overall"
    )
    return data.get("statistics", {})


@st.cache_data(ttl=3600)
def cargar_heatmap_temporada(player_id, tournament_id, season_id):
    """Heatmap agregado de toda la temporada con conteo de frecuencia por posición."""
    data = _get(
        f"{SOFASCORE_BASE}/player/{player_id}"
        f"/unique-tournament/{tournament_id}/season/{season_id}/heatmap/overall"
    )
    return data.get("points", []), data.get("matches", 0)


CACHE_DIR = ".cache_liga"
CACHE_TTL_SEGUNDOS = 6 * 3600  # 6 horas en disco


def _cache_disco_path(tournament_id, season_id):
    return os.path.join(CACHE_DIR, f"liga_{tournament_id}_{season_id}.json")


def _leer_cache_disco(tournament_id, season_id):
    """Lee la cache de disco si existe y no ha expirado. Devuelve None si no es válida."""
    path = _cache_disco_path(tournament_id, season_id)
    if not os.path.exists(path):
        return None
    try:
        edad = time.time() - os.path.getmtime(path)
        if edad > CACHE_TTL_SEGUNDOS:
            return None
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return None


def _escribir_cache_disco(tournament_id, season_id, datos):
    try:
        os.makedirs(CACHE_DIR, exist_ok=True)
        with open(_cache_disco_path(tournament_id, season_id), "w", encoding="utf-8") as f:
            json.dump(datos, f)
    except Exception:
        pass


def cargar_stats_liga(tournament_id, season_id):
    """Devuelve {stat_key: {"avg": float, "values": [v1, v2, ...]}} con los top jugadores
    de la liga por stat. Cacheado en disco (6h).

    No usamos @st.cache_data en memoria a propósito: si una llamada anterior devolvió {} por
    un fallo temporal, quedaba atascado durante 1h. La lectura de disco es <1ms.

    Combina /top-players/overall (totales) y /top-players-per-game (per-game) como fallback
    para stats que no aparecen en overall.
    """
    if not tournament_id or not season_id:
        return {}

    # Normalizar a int — pandas convierte ints a float cuando hay NaN en la columna,
    # y los IDs llegarían como 54.0 / 77558.0, rompiendo URLs y nombres de cache
    try:
        tournament_id = int(tournament_id)
        season_id = int(season_id)
    except (TypeError, ValueError):
        return {}

    # 1) Intenta cache de disco primero
    desde_disco = _leer_cache_disco(tournament_id, season_id)
    if desde_disco is not None:
        return desde_disco

    # 2) Fetch real
    resultado = {}

    def _procesar(data, sobreescribir=False):
        if not isinstance(data, dict):
            return
        bloque = data.get("topPlayers") or {}
        for stat_key, lista in bloque.items():
            if not sobreescribir and stat_key in resultado:
                continue
            if not isinstance(lista, list) or not lista:
                continue
            valores = []
            for entrada in lista:
                stats = entrada.get("statistics") if isinstance(entrada, dict) else None
                if not isinstance(stats, dict):
                    continue
                v = stats.get(stat_key)
                if isinstance(v, (int, float)):
                    valores.append(v)
            if valores:
                resultado[stat_key] = {
                    "avg": sum(valores) / len(valores),
                    "values": sorted(valores),
                }

    _procesar(_get(f"{SOFASCORE_BASE}/unique-tournament/{tournament_id}/season/{season_id}/top-players/overall"), sobreescribir=True)
    _procesar(_get(f"{SOFASCORE_BASE}/unique-tournament/{tournament_id}/season/{season_id}/top-players-per-game/all/overall"), sobreescribir=False)

    # 3) Persistir en disco para futuros arranques
    if resultado:
        _escribir_cache_disco(tournament_id, season_id, resultado)

    return resultado


@st.cache_data(ttl=3600)
def cargar_ratings_temporada(player_id, tournament_id, season_id):
    """Rating del jugador en cada partido de la temporada (más reciente primero)."""
    data = _get(
        f"{SOFASCORE_BASE}/player/{player_id}"
        f"/unique-tournament/{tournament_id}/season/{season_id}/ratings/overall"
    )
    return data.get("seasonRatings", [])


@st.cache_data(ttl=3600)
def cargar_stats_evento(event_id, player_id):
    data = _get(f"{SOFASCORE_BASE}/event/{event_id}/player/{player_id}/statistics")
    return data.get("statistics", {})


@st.cache_data(ttl=3600)
def cargar_heatmap(event_id, player_id):
    data = _get(f"{SOFASCORE_BASE}/event/{event_id}/player/{player_id}/heatmap")
    return data.get("heatmap", [])


@st.cache_data(ttl=3600)
def cargar_shotmap(event_id, player_id):
    data = _get(f"{SOFASCORE_BASE}/event/{event_id}/shotmap/player/{player_id}")
    return data.get("shotmap", [])


@st.cache_data(ttl=3600)
def cargar_transferencias(player_id):
    """Devuelve la lista de transferencias del jugador, ordenada de más antigua a más reciente.
    Cada elemento es (timestamp, from_team_id, to_team_id)."""
    data = _get(f"{SOFASCORE_BASE}/player/{player_id}/transfer-history")
    transfers = data.get("transferHistory", []) or []
    out = []
    for t in transfers:
        ts = t.get("transferDateTimestamp")
        if ts is None:
            continue
        from_id = (t.get("transferFrom") or {}).get("id")
        to_id = (t.get("transferTo") or {}).get("id")
        out.append((ts, from_id, to_id))
    out.sort(key=lambda x: x[0])
    return out


def equipo_jugador_en(timestamp, transferencias, equipo_actual_id):
    """Devuelve el id del equipo en el que estaba el jugador en `timestamp`.
    Si no hay datos suficientes, cae al equipo actual."""
    if not transferencias:
        return equipo_actual_id
    # Si el timestamp es anterior a la primera transferencia, estaba en transferFrom de la primera
    if timestamp < transferencias[0][0]:
        return transferencias[0][1] or equipo_actual_id
    # Buscar la última transferencia con fecha <= timestamp; estaba en su transferTo
    equipo = equipo_actual_id
    for ts, from_id, to_id in transferencias:
        if ts <= timestamp:
            equipo = to_id or equipo
        else:
            break
    return equipo


# ── Parseo de eventos ──────────────────────────────────────────────────────────

def parsear_evento(ev, equipo_id, stats_map, bench_map, transferencias=None):
    home = ev.get("homeTeam", {})
    away = ev.get("awayTeam", {})
    hs = ev.get("homeScore", {}).get("current")
    as_ = ev.get("awayScore", {}).get("current")

    # Determinar el equipo en el que estaba el jugador en el momento del partido
    ts_evento = ev.get("startTimestamp", 0)
    equipo_en_partido = equipo_jugador_en(ts_evento, transferencias or [], equipo_id)

    home_id = home.get("id")
    away_id = away.get("id")
    if home_id == equipo_en_partido:
        es_local = True
    elif away_id == equipo_en_partido:
        es_local = False
    else:
        # Fallback: si el ID histórico no coincide con ninguno (datos incompletos), comparar por equipo actual
        es_local = home_id == equipo_id

    rival = away.get("name", "—") if es_local else home.get("name", "—")

    if hs is not None and as_ is not None:
        g_favor = hs if es_local else as_
        g_contra = as_ if es_local else hs
        resultado = f"{g_favor}–{g_contra}"
        tipo = "V" if g_favor > g_contra else ("D" if g_favor < g_contra else "E")
    else:
        resultado, tipo = "—", "—"

    ts = ev.get("startTimestamp", 0)
    fecha = datetime.fromtimestamp(ts).strftime("%d/%m/%Y") if ts else "—"
    eid = str(ev.get("id"))

    # Inline stats ya incluidos en la respuesta del endpoint
    inline = stats_map.get(eid)          # dict con rating/minutesPlayed, o {} si estuvo en banca
    en_banca = bench_map.get(eid, False)

    if en_banca:
        participacion = "🪑 Banca"
    elif inline is not None and inline:
        participacion = "✅ Jugó"
    else:
        participacion = "—"

    tiene_heatmap = ev.get("hasEventPlayerHeatMap", False)

    equipo_local = home.get("name", "—")
    equipo_visitante = away.get("name", "—")
    equipo_jugador = equipo_local if es_local else equipo_visitante

    return {
        "id": ev.get("id"),
        "ts": ts,
        "fecha": fecha,
        "rival": rival,
        "equipo_local": equipo_local,
        "equipo_visitante": equipo_visitante,
        "equipo_jugador": equipo_jugador,
        "es_local": es_local,
        "competicion": ev.get("tournament", {}).get("name", "—"),
        "jornada": ev.get("roundInfo", {}).get("round", ""),
        "resultado": resultado,
        "tipo": tipo,
        "participacion": participacion,
        "jugó": bool(inline and not en_banca),
        "rating": inline.get("rating") if inline else None,
        "minutos": inline.get("minutesPlayed") if inline else None,
        "tiene_heatmap": tiene_heatmap,
    }


# ── Visualizaciones ────────────────────────────────────────────────────────────

def render_heatmap(puntos, titulo="Mapa de calor", figsize=(7, 5)):
    if not puntos or len(puntos) < 3:
        return None
    # SofaScore heatmap/overall: x=0→100 defensa→ataque, y=0→100 lateral
    # mplsoccer opta mismo sistema → mapeo directo
    px = np.array([p["x"] for p in puntos], dtype=float)
    py = np.array([p["y"] for p in puntos], dtype=float)
    # 'count' está presente en heatmaps de temporada; en los de partido cada punto vale 1
    pesos = np.array([p.get("count", 1) for p in puntos], dtype=float)

    bw = 0.5 if len(puntos) > 200 else 0.7

    pitch = Pitch(pitch_color=PITCH_BG, line_color="white", pitch_type="opta",
                  line_zorder=2)
    fig, ax = pitch.draw(figsize=figsize)
    pitch.kdeplot(px, py, ax=ax, fill=True, levels=100,
                  cmap="hot", alpha=0.65, bw_adjust=bw, weights=pesos)
    ax.set_title(titulo, color="white", fontsize=13, pad=8)
    fig.patch.set_facecolor(PITCH_BG)
    return fig


def render_grafico_ratings(ratings):
    """Gráfico de barras horizontales con el rating por partido de la temporada."""
    if not ratings:
        return None

    # Ordenar de más antiguo a más reciente para el gráfico (abajo=antiguo, arriba=reciente)
    datos = sorted(ratings, key=lambda r: r.get("startTimestamp", 0))

    etiquetas = []
    for r in datos:
        rival = r.get("opponent", {}).get("name", "—") if isinstance(r.get("opponent"), dict) else "—"
        loc = "(L)" if r.get("isHome") else "(V)"
        etiquetas.append(f"{loc} {rival}")

    valores = [r.get("rating", 0) for r in datos]
    media = sum(valores) / len(valores) if valores else 0

    def bar_color(v):
        if v >= 8.0: return "#1a7a1a"
        if v >= 7.5: return "#4caf50"
        if v >= 7.0: return "#8bc34a"
        if v >= 6.5: return "#ffc107"
        if v >= 6.0: return "#ff9800"
        return "#f44336"

    colores = [bar_color(v) for v in valores]

    fig, ax = plt.subplots(figsize=(10, max(5, len(datos) * 0.42)))
    fig.patch.set_facecolor("#0e1117")
    ax.set_facecolor("#0e1117")

    bars = ax.barh(etiquetas, valores, color=colores, height=0.65, zorder=2)

    # Valor al final de cada barra
    for bar, val in zip(bars, valores):
        ax.text(val + 0.04, bar.get_y() + bar.get_height() / 2,
                f"{val:.1f}", va="center", ha="left",
                color="white", fontsize=9, fontweight="bold")

    # Línea de media
    ax.axvline(media, color="white", linestyle="--", linewidth=1.2, alpha=0.6, zorder=3)
    ax.text(media + 0.05, -0.8, f"Media {media:.2f}",
            color="white", fontsize=8, alpha=0.8)

    ax.set_xlim(4.5, 11)
    ax.set_xlabel("Rating", color="white", fontsize=10)
    ax.tick_params(colors="white", labelsize=9)
    for spine in ax.spines.values():
        spine.set_edgecolor("#333")
    ax.grid(axis="x", color="#333", linestyle="--", alpha=0.4, zorder=1)

    plt.tight_layout()
    return fig


def render_shotmap(tiros):
    if not tiros:
        return None

    pitch = Pitch(pitch_color=PITCH_BG, line_color="white",
                  pitch_type="opta", half=True, line_zorder=2)
    fig, ax = pitch.draw(figsize=(7, 5))

    for t in tiros:
        # SofaScore: playerCoordinates.x = distancia a portería (0 = línea gol),
        # playerCoordinates.y = lateral (0-100). Convertimos al sistema opta horizontal
        # con ataque hacia la derecha (portería en x=100).
        pc = t.get("playerCoordinates") or {}
        sofa_x = pc.get("x")
        sofa_y = pc.get("y")
        if sofa_x is None or sofa_y is None:
            continue
        opta_x = 100 - float(sofa_x)
        opta_y = float(sofa_y)

        xg = t.get("xG") or 0
        tipo = t.get("shotType", "miss")
        if tipo not in COLORES_TIRO:
            tipo = "miss"
        color = COLORES_TIRO[tipo]
        size = max(80, xg * 1200)
        pitch.scatter(opta_x, opta_y, ax=ax, s=size, color=color,
                      edgecolors="white", linewidths=1.5, alpha=0.9, zorder=3)

    leyenda = [mpatches.Patch(facecolor=c, edgecolor="white", label=l)
               for l, c in [("Gol", COLORES_TIRO["goal"]),
                             ("Parado", COLORES_TIRO["save"]),
                             ("Fuera", COLORES_TIRO["miss"]),
                             ("Bloqueado", COLORES_TIRO["block"])]]
    ax.legend(handles=leyenda, loc="lower left",
              facecolor=PITCH_BG, labelcolor="white", fontsize=9, framealpha=0.7)
    ax.set_title("Mapa de tiros", color="white", fontsize=12, pad=8)
    fig.patch.set_facecolor(PITCH_BG)
    return fig


# ── Vistas ─────────────────────────────────────────────────────────────────────

# ── Campograma de campo ────────────────────────────────────────────────────────

ETIQUETAS_POSICION = {
    "GK":  "Portero",
    "LB":  "Lat. Izq", "CB1": "Central", "CB2": "Central", "RB": "Lat. Der",
    "CDM": "Pivote",
    "LM":  "Med. Izq", "CM1": "Med. Centro", "CM2": "Med. Centro", "RM": "Med. Der",
    "CAM": "M. Punta",
    "LW":  "Ext. Izq", "ST": "Delantero", "RW": "Ext. Der",
}

# Posiciones en el pitch: (left%, top%)  — top=0% es zona de ataque, top=100% es portería propia
POSICIONES_XY = {
    "GK":  (50, 88),
    "LB":  (15, 72), "CB1": (35, 72), "CB2": (65, 72), "RB":  (85, 72),
    "CDM": (50, 57),
    "LM":  (15, 40), "CM1": (35, 40), "CM2": (65, 40), "RM":  (85, 40),
    "CAM": (50, 25),
    "LW":  (15, 10), "ST":  (50, 10), "RW":  (85, 10),
}


LINEAS_FORMACION = [
    ["LW", "ST", "RW"],
    ["CAM"],
    ["LM", "CM1", "CM2", "RM"],
    ["CDM"],
    ["LB", "CB1", "CB2", "RB"],
    ["GK"],
]


def _slot_campo(pos, jugadores):
    etiqueta = ETIQUETAS_POSICION.get(pos, pos)
    if not jugadores:
        st.markdown(
            f'<div style="text-align:center;border:1px dashed rgba(255,255,255,0.2);'
            f'border-radius:10px;padding:8px 4px;min-height:105px;display:flex;'
            f'flex-direction:column;align-items:center;justify-content:center;">'
            f'<span style="color:rgba(255,255,255,0.25);font-size:10px">{etiqueta}</span>'
            f'</div>',
            unsafe_allow_html=True,
        )
        return

    def _b(v, pre=""):
        if v is None: return ""
        bg, fg = color_rating(v)
        return (f'<span style="background:{bg};color:{fg};border-radius:3px;'
                f'padding:1px 5px;font-size:10px;font-weight:700;margin:1px">{pre}{v:.1f}</span>')

    for idx, j in enumerate(jugadores):
        nombre = j.get("nombre_agencia") or j.get("nombre", "—")
        foto = j.get("foto", "")
        stats = j.get("stats") or {}
        rating_s = stats.get("rating") if isinstance(stats, dict) else None
        rating_u = j.get("ultimo_rating")
        ultimo_fecha = str(j.get("ultimo_fecha") or "").strip()
        ultimo_goles = int(j.get("ultimo_goles") or 0)
        ultimo_asist = int(j.get("ultimo_asistencias") or 0)
        lesion = str(j.get("lesion") or "").strip()

        lesion_html = (
            f'<div style="background:#c0392b;color:white;border-radius:4px;'
            f'font-size:9px;font-weight:700;padding:1px 4px;margin-bottom:3px">'
            f'🤕 {lesion}</div>'
        ) if lesion else ""

        # Badges de gol/asistencia del último partido (solo si > 0)
        destacados_html = ""
        if ultimo_goles or ultimo_asist:
            partes = []
            if ultimo_goles:
                partes.append(
                    f'<span style="background:#27ae60;color:white;border-radius:8px;'
                    f'padding:1px 5px;font-size:9px;font-weight:700;margin:0 1px">'
                    f'⚽ {ultimo_goles}</span>'
                )
            if ultimo_asist:
                partes.append(
                    f'<span style="background:#2980b9;color:white;border-radius:8px;'
                    f'padding:1px 5px;font-size:9px;font-weight:700;margin:0 1px">'
                    f'👟 {ultimo_asist}</span>'
                )
            destacados_html = f'<div style="margin-top:3px">{"".join(partes)}</div>'

        separador = "margin-top:6px;" if idx > 0 else ""

        fecha_html = f'<div style="color:rgba(255,255,255,0.5);font-size:8px;margin-top:3px">📅 {ultimo_fecha}</div>' if ultimo_fecha else ""
        st.markdown(
            f'<div style="text-align:center;background:rgba(0,0,0,0.38);{separador}'
            f'border:1px solid rgba(255,255,255,0.22);border-radius:10px;padding:7px 4px 5px;">'
            f'{lesion_html}'
            f'<img src="{foto}" style="width:50px;height:50px;border-radius:50%;'
            f'border:2px solid white;object-fit:cover;display:block;margin:0 auto">'
            f'<div style="color:white;font-size:10px;font-weight:600;margin:4px 0 1px;'
            f'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 2px">{nombre}</div>'
            f'<div style="color:rgba(255,255,255,0.42);font-size:9px;margin-bottom:3px">{etiqueta}</div>'
            f'<div>{_b(rating_s)}{_b(rating_u, "↻")}</div>'
            f'{destacados_html}'
            f'{fecha_html}'
            f'</div>',
            unsafe_allow_html=True,
        )
        if st.button("Ficha →", key=f"campo_{pos}_{j['id']}"):
            st.session_state.jugador_sel = j["id"]
            st.rerun()


def vista_campograma_campo(df):
    pos_map = {}
    for _, row in df.iterrows():
        pos = str(row.get("posicion_campo", "") or "").strip()
        if pos:
            pos_map.setdefault(pos, []).append(row.to_dict())

    # Fondo de campo para toda la sección
    st.markdown("""
    <style>
    section[data-testid="stMain"] > div {
        background: linear-gradient(180deg,rgba(0,0,0,0) 0,rgba(0,0,0,0) 210px,#1a5220 210px,#1e6b27 44%,#236e23 57%,#1e6b27 73%,#1a5220 100%) !important;
    }
    </style>
    <div style="text-align:center;color:rgba(255,255,255,0.45);font-size:11px;
                letter-spacing:3px;padding:6px 0 10px">▲ ATAQUE</div>
    <div style="text-align:center;margin-bottom:10px">
      <div style="display:inline-block;width:50%;border-top:2px solid rgba(255,255,255,0.3)"></div>
    </div>
    """, unsafe_allow_html=True)

    SEP_MEDIO = """
    <div style="position:relative;margin:8px 0 14px">
      <div style="border-top:2px solid rgba(255,255,255,0.4)"></div>
      <div style="position:absolute;left:50%;top:-40px;transform:translateX(-50%);
                  width:80px;height:80px;border-radius:50%;
                  border:2px solid rgba(255,255,255,0.4)"></div>
    </div>"""

    SEP_AREA = """<div style="text-align:center;margin:8px 0">
      <div style="display:inline-block;width:50%;border-top:2px solid rgba(255,255,255,0.25)"></div>
    </div>"""

    for idx, linea in enumerate(LINEAS_FORMACION):
        n = len(linea)
        if n == 1:
            c1, c2, c3 = st.columns([2, 3, 2])
            slot_cols = [(linea[0], c2)]
        elif n == 3:
            cols = st.columns([1, 3, 1, 3, 1, 3, 1])
            slot_cols = [(linea[0], cols[1]), (linea[1], cols[3]), (linea[2], cols[5])]
        else:
            cols = st.columns(4)
            slot_cols = [(linea[i], cols[i]) for i in range(4)]

        for pos, col in slot_cols:
            with col:
                _slot_campo(pos, pos_map.get(pos, []))

        # Decoraciones de líneas entre filas
        if idx == 2:   # tras mediocampo → línea de medio campo + círculo
            st.markdown(SEP_MEDIO, unsafe_allow_html=True)
        elif idx == 4:  # tras defensa → área pequeña
            st.markdown(SEP_AREA, unsafe_allow_html=True)
        else:
            st.markdown("<div style='height:10px'></div>", unsafe_allow_html=True)

    st.markdown("""
    <div style="text-align:center;margin-top:8px">
      <div style="display:inline-block;width:50%;border-top:2px solid rgba(255,255,255,0.25)"></div>
    </div>
    <div style="text-align:center;color:rgba(255,255,255,0.45);font-size:11px;
                letter-spacing:3px;padding:10px 0 4px">DEFENSA ▼</div>
    """, unsafe_allow_html=True)


def vista_campograma(df_filtrado, df_total):
    st.sidebar.markdown("---")
    st.sidebar.metric("Total jugadores", len(df_total))
    st.sidebar.metric("Mostrando", len(df_filtrado))

    for categoria in ["Sub-19 (Juvenil)", "Sub-23", "23+"]:
        grupo = df_filtrado[df_filtrado["categoria"] == categoria]
        if grupo.empty:
            continue
        st.subheader(f"📋 {categoria} ({len(grupo)} jugadores)")
        cols = st.columns(4)
        for i, (_, j) in enumerate(grupo.iterrows()):
            with cols[i % 4]:
                with st.container(border=True):
                    c1, c2 = st.columns([1, 2])
                    with c1:
                        st.markdown(
                            f'<img src="{j["foto"]}" '
                            f'style="width:60px;height:60px;object-fit:cover;border-radius:6px">',
                            unsafe_allow_html=True,
                        )
                    with c2:
                        nombre_display = j.get("nombre_agencia") or j["nombre"]
                        st.markdown(f"**{nombre_display}**")
                        if j.get("nombre_agencia"):
                            st.caption(j["nombre"])
                        st.caption(f"🛡️ {j['equipo']}")
                        st.caption(f"📍 {j['posicion']} · {j['edad']} años")

                    # Badges de rating (media temporada + último partido)
                    media = j["stats"].get("rating")
                    ultimo = j.get("ultimo_rating")
                    ultimo_rival = j.get("ultimo_rival", "")
                    badges = badge_rating("Media", media) + badge_rating("Último", ultimo)
                    if badges:
                        tooltip = f"Último partido vs {ultimo_rival}" if ultimo_rival else ""
                        st.markdown(
                            f'<div title="{tooltip}" style="margin:4px 0 6px 0">{badges}</div>',
                            unsafe_allow_html=True,
                        )

                    # Indicadores de gol/asistencia del último partido
                    ult_goles = int(j.get("ultimo_goles") or 0)
                    ult_asist = int(j.get("ultimo_asistencias") or 0)
                    if ult_goles or ult_asist:
                        destacados = ""
                        if ult_goles:
                            destacados += (
                                f'<span style="background:#27ae60;color:white;border-radius:10px;'
                                f'padding:2px 8px;font-size:11px;font-weight:700;margin-right:4px">'
                                f'⚽ {ult_goles}</span>'
                            )
                        if ult_asist:
                            destacados += (
                                f'<span style="background:#2980b9;color:white;border-radius:10px;'
                                f'padding:2px 8px;font-size:11px;font-weight:700">'
                                f'👟 {ult_asist}</span>'
                            )
                        tooltip = f"Último partido vs {ultimo_rival}" if ultimo_rival else ""
                        st.markdown(
                            f'<div title="Acciones destacadas — {tooltip}" '
                            f'style="margin:2px 0 6px 0">{destacados}</div>',
                            unsafe_allow_html=True,
                        )

                    if st.button("Ver ficha", key=f"btn_{j['id']}"):
                        st.session_state.jugador_sel = j["id"]
                        st.rerun()


def vista_partidos(jugador):
    player_id = jugador["id"]
    equipo_id = jugador.get("equipo_id")

    with st.spinner("Cargando partidos..."):
        eventos_raw, stats_map, bench_map = cargar_eventos(player_id)
        transferencias = cargar_transferencias(player_id)

    if not eventos_raw:
        st.info("No se pudieron obtener los partidos. Comprueba la conexión.")
        return

    partidos = [parsear_evento(e, equipo_id, stats_map, bench_map, transferencias) for e in eventos_raw if e.get("id")]

    # ── Tabla de partidos ──────────────────────────────────────────────────────
    # Color del resultado (V/E/D) y color del rating (escala SofaScore) por separado
    COLORES_TIPO = {"V": "#d4edda", "E": "#fff3cd", "D": "#f8d7da", "—": "#f8f9fa"}
    filas_html = ""
    for idx, p in enumerate(partidos):
        bg_fila = "#ffffff" if idx % 2 == 0 else "#f8f9fa"
        bg_resultado = COLORES_TIPO.get(p["tipo"], "#f8f9fa")
        min_str = str(p["minutos"]) if p["minutos"] else "—"
        jornada_str = f"J{p['jornada']}" if p["jornada"] else ""

        # Celda de partido: ambos equipos, el del jugador en negrita
        if p["es_local"]:
            partido_cell = (
                f'<strong style="color:#222">{p["equipo_local"]}</strong>'
                f'<span style="color:#888"> vs {p["equipo_visitante"]}</span>'
            )
        else:
            partido_cell = (
                f'<span style="color:#888">{p["equipo_local"]} vs </span>'
                f'<strong style="color:#222">{p["equipo_visitante"]}</strong>'
            )

        # Celda de rating con el color de su escala
        if p["rating"]:
            r_bg, r_fg = color_rating(p["rating"])
            rating_cell = (
                f'<td style="padding:6px 10px;text-align:center">'
                f'<span style="background:{r_bg};color:{r_fg};padding:2px 8px;border-radius:4px;'
                f'font-weight:700;display:inline-block;min-width:36px">{p["rating"]:.1f}</span></td>'
            )
        else:
            rating_cell = '<td style="padding:6px 10px;text-align:center;color:#999">—</td>'

        filas_html += (
            f'<tr style="background:{bg_fila}">'
            f'<td style="padding:6px 10px;color:#888;font-size:0.85em">{jornada_str}</td>'
            f'<td style="padding:6px 10px">{p["fecha"]}</td>'
            f'<td style="padding:6px 10px">{partido_cell}</td>'
            f'<td style="padding:6px 10px;font-weight:700;text-align:center;background:{bg_resultado}">{p["resultado"]}</td>'
            f'<td style="padding:6px 10px;text-align:center">{p["participacion"]}</td>'
            f'<td style="padding:6px 10px;text-align:center">{min_str}\'</td>'
            f'{rating_cell}'
            f'<td style="padding:6px 10px;color:#666;font-size:0.85em">{p["competicion"]}</td>'
            f"</tr>"
        )

    tabla_html = (
        '<table style="width:100%;border-collapse:collapse;border:1px solid #ddd;'
        'border-radius:8px;overflow:hidden;margin-bottom:18px;font-size:0.88em">'
        '<thead><tr style="background:#1A1A1A;color:white;font-size:0.95em">'
        "<th style='padding:8px 8px'>Jor.</th>"
        "<th style='padding:8px 8px;text-align:left'>Fecha</th>"
        "<th style='padding:8px 8px;text-align:left'>Partido</th>"
        "<th style='padding:8px 8px'>Result.</th>"
        "<th style='padding:8px 8px'>Estado</th>"
        "<th style='padding:8px 8px'>Min.</th>"
        "<th style='padding:8px 8px'>Rating</th>"
        "<th style='padding:8px 8px;text-align:left'>Competición</th>"
        "</tr></thead>"
        f"<tbody>{filas_html}</tbody></table>"
    )

    # Layout: izquierda tabla, derecha selector + detalle del partido
    col_tabla, col_detalle = st.columns([5, 4], gap="large")

    with col_tabla:
        st.markdown("#### Historial de partidos")
        st.markdown(tabla_html, unsafe_allow_html=True)

    with col_detalle:
        st.markdown("#### Detalle de partido")

        # Solo se pueden analizar partidos en los que jugó
        jugados = [p for p in partidos if p["jugó"]]
        if not jugados:
            st.info("No hay partidos con estadísticas disponibles.")
            return

        opciones = {
            f"{p['fecha']} · {p['rival']} ({p['resultado']})": p
            for p in jugados
        }
        seleccion = st.selectbox(
            "Selecciona un partido:",
            ["— Elige un partido —"] + list(opciones.keys()),
            key=f"partido_sel_{player_id}",
        )

        if seleccion == "— Elige un partido —":
            st.caption("Elige un partido del desplegable para ver sus estadísticas, mapa de calor y tiros.")
            return

        partido_sel = opciones[seleccion]
        event_id = partido_sel["id"]

        with st.spinner("Cargando estadísticas del partido..."):
            match_stats = cargar_stats_evento(event_id, player_id)
            heatmap_pts = cargar_heatmap(event_id, player_id) if partido_sel["tiene_heatmap"] else []
            shotmap_tiros = cargar_shotmap(event_id, player_id)

        # Stats en tablas (1 columna porque el espacio es estrecho)
        st.markdown("##### Estadísticas del partido")
        for nombre_grupo, campos in MATCH_STATS_GRUPOS.items():
            html = tabla_stats_html(campos, match_stats)
            if html:
                st.markdown(f"**{nombre_grupo}**")
                st.markdown(html, unsafe_allow_html=True)

        # Visualizaciones apiladas verticalmente (columna estrecha)
        st.markdown("##### Visualizaciones")
        fig_heat = render_heatmap(heatmap_pts)
        if fig_heat:
            st.pyplot(fig_heat, use_container_width=True)
            plt.close(fig_heat)
        else:
            st.info("Sin mapa de calor para este partido.")
        fig_shot = render_shotmap(shotmap_tiros)
        if fig_shot:
            st.pyplot(fig_shot, use_container_width=True)
            plt.close(fig_shot)
        else:
            st.info("Sin tiros registrados en este partido.")


def vista_heatmap_global(jugador):
    player_id = jugador["id"]
    tournament_id = jugador.get("tournament_id")
    season_id = jugador.get("season_id")
    comp = jugador.get("competicion", "")
    temp = jugador.get("temporada", "")

    if not tournament_id or not season_id:
        st.info("No se encontraron datos de competición para generar el mapa.")
        return

    with st.spinner("Cargando mapa de movimiento de temporada..."):
        puntos, n_partidos = cargar_heatmap_temporada(player_id, tournament_id, season_id)

    if not puntos:
        st.info("No hay datos de mapa de calor disponibles para esta temporada.")
        return

    n_posiciones = sum(p.get("count", 1) for p in puntos)
    st.caption(
        f"{n_posiciones} posiciones registradas en {n_partidos} partidos · {comp} {temp}"
    )

    fig = render_heatmap(
        puntos,
        titulo=f"{jugador['nombre']} — {comp} {temp}",
        figsize=(12, 8),
    )
    if fig:
        st.pyplot(fig, use_container_width=True)
        plt.close(fig)


def vista_ficha(jugador):
    st.markdown("""<style>
    section[data-testid="stMain"] > div {
        background: transparent !important;
    }
    </style>""", unsafe_allow_html=True)

    stats = jugador.get("stats", {})
    comp = jugador.get("competicion", "—")
    temp = jugador.get("temporada", "—")

    if st.button("← Volver al campograma"):
        st.session_state.jugador_sel = None
        st.rerun()

    st.divider()

    # Cabecera del jugador
    lesion = jugador.get("lesion", "").strip()
    fecha_vuelta = jugador.get("fecha_vuelta", "").strip()

    c_foto, c_info = st.columns([1, 5])
    with c_foto:
        st.markdown(
            f'<img src="{jugador["foto"]}" '
            f'style="width:110px;height:110px;object-fit:cover;border-radius:8px">',
            unsafe_allow_html=True,
        )
        if lesion:
            vuelta_txt = f"Vuelta: {fecha_vuelta}" if fecha_vuelta else "Fecha sin confirmar"
            st.markdown(
                f'<div style="background:#c0392b;color:white;border-radius:6px;'
                f'padding:5px 8px;font-size:12px;font-weight:600;text-align:center;margin-top:4px">'
                f'🤕 Lesionado<br><span style="font-weight:400;font-size:11px">{lesion}</span><br>'
                f'<span style="font-size:10px;opacity:0.85">{vuelta_txt}</span></div>',
                unsafe_allow_html=True,
            )
    # ── Diálogos modales para editar lesión / notas ───────────────────
    @st.dialog("🤕 Editar lesión")
    def _dialog_lesion():
        lesion_actual = (jugador.get("lesion") or "").strip()
        fecha_actual = (jugador.get("fecha_vuelta") or "").strip()
        fecha_default = None
        if fecha_actual:
            try:
                fecha_default = datetime.strptime(fecha_actual, "%d/%m/%Y").date()
            except ValueError:
                fecha_default = None

        motivo = st.text_input(
            "Motivo de la lesión",
            value=lesion_actual,
            placeholder="Ej: Rodilla, esguince tobillo, gemelo...",
            key=f"lesion_motivo_{jugador['id']}",
        )
        sin_fecha = st.checkbox(
            "Fecha de vuelta sin confirmar",
            value=(fecha_default is None and bool(lesion_actual)),
            key=f"lesion_sin_fecha_{jugador['id']}",
        )
        if not sin_fecha:
            fecha_vuelta_d = st.date_input(
                "Fecha de vuelta prevista",
                value=fecha_default or date.today(),
                format="DD/MM/YYYY",
                key=f"lesion_fecha_{jugador['id']}",
            )
        else:
            fecha_vuelta_d = None

        st.markdown("---")
        c1, c2 = st.columns(2)
        with c1:
            if st.button("💾 Guardar", use_container_width=True, type="primary"):
                fecha_str = fecha_vuelta_d.strftime("%d/%m/%Y") if fecha_vuelta_d else ""
                ok = guardar_campos_jugador(int(jugador["id"]), {
                    "lesion": motivo.strip(),
                    "fecha_vuelta": fecha_str,
                })
                if ok:
                    st.cache_data.clear()
                    st.rerun()
        with c2:
            if st.button("🩹 Quitar lesión", use_container_width=True):
                ok = guardar_campos_jugador(int(jugador["id"]), {
                    "lesion": "",
                    "fecha_vuelta": "",
                })
                if ok:
                    st.cache_data.clear()
                    st.rerun()

    @st.dialog("📝 Editar notas")
    def _dialog_notas():
        notas_actuales = (jugador.get("notas") or "").strip()
        notas = st.text_area(
            "Notas del jugador",
            value=notas_actuales,
            height=220,
            placeholder="Observaciones del scouting, recordatorios, contactos...",
            key=f"notas_text_{jugador['id']}",
        )
        st.markdown("---")
        if st.button("💾 Guardar notas", use_container_width=True, type="primary"):
            ok = guardar_campos_jugador(int(jugador["id"]), {"notas": notas.strip()})
            if ok:
                st.cache_data.clear()
                st.rerun()

    with c_info:
        nombre_display = jugador.get("nombre_agencia") or jugador["nombre"]
        c_nombre, c_btn_l, c_btn_n = st.columns([6, 1.2, 1.2])
        with c_nombre:
            st.markdown(f"## [{nombre_display}]({jugador['sofascore_url']})")
        with c_btn_l:
            st.markdown("<div style='height:18px'></div>", unsafe_allow_html=True)
            if st.button("🤕 Lesión", use_container_width=True,
                         key=f"btn_lesion_{jugador['id']}", help="Añadir/editar motivo de lesión y fecha de vuelta"):
                _dialog_lesion()
        with c_btn_n:
            st.markdown("<div style='height:18px'></div>", unsafe_allow_html=True)
            if st.button("📝 Notas", use_container_width=True,
                         key=f"btn_notas_{jugador['id']}", help="Editar las notas internas del jugador"):
                _dialog_notas()
        if jugador.get("nombre_agencia"):
            st.caption(f"SofaScore: {jugador['nombre']}")
        if jugador.get("notas"):
            st.info(f"📝 {jugador['notas']}", icon=None)
        col1, col2, col3 = st.columns(3)
        col1.markdown(f"**Equipo:** {jugador['equipo']}")
        col2.markdown(f"**Posición:** {jugador['posicion']}")
        col3.markdown(f"**Edad:** {jugador['edad']} años")
        col1.markdown(f"**Nacionalidad:** {jugador['nacionalidad']}")
        col2.markdown(f"**Competición:** {comp} {temp}")
        media = stats.get("rating")
        ultimo = jugador.get("ultimo_rating")
        ultimo_rival = jugador.get("ultimo_rival", "")
        ultimo_fecha = jugador.get("ultimo_fecha", "")
        rating_html = badge_rating("Media temporada", media) + badge_rating(f"Último ({ultimo_fecha})", ultimo)
        if rating_html:
            col3.markdown(rating_html, unsafe_allow_html=True)
            if ultimo_rival:
                col3.caption(f"vs {ultimo_rival}")

    st.divider()

    # Pestañas con estado persistente (st.tabs se resetea al rerun)
    OPCIONES_TAB = ["📊 Estadísticas de temporada", "📅 Partidos", "🗺️ Mapa de movimiento"]
    if "ficha_tab" not in st.session_state:
        st.session_state.ficha_tab = OPCIONES_TAB[0]
    tab_actual = st.radio(
        "Sección",
        OPCIONES_TAB,
        horizontal=True,
        label_visibility="collapsed",
        key="ficha_tab",
    )

    if tab_actual == OPCIONES_TAB[0]:
        # Gráfico de evolución de ratings
        tid = jugador.get("tournament_id")
        sid = jugador.get("season_id")
        if tid and sid:
            with st.spinner("Cargando ratings..."):
                ratings = cargar_ratings_temporada(jugador["id"], tid, sid)
            if ratings:
                st.markdown("#### Evolución de rating por partido")
                fig_r = render_grafico_ratings(ratings)
                if fig_r:
                    st.pyplot(fig_r, use_container_width=True)
                    plt.close(fig_r)
                st.divider()

        # Datos de liga para comparativa y percentiles
        liga_data = {}
        if tid and sid:
            with st.spinner("Cargando referencia de liga..."):
                liga_data = cargar_stats_liga(tid, sid)

        for nombre_grupo, campos in STATS_GRUPOS.items():
            campos_sin_rating = [(c, e, f) for c, e, f in campos if c != "rating"]
            html = tabla_stats_html(campos_sin_rating, stats, liga_data)
            if not html:
                continue
            st.markdown(f"#### {nombre_grupo}")
            st.markdown(html, unsafe_allow_html=True)
        if liga_data:
            st.caption(
                "ℹ️ La columna **Top 50** muestra el puesto del jugador en el ranking de los ~50 mejores de la liga "
                "para esa estadística (SofaScore solo expone ese top). En empates se da la mejor posición posible. "
                "*\"fuera top\"* indica que su valor está por debajo del top. Color: verde = parte alta, naranja = parte baja."
            )

    elif tab_actual == OPCIONES_TAB[1]:
        vista_partidos(jugador)

    elif tab_actual == OPCIONES_TAB[2]:
        vista_heatmap_global(jugador)

