import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import xbmcgui
import os
import re
import urllib.parse
from datetime import datetime, timedelta, timezone
import time
# from epggrid import EPGGrid
import requests
from tools import show_tools, debug_log
from analytics import track
from endpoints import SPORTS_LEAGUE_BASE, get_sport_url
from themes import apply_theme_to_window
from sportsgrid_config import (
    MORE_SPORTS,
    SPORT_BUTTON_MAP,
    SPORT_THUMBNAILS,
    TEAM_COLOR_OVERRIDES,
    get_espn_core_sport,
    get_espn_site_sport,
    get_sport_stats_config as config_get_sport_stats_config,
    get_standings_years,
)
from sportsgrid_slideshow import LiveGamesSlideshow, filter_live_games
from sportsgrid_data_schedule import get_next_game_data, get_team_recent_games_data
from sportsgrid_data_stats import get_team_game_stats_data
from sportsgrid_data_leaders import get_team_player_leaders_data, get_team_season_leaders_data
from sportsgrid_racing import (
    get_racing_scores,
    get_racing_standings,
    format_racing_channels,
    format_racing_programs,
)

ADDON = xbmcaddon.Addon()
TEMP_PATH = xbmcvfs.translatePath('special://temp')

def get_sport_stats_config(sport):
    return config_get_sport_stats_config(sport)

class TimeListControl(xbmcgui.ControlList):
    def __init__(self, *args, **kwargs):
        self.grid = kwargs.pop('grid', None)
        super().__init__(*args, **kwargs)
        TimeoutError
    def onAction(self, action):
        if action.getId() == 7:  # ACTION_SELECT_ITEM
            if self.grid:
                self.grid.handleGameSelection()
        super().onAction(action)

class SportsGrid(xbmcgui.WindowXML):
    @classmethod
    def is_dialog_locked(cls):
        """Check if a dialog is currently locked"""
        return cls._dialog_lock
    
    @classmethod
    def acquire_dialog_lock(cls, owner):
        """Acquire dialog lock to prevent concurrent dialogs"""
        if cls._dialog_lock:
            return False
        cls._dialog_lock = True
        cls._dialog_locked_by = owner
        return True
    
    @classmethod
    def release_dialog_lock(cls, owner):
        """Release dialog lock"""
        if cls._dialog_locked_by == owner:
            cls._dialog_lock = False
            cls._dialog_locked_by = None
    
    # Class-level dialog lock to prevent concurrent busydialog crashes
    _dialog_lock = False
    _dialog_locked_by = None
    
    # API rate limiting cache - stores team data to reduce API calls
    # Cache duration: 5 minutes to avoid rate limiting
    _team_cache = {}
    _cache_duration = 300  # 5 minutes
    
    def _api_rate_limit_delay(self):
        """Small delay between API calls to avoid rate limiting"""
        time.sleep(0.3)  # 300ms delay between calls
    
    def _get_cached_team_data(self, team_name, data_type):
        """Get cached team data if available and not expired"""
        cache_key = f"{team_name}_{data_type}"
        if cache_key in self._team_cache:
            cached = self._team_cache[cache_key]
            if time.time() - cached.get('timestamp', 0) < self._cache_duration:
                debug_log(f"SportsGrid: Using cached {data_type} for {team_name}", xbmc.LOGINFO)
                return cached.get('data')
        return None
    
    def _set_cached_team_data(self, team_name, data_type, data):
        """Cache team data"""
        cache_key = f"{team_name}_{data_type}"
        self._team_cache[cache_key] = {
            'timestamp': time.time(),
            'data': data
        }
    
    # Sport-specific team name mappings
    TEAM_MAPPINGS = {
        'mlb': {
            'chi': 'chicago',
            'la': 'los angeles',
            'ny': 'new york',
            'chi cubs': 'chicago cubs',
            'chi white sox': 'chicago white sox',
            'la angels': 'los angeles angels',
            'la dodgers': 'los angeles dodgers',
            'ny yankees': 'new york yankees',
            'ny mets': 'new york mets',
            'athletics': 'oakland athletics',
            'a\'s': 'oakland athletics',
            'jays': 'toronto blue jays',
            'rays': 'tampa bay rays',
            'nats': 'washington nationals',
            'detroit': 'detroit tigers',
            'cincinnati': 'cincinnati reds',
            'toronto': 'toronto blue jays',
            'philadelphia': 'philadelphia phillies',
            'miami': 'miami marlins',
            'washington': 'washington nationals',
            'colorado': 'colorado rockies',
            'atlanta': 'atlanta braves',
            'boston': 'boston red sox',
            'baltimore': 'baltimore orioles',
            'tampa bay': 'tampa bay rays',
            'minnesota': 'minnesota twins',
            'houston': 'houston astros',
            'st. louis': 'st. louis cardinals',
            'milwaukee': 'milwaukee brewers',
            'kansas city': 'kansas city royals',
            'pittsburgh': 'pittsburgh pirates',
            'texas': 'texas rangers',
            'san diego': 'san diego padres',
            'arizona': 'arizona diamondbacks',
            'cleveland': 'cleveland guardians',
            'seattle': 'seattle mariners',
            'san francisco': 'san francisco giants'
        },
        'nba': {
            'cleveland': 'cleveland cavaliers',
            'boston': 'boston celtics',
            'new york': 'new york knicks',
            'indiana': 'indiana pacers',
            'milwaukee': 'milwaukee bucks',
            'detroit': 'detroit pistons',
            'orlando': 'orlando magic',
            'atlanta': 'atlanta hawks',
            'chicago': 'chicago bulls',
            'miami': 'miami heat',
            'toronto': 'toronto raptors',
            'brooklyn': 'brooklyn nets',
            'philadelphia': 'philadelphia 76ers',
            'charlotte': 'charlotte hornets',
            'washington': 'washington wizards',
            'oklahoma city': 'oklahoma city thunder',
            'houston': 'houston rockets',
            'la lakers': 'los angeles lakers',
            'la clippers': 'los angeles clippers',
            'denver': 'denver nuggets',
            'minnesota': 'minnesota timberwolves',
            'golden state': 'golden state warriors',
            'memphis': 'memphis grizzlies',
            'sacramento': 'sacramento kings',
            'dallas': 'dallas mavericks',
            'phoenix': 'phoenix suns',
            'portland': 'portland trail blazers',
            'san antonio': 'san antonio spurs',
            'new orleans': 'new orleans pelicans',
            'utah': 'utah jazz'
        },
        'wnba': {
            'atlanta': 'atlanta dream',
            'chicago': 'chicago sky',
            'connecticut': 'connecticut sun',
            'dallas': 'dallas wings',
            'indiana': 'indiana fever',
            'las vegas': 'las vegas aces',
            'minnesota': 'minnesota lynx',
            'new york': 'new york liberty',
            'phoenix': 'phoenix mercury',
            'seattle': 'seattle storm',
            'washington': 'washington mystics',
            'fever': 'indiana fever',
            'sky': 'chicago sky',
            'sun': 'connecticut sun',
            'wings': 'dallas wings',
            'aces': 'las vegas aces',
            'lynx': 'minnesota lynx',
            'liberty': 'new york liberty',
            'mercury': 'phoenix mercury',
            'storm': 'seattle storm',
            'mystics': 'washington mystics',
            'dream': 'atlanta dream'
        },
        'nhl': {
            'boston': 'boston bruins',
            'buffalo': 'buffalo sabres',
            'detroit': 'detroit red wings',
            'florida': 'florida panthers',
            'montreal': 'montreal canadiens',
            'ottawa': 'ottawa senators',
            'tampa bay': 'tampa bay lightning',
            'toronto': 'toronto maple leafs',
            'carolina': 'carolina hurricanes',
            'columbus': 'columbus blue jackets',
            'new jersey': 'new jersey devils',
            'ny islanders': 'new york islanders',
            'ny rangers': 'new york rangers',
            'philadelphia': 'philadelphia flyers',
            'pittsburgh': 'pittsburgh penguins',
            'washington': 'washington capitals',
            'arizona': 'arizona coyotes',
            'chicago': 'chicago blackhawks',
            'colorado': 'colorado avalanche',
            'dallas': 'dallas stars',
            'minnesota': 'minnesota wild',
            'nashville': 'nashville predators',
            'st. louis': 'st. louis blues',
            'winnipeg': 'winnipeg jets',
            'anaheim': 'anaheim ducks',
            'calgary': 'calgary flames',
            'edmonton': 'edmonton oilers',
            'los angeles': 'los angeles kings',
            'san jose': 'san jose sharks',
            'seattle': 'seattle kraken',
            'vancouver': 'vancouver canucks',
            'vegas': 'vegas golden knights'
        },
        'nfl': {
            'buffalo': 'buffalo bills',
            'miami': 'miami dolphins',
            'new england': 'new england patriots',
            'ny jets': 'new york jets',
            'baltimore': 'baltimore ravens',
            'cincinnati': 'cincinnati bengals',
            'cleveland': 'cleveland browns',
            'pittsburgh': 'pittsburgh steelers',
            'houston': 'houston texans',
            'indianapolis': 'indianapolis colts',
            'jacksonville': 'jacksonville jaguars',
            'tennessee': 'tennessee titans',
            'denver': 'denver broncos',
            'kansas city': 'kansas city chiefs',
            'la chargers': 'los angeles chargers',
            'las vegas': 'las vegas raiders',
            'dallas': 'dallas cowboys',
            'ny giants': 'new york giants',
            'philadelphia': 'philadelphia eagles',
            'washington': 'washington commanders',
            'chicago': 'chicago bears',
            'detroit': 'detroit lions',
            'green bay': 'green bay packers',
            'minnesota': 'minnesota vikings',
            'atlanta': 'atlanta falcons',
            'carolina': 'carolina panthers',
            'new orleans': 'new orleans saints',
            'tampa bay': 'tampa bay buccaneers',
            'arizona': 'arizona cardinals',
            'la rams': 'los angeles rams',
            'san francisco': 'san francisco 49ers',
            'seattle': 'seattle seahawks'
        },
        'mls': {
            'atlanta': 'atlanta united',
            'austin': 'austin fc',
            'charlotte': 'charlotte fc',
            'chicago': 'chicago fire',
            'colorado': 'colorado rapids',
            'columbus': 'columbus crew',
            'd.c.': 'dc united',
            'dc': 'dc united',
            'dallas': 'fc dallas',
            'houston': 'houston dynamo',
            'los angeles': 'los angeles fc',
            'la': 'los angeles fc',
            'la galaxy': 'la galaxy',
            'miami': 'inter miami',
            'minnesota': 'minnesota united',
            'montreal': 'cf montreal',
            'nashville': 'nashville sc',
            'new england': 'new england revolution',
            'new york': 'new york city fc',
            'nyc': 'new york city fc',
            'ny red bulls': 'new york red bulls',
            'orlando': 'orlando city',
            'philadelphia': 'philadelphia union',
            'portland': 'portland timbers',
            'real salt lake': 'real salt lake',
            'san jose': 'san jose earthquakes',
            'seattle': 'seattle sounders',
            'sporting kc': 'sporting kansas city',
            'st. louis': 'st. louis city',
            'toronto': 'toronto fc',
            'vancouver': 'vancouver whitecaps'
        },
        'premierleague': {
            'arsenal': 'arsenal',
            'aston villa': 'aston villa',
            'bournemouth': 'bournemouth',
            'brentford': 'brentford',
            'brighton': 'brighton & hove albion',
            'chelsea': 'chelsea',
            'crystal palace': 'crystal palace',
            'everton': 'everton',
            'fulham': 'fulham',
            'liverpool': 'liverpool',
            'manchester city': 'manchester city',
            'man city': 'manchester city',
            'manchester united': 'manchester united',
            'man united': 'manchester united',
            'newcastle': 'newcastle united',
            'nottingham': 'nottingham forest',
            'southampton': 'southampton',
            'tottenham': 'tottenham hotspur',
            'west ham': 'west ham united',
            'wolves': 'wolverhampton wanderers'
        },
        'laliga': {
            'alaves': 'alaves',
            'almería': 'almeria',
            'athletic bilbao': 'athletic bilbao',
            'athletic': 'athletic bilbao',
            'atletico madrid': 'atletico madrid',
            'atletico': 'atletico madrid',
            'barcelona': 'fc barcelona',
            'barca': 'fc barcelona',
            'celta vigo': 'celta vigo',
            'celta': 'celta vigo',
            'espanyol': 'espanyol',
            'girona': 'girona',
            'granada': 'granada',
            'las palmas': 'las palmas',
            'levante': 'levante',
            'mallorca': 'mallorca',
            'osasuna': 'osasuna',
            'rayo vallecano': 'rayo vallecano',
            'real betis': 'real betis',
            'real madrid': 'real madrid',
            'real sociedad': 'real sociedad',
            'sevilla': 'sevilla',
            'valencia': 'valencia',
            'villarreal': 'villarreal'
        },
        'bundesliga': {
            'augsburg': 'augsburg',
            'bayern munich': 'bayern munich',
            'bayern': 'bayern munich',
            'bochum': 'bochum',
            'darmstadt': 'darmstadt',
            'dortmund': 'borussia dortmund',
            'eintracht frankfurt': 'eintracht frankfurt',
            'freiburg': 'freiburg',
            'hertha': 'hertha bsc',
            'hoffenheim': 'hoffenheim',
            'koln': 'koln',
            'leverkusen': 'bayer leverkusen',
            'mainz': 'mainz',
            'monchengladbach': 'borussia monchengladbach',
            'rb leipzig': 'rb leipzig',
            'stuttgart': 'vfb stuttgart',
            'union berlin': 'union berlin',
            'werder bremen': 'werder bremen',
            'wolfsburg': 'wolfsburg'
        },
        'seriea': {
            'atalanta': 'atalanta',
            'bologna': 'bologna',
            'cagliari': 'cagliari',
            'empoli': 'empoli',
            'fiorentina': 'fiorentina',
            'frosinone': 'frosinone',
            'genoa': 'genoa',
            'inter milan': 'inter milan',
            'inter': 'inter milan',
            'juventus': 'juventus',
            'lazio': 'lazio',
            'lecce': 'lecce',
            'milan': 'ac milan',
            'ac milan': 'ac milan',
            'monza': 'monza',
            'napoli': 'napoli',
            'roma': 'roma',
            'salernitana': 'salernitana',
            'sassuolo': 'sassuolo',
            'torino': 'torino',
            'udinese': 'udinese',
            'venezia': 'venezia',
            'verona': 'verona'
        },
        'ligue1': {
            'brest': 'brest',
            'clermont': 'clermont',
            'le havre': 'le havre',
            'lens': 'lens',
            'lille': 'lille',
            'lyon': 'lyon',
            'marseille': 'marseille',
            'monaco': 'monaco',
            'montpellier': 'montpellier',
            'nantes': 'nantes',
            'nice': 'nice',
            'paris': 'paris saint-germain',
            'psg': 'paris saint-germain',
            'reims': 'reims',
            'rennes': 'rennes',
            'strasbourg': 'strasbourg',
            'toulouse': 'toulouse'
        },
        'championsleague': {
            'arsenal': 'arsenal',
            'atletico madrid': 'atletico madrid',
            'barcelona': 'fc barcelona',
            'bayern': 'bayern munich',
            'bayern munich': 'bayern munich',
            'benfica': 'benfica',
            'borussia dortmund': 'borussia dortmund',
            'chelsea': 'chelsea',
            'dortmund': 'borussia dortmund',
            'fc barcelona': 'fc barcelona',
            'inter milan': 'inter milan',
            'juventus': 'juventus',
            'liverpool': 'liverpool',
            'manchester city': 'manchester city',
            'manchester united': 'manchester united',
            'marseille': 'marseille',
            'milan': 'ac milan',
            'ac milan': 'ac milan',
            'monaco': 'monaco',
            'napoli': 'napoli',
            'paris saint-germain': 'paris saint-germain',
            'psg': 'paris saint-germain',
            'real madrid': 'real madrid',
            'roma': 'roma',
            'sevilla': 'sevilla',
            'tottenham': 'tottenham hotspur',
            'valencia': 'valencia'
        }
    }

    @classmethod
    def normalize_team_name(cls, name, sport):
        """Normalize team name for comparison"""
        if not name:
            return ''
        # Clean up the input name
        name = name.lower().strip()
        original = name
        # Remove playoff seeds and qualifiers (e.g., "(4)", "-z", "-y", "-x")
        name = re.sub(r'\(\d+\)|-[xyz]', '', name).strip()
        # Special case for MLB A's/Athletics
        if sport == 'mlb' and name in ['a\'s', 'athletics', 'oakland a\'s', 'oakland athletics']:
            return 'oakland athletics'
        # Remove division prefix
        if name.startswith(('e-', 'c-', 'w-')):
            name = name[2:].strip()
        # Try direct mapping first
        sport_mappings = cls.TEAM_MAPPINGS.get(sport, {})
        mapped = sport_mappings.get(name)
        if mapped:
            return mapped
        # Split into parts and handle duplicates
        parts = name.split()
        if len(parts) >= 2:
            # Try city mapping
            city = ' '.join(parts[:-1])  # Everything except last word
            nickname = parts[-1]
            # Check if nickname repeats city
            if nickname in city:
                name = city  # Use city as base if nickname is redundant
            else:
                name = ' '.join(parts)
            # Check city mapping again
            mapped_city = sport_mappings.get(city)
            if mapped_city:
                result = mapped_city
                return result
            # Try full team name
            mapped = sport_mappings.get(name)
            if mapped:
                return mapped
        return name

    def set_time_slots(self):
        """Set up sports-appropriate time slots for the grid."""
        now = datetime.now().astimezone(self.local_tz)
        # Create predefined time slots for games
        self.time_slots = [
            # Morning games
            (now.replace(hour=9, minute=0, second=0, microsecond=0),
             now.replace(hour=12, minute=0, second=0, microsecond=0)),
            # Early afternoon games
            (now.replace(hour=12, minute=0, second=0, microsecond=0),
             now.replace(hour=15, minute=0, second=0, microsecond=0)),
            # Late afternoon games
            (now.replace(hour=15, minute=0, second=0, microsecond=0),
             now.replace(hour=18, minute=0, second=0, microsecond=0)),
            # Evening games
            (now.replace(hour=18, minute=0, second=0, microsecond=0),
             now.replace(hour=21, minute=0, second=0, microsecond=0)),
            # Night games
            (now.replace(hour=21, minute=0, second=0, microsecond=0),
             now.replace(hour=23, minute=59, second=59, microsecond=999999))
        ]

    def __init__(self, xml_file: str, addon_path: str, sport: str):
        # Call parent constructor first to ensure window is properly initialized
        super().__init__(xml_file, addon_path)
        # Initialize controls
        self.time_panel = None
        self.channel_panel = None
        self.sport = sport.lower()
        self.local_tz = datetime.now().astimezone().tzinfo
        self.is_android = 'ANDROID_DATA' in os.environ or 'ANDROID_ROOT' in os.environ
        self.android_optimize_mode = False
        self.android_input_debounce_ms = 120
        self.last_interaction_time = time.time()
        # Initialize basic attributes needed by EPGGrid
        self.all_channels = []
        self.channels = []
        self.programs = []
        self.scores = []  # Raw scores for slideshow
        self.is_sports_view = True
        self.program_cache = {}
        self.visible_rows = 13
        self.current_page = 0
        self.time_scroll_pos = 0
        self.time_slots = []
        self.selected_program = None
        # Live games slideshow
        self.slideshow = LiveGamesSlideshow(self)
        # Set up initial time slots
        self.set_time_slots()

        if self.is_android:
            try:
                self.android_optimize_mode = ADDON.getSettingBool('android_optimize_mode')
            except Exception:
                self.android_optimize_mode = ADDON.getSetting('android_optimize_mode') == 'true'
            try:
                self.android_input_debounce_ms = max(50, int(ADDON.getSetting('android_input_debounce_ms') or '120'))
            except Exception:
                self.android_input_debounce_ms = 120

        # Get sports data
        
        try:
            # Check if this is a racing sport
            is_racing = self.sport.lower() in ['f1', 'irl', 'nascar']
            
            if is_racing:
                # Use racing-specific data handlers
                races = get_racing_scores(self.sport)
                self.scores = races  # Store raw race data
                debug_log(f"SportsGrid: Found {len(races)} races for {self.sport}", xbmc.LOGINFO)
                
                standings_data = get_racing_standings(self.sport)
                debug_log(f"SportsGrid: Found {len(standings_data)} drivers for {self.sport}", xbmc.LOGINFO)
                
                # Format using racing-specific formatters
                self.all_channels = format_racing_channels(standings_data, self.sport)
                debug_log(f"SportsGrid: Created {len(self.all_channels)} driver channels", xbmc.LOGINFO)
                self.channels = self.all_channels[:]
                self.programs = format_racing_programs(races, self.channels, self.sport)
                debug_log(f"SportsGrid: Created {len(self.programs)} race programs", xbmc.LOGINFO)
            else:
                # Use traditional team sports handlers
                scores = self.get_scores()
                self.scores = scores  # Store raw scores for slideshow
                debug_log(f"SportsGrid: Found {len(scores)} games for {self.sport}", xbmc.LOGINFO)
                standings_data = self.get_standings()
                debug_log(f"SportsGrid: Found {len(standings_data)} standings entries for {self.sport}", xbmc.LOGINFO)
                # Format data
                self.all_channels = self.format_channels(standings_data)
                debug_log(f"SportsGrid: Created {len(self.all_channels)} channels", xbmc.LOGINFO)
                self.channels = self.all_channels[:]
                self.programs = self.format_programs(scores)
                debug_log(f"SportsGrid: Created {len(self.programs)} programs", xbmc.LOGINFO)
            
        except Exception as e:
            debug_log(f"SportsGrid: Exception in load: {str(e)}", xbmc.LOGERROR)
            xbmcgui.Dialog().ok("Error", "Failed to load sports data. Please try again.")
            self.channels = []
            self.programs = []
            self.close()
            return
        
        if not self.channels:
            xbmcgui.Dialog().ok("Error", "No sports data available. Please try again.")
            self.close()
            return

    def onInit(self):
        """Override onInit to handle sports-specific initialization"""
        # Apply theme colors to window first
        try:
            apply_theme_to_window(self)
        except Exception as e:
            debug_log(f"SportsGrid: Error applying theme: {str(e)}", xbmc.LOGERROR)
        
        try:
            debug_log("SportsGrid: onInit starting", xbmc.LOGINFO)
            # Initialize panels before parent onInit
            self.channel_panel = None
            self.time_panel = None
            # Call parent onInit
            super().onInit()
            debug_log("SportsGrid: super().onInit() done", xbmc.LOGINFO)
            # Set window title
            self.setProperty('SportTitle', self.sport.upper())
            # Verify we have channels (standings)
            if not self.channels:
                xbmcgui.Dialog().ok("Error", "No sports data available. Please try again.")
                self.close()
                return
            # Initialize and populate panels
            self.channel_panel = self.getControl(100)
            self.time_panel = self.getControl(101)
            debug_log(f"SportsGrid: Got controls: channel={self.channel_panel}, time={self.time_panel}", xbmc.LOGINFO)
            # Configure panels
            self.channel_panel.setEnabled(True)
            self.time_panel.setEnabled(True)
            # Populate data
            self.populate_channel_panel()
            self.populate_time_panel()
            debug_log("SportsGrid: Populated panels", xbmc.LOGINFO)
            # Start live games slideshow with raw scores data
            self.slideshow.start(self.scores, self.channels)
            debug_log("SportsGrid: Started live games slideshow", xbmc.LOGINFO)
            # Initial focus and selection - start with teams panel
            # Fix: Check if control exists and is focusable before setting focus
            if self.channel_panel.size() > 0:
                self.channel_panel.selectItem(0)
                # Only try to focus if the control is valid and visible
                try:
                    if self.channel_panel:
                        self.setFocusId(100)
                except Exception as e:
                    debug_log(f"SportsGrid: Could not set focus to control 100: {str(e)}", xbmc.LOGWARNING)
                    # Android/skin fallback: try time panel if teams panel cannot receive focus
                    try:
                        if self.time_panel and self.time_panel.size() > 0:
                            self.time_panel.selectItem(0)
                            self.setFocusId(101)
                    except Exception:
                        pass
            # Make sure both panels are visible and enabled
            self.channel_panel.setVisible(True)
            self.time_panel.setVisible(True)
            
            # Update initial info display
            self.update_selected_info()
            
            # Hide loading overlay (now safely handles missing control 999)
            self.hide_loading()
            debug_log("SportsGrid: onInit complete", xbmc.LOGINFO)
                
        except Exception as e:
            debug_log(f"SportsGrid: onInit error: {str(e)}", xbmc.LOGERROR)
            xbmcgui.Dialog().ok("Error", "Failed to initialize display. Please try again.")
            self.close()
            return

    def onFocus(self, controlId):
        """Handle focus changes to update info panel"""
        try:
            debug_log(f"SportsGrid: onFocus called with controlId={controlId}", xbmc.LOGINFO)
            if controlId in [100, 101]:
                # Small delay to ensure focus is fully set
                xbmc.sleep(50)
                self.update_selected_info()
        except Exception as e:
            debug_log(f"SportsGrid: onFocus error: {str(e)}", xbmc.LOGERROR)

    def onAction(self, action):
        """Handle actions to update info panel when selection changes"""
        try:
            action_id = action.getId()
            # Navigation actions: up, down, left, right, page up, page down
            nav_actions = [1, 2, 3, 4, 5, 6, 7, 105, 106, 107, 108]
            if action_id in nav_actions:
                focused_id = self.getFocusId()
                if focused_id in [100, 101]:
                    self.update_selected_info()
            # Refresh scores on F5 or specific key
            if action_id == 119:  # F5 - refresh
                self._refresh_scores()
        except Exception as e:
            debug_log(f"SportsGrid: onAction error: {str(e)}", xbmc.LOGERROR)
        super().onAction(action)
        
    def _refresh_scores(self):
        """Refresh live scores and update slideshow."""
        try:
            is_racing = self.sport.lower() in ['f1', 'irl', 'nascar']
            
            if is_racing:
                # Use racing-specific refresh
                races = get_racing_scores(self.sport)
                self.scores = races
                self.programs = format_racing_programs(races, self.channels, self.sport)
            else:
                # Use traditional sports refresh
                scores = self.get_scores()
                self.scores = scores  # Update raw scores
                self.programs = self.format_programs(scores)
            
            self.populate_time_panel()
            self.slideshow.refresh(self.scores, self.channels)
            debug_log(f"SportsGrid: Refreshed scores for {self.sport}", xbmc.LOGINFO)
        except Exception as e:
            debug_log(f"SportsGrid: Error refreshing scores: {str(e)}", xbmc.LOGERROR)

    def get_standings(self):
        try:
            espn_sport = get_espn_site_sport(self.sport)
            current_year = datetime.now().year
            years_to_try = get_standings_years(self.sport, current_year)
            data = None
            for year in years_to_try:
                try:
                    url = f"https://site.api.espn.com/apis/v2/{espn_sport}/standings?season={year}"
                    debug_log(f"SportsGrid: Fetching standings from {url}", xbmc.LOGINFO)
                    response = requests.get(
                        url,
                        headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'},
                        timeout=10
                    )
                    if response.status_code == 200:
                        candidate_data = response.json()
                        # Validate the data actually contains team entries
                        # Some sports return 200 with empty standings during off-season
                        total_entries = 0
                        for child in candidate_data.get('children', []):
                            total_entries += len(child.get('standings', {}).get('entries', []))
                        if total_entries > 0:
                            data = candidate_data
                            debug_log(f"SportsGrid: Found {total_entries} standings entries for year {year}", xbmc.LOGINFO)
                            break
                        else:
                            debug_log(f"SportsGrid: Year {year} returned empty standings, trying next year", xbmc.LOGWARNING)
                except Exception as e:
                    debug_log(f"SportsGrid: Error fetching standings for {year}: {e}", xbmc.LOGWARNING)
                    continue
            if not data:
                debug_log("SportsGrid: No standings data available", xbmc.LOGWARNING)
                return []
            
            # Extract league logo from the response
            self.league_logo = ''
            debug_log(f"SportsGrid: API response keys: {data.keys()}", xbmc.LOGINFO)
            leagues = data.get('leagues', [])
            debug_log(f"SportsGrid: Found {len(leagues)} leagues in response", xbmc.LOGINFO)
            if leagues:
                first_league = leagues[0]
                debug_log(f"SportsGrid: First league: {first_league.get('name')}", xbmc.LOGINFO)
                league_logos = first_league.get('logos', [])
                debug_log(f"SportsGrid: League has {len(league_logos)} logos", xbmc.LOGINFO)
                if league_logos:
                    self.league_logo = league_logos[0].get('href', '')
                    debug_log(f"SportsGrid: Found league logo: {self.league_logo}", xbmc.LOGINFO)
            else:
                debug_log("SportsGrid: No leagues found in response", xbmc.LOGWARNING)
            
            standings = []
            children = data.get('children', [])
            debug_log(f"SportsGrid: Found {len(children)} children in standings data", xbmc.LOGINFO)
            for league in children:
                league_name = league.get('name', '')
                debug_log(f"SportsGrid: Processing league: {league_name}", xbmc.LOGINFO)
                # Track current division/conference
                current_division = ''
                if league_name:
                    # Check if it's a division or conference header
                    # Include MLB patterns: AL West, NL East, American League, National League
                    league_name_lower = league_name.lower()
                    is_header = any(word in league_name_lower for word in ['division', 'conference']) or \
                               league_name_lower in ['american league', 'national league'] or \
                               (len(league_name) >= 4 and league_name[:2] in ['AL', 'NL'] and 'West' in league_name or 'East' in league_name or 'Central' in league_name)
                    if is_header:
                        current_division = league_name
                        debug_log(f"SportsGrid: Found division header: {league_name}", xbmc.LOGINFO)
                        standings.append({'team_name': league_name, 'is_division_header': True})
                league_standings = league.get('standings', {})
                entries = league_standings.get('entries', [])
                for entry in entries:
                    team = entry.get('team', {})
                    team_name = team.get('displayName', '')
                    if not team_name:
                        continue
                    if any(word in team_name.lower() for word in ['division', 'conference', 'league', 'wild card']) or \
                       team_name.lower() in ['american league', 'national league'] or \
                       (len(team_name) >= 4 and team_name[:2] in ['AL', 'NL'] and ('West' in team_name or 'East' in team_name or 'Central' in team_name)):
                        # Update current division for wildcard/league headers
                        current_division = team_name
                        standings.append({'team_name': team_name, 'is_division_header': True})
                        continue
                    logos = team.get('logos', [])
                    logo_url = ''
                    if logos:
                        logo_url = logos[0].get('href', '')
                    normalized_name = self.normalize_team_name(team_name, self.sport)
                    record = {'team_name': normalized_name, 'logo_url': logo_url, 'division': current_division}
                    stats = entry.get('stats', [])
                    for stat in stats:
                        stat_name = stat.get('name', '')
                        stat_type = stat.get('type', '')
                        display_value = stat.get('displayValue', '')
                        if stat_name == 'wins':
                            record['wins'] = display_value
                        elif stat_name == 'losses':
                            record['losses'] = display_value
                        elif stat_name == 'winPercent':
                            record['pct'] = display_value
                        elif stat_name == 'gamesBehind':
                            record['gb'] = display_value
                        elif stat_name == 'Home' or stat_type == 'home':
                            record['home'] = display_value
                        elif stat_name == 'Road' or stat_type == 'road':
                            record['away'] = display_value
                        elif stat_name == 'Last Ten Games' or stat_type == 'lasttengames':
                            record['last10'] = display_value
                        elif stat_name == 'streak':
                            record['streak'] = display_value
                        elif 'division' in stat_name.lower() or 'vsdivision' in stat_name.lower() or 'vsdiv' in stat_name.lower() or stat_type == 'division' or stat_type == 'vsDivision':
                            if 'division_record' not in record:
                                record['division_record'] = display_value
                        elif 'conference' in stat_name.lower() or 'vsconference' in stat_name.lower() or 'vsconf' in stat_name.lower() or stat_type == 'conference' or stat_type == 'vsConference':
                            if 'conference_record' not in record:
                                record['conference_record'] = display_value
                        elif stat_name == 'differential':
                            record['differential'] = display_value
                            debug_log(f"SportsGrid: Found differential for {team_name}: {display_value}", xbmc.LOGINFO)
                        elif stat_name == 'avgPointsFor' or stat_name == 'avgPointsAgainst':
                            record[stat_name] = display_value
                        elif stat_name == 'runDifferential' or stat_name == 'goalDifferential':
                            record[stat_name] = display_value
                            debug_log(f"SportsGrid: Found {stat_name} for {team_name}: {display_value}", xbmc.LOGINFO)
                    standings.append(record)
            return standings
        except Exception as e:
            debug_log(f"SportsGrid: Error in get_standings: {str(e)}", xbmc.LOGERROR)
            return []

    def populate_channel_panel(self):
        """Populate or repopulate the channel panel"""
        debug_log("SportsGrid DEBUG: Entered populate_channel_panel", xbmc.LOGINFO)
        self.channel_panel.reset()
        
        is_racing = self.sport.lower() in ['f1', 'irl', 'nascar']
        
        for idx, channel in enumerate(self.channels):
            team_name = channel.get('name', '')
            record = channel.get('record', '')
            item = xbmcgui.ListItem(label=team_name.upper())
            item.setProperty('record', record)
            logo_url = channel.get('logo', '')
            debug_log(f"SportsGrid: Team {team_name}, logo: {logo_url[:50] if logo_url else 'None'}...", xbmc.LOGINFO)
            if logo_url:
                item.setArt({'thumb': logo_url, 'icon': logo_url})
                item.setProperty('logo', logo_url)
            
            if is_racing:
                # Racing-specific properties
                for stat in ['points', 'wins', 'podiums', 'poles', 'top5', 'top10']:
                    value = channel.get(stat, '')
                    if value:
                        item.setProperty(stat, str(value))
                country = channel.get('country', '')
                if country:
                    item.setProperty('country', country)
            else:
                # Traditional sports properties
                for stat in ['wins', 'losses', 'pct', 'gb', 'home', 'away', 'strk']:
                    value = channel.get(stat, '')
                    if value:
                        item.setProperty(stat, str(value))
                # last10 is stored as 'l10' in channel
                l10_value = channel.get('l10', '')
                if l10_value:
                    item.setProperty('l10', str(l10_value))
                
                # Division/Conference info
                division = channel.get('division', '')
                if division:
                    item.setProperty('division', division)
                
                # Division/Conference records
                division_record = channel.get('division_record', '')
                if division_record:
                    item.setProperty('division_record', division_record)
                conference_record = channel.get('conference_record', '')
                if conference_record:
                    item.setProperty('conference_record', conference_record)
                
                # Point/Run differential
                differential = channel.get('differential', '')
                if differential:
                    item.setProperty('differential', differential)
            
            self.channel_panel.addItem(item)
             
    def populate_time_panel(self):
        """Populate or repopulate the time panel"""
        self.time_panel.reset()
        sorted_programs = sorted(
            self.programs,
            key=lambda x: (
                x.get('time', datetime.max) if isinstance(x.get('time'), datetime) else datetime.max,
                'A' if 'final' in str(x.get('status', '')).lower() else
                'B' if any(hint in str(x.get('status', '')).lower() for hint in ['live', 'quarter', 'period', 'inning']) else 'C'
            )
        )
        for idx, program in enumerate(sorted_programs, 1):
            title = program.get('title', '')
            desc = program.get('desc', '')
            status = program.get('status', '')
            # Check if game is live for badge
            is_live = 'live' in str(status).lower() or 'in progress' in str(status).lower()
            if isinstance(program.get('time'), datetime):
                time_str = program['time'].strftime('%I:%M %p').lstrip('0')
            else:
                time_str = 'TBD'
            item = xbmcgui.ListItem(label=title.upper())
            item.setProperty('time', time_str)
            item.setProperty('game', title)
            item.setProperty('status', status)
            item.setProperty('is_live', '1' if is_live else '')
            item.setProperty('description', desc)
            self.time_panel.addItem(item)

    def get_scores(self):
        try:
            espn_sport = get_espn_site_sport(self.sport)
            url = f"https://site.api.espn.com/apis/site/v2/{espn_sport}/scoreboard"
            response = requests.get(
                url,
                headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'},
                timeout=10
            )
            response.raise_for_status()
            data = response.json()
            games = []
            events = data.get('events', [])
            for event in events:
                competition = event.get('competitions', [{}])[0]
                competitors = competition.get('competitors', [])
                if len(competitors) < 2:
                    continue
                team1 = competitors[0].get('team', {})
                team2 = competitors[1].get('team', {})
                team1_name = team1.get('displayName', '')
                team2_name = team2.get('displayName', '')
                
                # Skip All-Star type games
                if team1_name.startswith('Team ') or team2_name.startswith('Team '):
                    continue
                
                if not team1_name or not team2_name:
                    continue
                team1_norm = self.normalize_team_name(team1_name, self.sport)
                team2_norm = self.normalize_team_name(team2_name, self.sport)
                score1 = str(competitors[0].get('score', ''))
                score2 = str(competitors[1].get('score', ''))
                status_obj = competition.get('status', {})
                status_type = status_obj.get('type', {})
                status = status_type.get('description', 'TBD')
                period = status_obj.get('period', '')
                clock = status_obj.get('displayClock', '')
                if period and clock:
                    status = f"{status} - {period}Q {clock}"
                elif period:
                    status = f"{status} - {period}"
                networks = competition.get('broadcasts', [])
                network = networks[0].get('names', ['TBD'])[0] if networks else 'TBD'
                games.append({
                    'team1': team1_norm,
                    'team2': team2_norm,
                    'score1': score1,
                    'score2': score2,
                    'status': status,
                    'network': network,
                    'time': None
                })
            return games
        except Exception as e:
            debug_log(f"SportsGrid: Error in get_scores: {str(e)}", xbmc.LOGERROR)
            return []

    def get_team_game_stats(self, team_name):
        """Get the latest game stats for a specific team"""
        return get_team_game_stats_data(
            self.sport,
            team_name,
            self._get_cached_team_data,
            self._set_cached_team_data,
            self._api_rate_limit_delay,
            lambda msg: debug_log(msg, xbmc.LOGINFO),
            lambda msg: debug_log(msg, xbmc.LOGERROR),
            {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'},
        )

    def get_team_recent_games(self, team_name):
        """Get the last 5 games for a team using date range API"""
        return get_team_recent_games_data(
            self.sport,
            team_name,
            self._get_cached_team_data,
            self._set_cached_team_data,
            lambda msg: debug_log(msg, xbmc.LOGINFO),
            lambda msg: debug_log(msg, xbmc.LOGERROR),
            {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'},
        )

    def get_next_game(self, team_name):
        """Get the next upcoming game for a team"""
        return get_next_game_data(
            self.sport,
            team_name,
            self._get_cached_team_data,
            self._set_cached_team_data,
            lambda msg: debug_log(msg, xbmc.LOGINFO),
            lambda msg: debug_log(msg, xbmc.LOGERROR),
            {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'},
        )

    def get_team_season_leaders(self, team_name):
        """Get team's season statistical rankings using the core statistics API"""
        return get_team_season_leaders_data(
            self.sport,
            team_name,
            self._get_cached_team_data,
            self._set_cached_team_data,
            get_espn_core_sport,
            TEAM_COLOR_OVERRIDES,
            self.get_team_player_leaders,
            lambda msg: debug_log(msg, xbmc.LOGINFO),
            lambda msg: debug_log(msg, xbmc.LOGERROR),
            {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'},
        )

    def get_team_player_leaders(self, team_name, team_id, espn_sport, league):
        """Get the team's leading players (top scorers, etc.)"""
        return get_team_player_leaders_data(
            team_id,
            espn_sport,
            league,
            lambda msg: debug_log(msg, xbmc.LOGINFO),
            {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'},
        )

    def _extract_primary_stat(self, stat_str):
        """Extract primary number from stat string for sorting"""
        import re
        numbers = re.findall(r'\d+', str(stat_str))
        return int(numbers[0]) if numbers else 0

    def format_channels(self, standings):
        """Format standings data as channels for the grid"""
        channels = []
        channel_number = 1
        
        # Get default logo - prefer league logo from API, then fall back to local thumbnail
        default_logo = getattr(self, 'league_logo', '')
        debug_log(f"SportsGrid: league_logo attribute: {default_logo}", xbmc.LOGINFO)
        
        # If no league logo from API, try local thumbnail
        if not default_logo:
            default_thumb = SPORT_THUMBNAILS.get(self.sport, '')
            if default_thumb:
                default_logo = f"special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/sports/{default_thumb}"
        
        debug_log(f"SportsGrid: Using default logo: {default_logo}", xbmc.LOGINFO)
        
        # Sort standings by division/conference blocks, then by wins (descending), then losses (ascending)
        def sort_key(entry):
            try:
                wins = float(entry.get('wins', 0))
                losses = float(entry.get('losses', 0))
                # Use negative wins for descending order
                return (-wins, losses)
            except Exception:
                return (0, 0)
        
        # Group by division/conference headers, keep their order, but sort teams within each group
        sorted_standings = []
        group = []
        for entry in standings:
            # Check if this is a division/conference header
            if entry.get('is_division_header'):
                # Sort previous group if exists
                if group:
                    group.sort(key=sort_key)
                    sorted_standings.extend(group)
                    group = []
                sorted_standings.append(entry)
            else:
                group.append(entry)
        if group:
            group.sort(key=sort_key)
            sorted_standings.extend(group)
        
        for entry in sorted_standings:
            # Skip division/conference header entries
            if entry.get('is_division_header'):
                continue
            stats = []
            if entry.get('wins') and entry.get('losses'):
                stats.append(f"{entry['wins']} - {entry['losses']}")
            if entry.get('pct'):
                stats.append(f"{entry['pct']}")
            if entry.get('gb'):
                stats.append(f"{entry['gb']}")
            if entry.get('home'):
                stats.append(f"{entry['home']}")
            if entry.get('away') or entry.get('road'):
                stats.append(f"{entry.get('away', entry.get('road', ''))}")
            if entry.get('last10') or entry.get('last5'):
                stats.append(f"{entry.get('last10', entry.get('last5', ''))}")
            if entry.get('streak'):
                stats.append(f"{entry['streak']}")
            record = " | ".join(stats)
            
            # Use ESPN logo if available, otherwise use default sport thumbnail
            logo = entry.get('logo_url', '')
            if not logo and default_logo:
                logo = default_logo
            
            channel = {
                'name': entry['team_name'],
                'tvg_id': f"sports_{self.sport}_{channel_number}",
                'logo': logo,
                'group': self.sport.upper(),
                'record': record,
                'label': str(channel_number),
                'label2': entry['team_name'],
                'wins': entry.get('wins', ''),
                'losses': entry.get('losses', ''),
                'pct': entry.get('pct', ''),
                'gb': entry.get('gb', ''),
                'home': entry.get('home', ''),
                'away': entry.get('away', ''),
                'l10': entry.get('last10', ''),
                'strk': entry.get('streak', ''),
                'division': entry.get('division', ''),
                'division_record': entry.get('division_record', ''),
                'conference_record': entry.get('conference_record', '')
            }
            channels.append(channel)
            channel_number += 1
        return channels

    def format_programs(self, games):
        """Format game data as programs for the grid"""
        programs = []
        processed_games = set()
        if not self.time_slots:
            self.set_time_slots()
        now = datetime.now().astimezone(self.local_tz)
        
        for game in games:
            game_id = f"{game['team1']}-{game['team2']}"
            if game_id in processed_games:
                continue
            processed_games.add(game_id)
            status_text = game['status'] if game['status'] else "TBD"
            
            # Simplified - just use default time slot and focus on status
            slot_start, slot_end = self.time_slots[3]  # Default to evening slot
            time_text = status_text  # Use status as time display
            
            team1_norm = game['team1']
            team2_norm = game['team2']
            
            team1_channel = next((c['tvg_id'] for c in self.channels
                                if c['name'] == team1_norm), None)
            team2_channel = next((c['tvg_id'] for c in self.channels
                                if c['name'] == team2_norm), None)
            
            # Add program even if only one channel is found
            if team1_channel or team2_channel:
                score_text = f" ({game['score1']}-{game['score2']})" if game['score1'] and game['score2'] else ""
                if 'final' in status_text.lower():
                    prefix = "[FINAL]"
                elif 'postponed' in status_text.lower() or 'ppd' in status_text.lower():
                    prefix = "[POSTPONED]"
                elif any(hint in status_text.lower() for hint in ['inning', 'quarter', 'period', 'live', 'bot', 'top']):
                    prefix = f"[[COLORlime]Live[/COLOR] - {status_text}]"
                elif 'scheduled' in status_text.lower():
                    prefix = "[SCHEDULED]"
                else:
                    prefix = f"[{status_text}]" if status_text != "Scheduled" else "[TBD]"
                game_text = f"{team1_norm.upper()} vs {team2_norm.upper()}{score_text}"
                game_title = f"{prefix} | {game_text}"
                desc_lines = [
                    f"Status: {status_text}",
                    f"Time: {time_text}",
                    f"Teams: {team1_norm.upper()} vs {team2_norm.upper()}{score_text}",
                    f"Network: {game['network'] or 'TBD'}"
                ]
                program_data = {
                    'start': slot_start,
                    'stop': slot_end,
                    'title': game_title,
                    'desc': "\n".join(desc_lines),
                    'category': self.sport.upper(),
                    'time': time_text,
                    'status': status_text
                }
                program_data['channel'] = team1_channel or team2_channel
                program_data['away_channel'] = team2_channel if team1_channel else None
                programs.append(program_data)
        return programs
        
    def update_selected_info(self):
        """Update info panel based on current selection"""
        try:
            focused_id = self.getFocusId()
            debug_log(f"SportsGrid: update_selected_info called, focused_id={focused_id}", xbmc.LOGINFO)
            
            # Default to channel panel if no focus
            if focused_id == 0:
                focused_id = 100
            
            if focused_id == 100 and self.channel_panel:  # Team stats panel
                # Show team statistics
                selected_pos = self.channel_panel.getSelectedPosition()
                debug_log(f"SportsGrid: Channel panel pos={selected_pos}, size={self.channel_panel.size()}", xbmc.LOGINFO)
                if 0 <= selected_pos < self.channel_panel.size():
                    selected_item = self.channel_panel.getSelectedItem()
                    team_name = selected_item.getLabel()
                    team_record = selected_item.getProperty('record')
                    debug_log(f"SportsGrid: Selected team: {team_name}, record: {team_record}", xbmc.LOGINFO)
                    
                    # Build team stats info
                    stats_info = []
                    if team_record:
                        stats_info.append(f"Record: {team_record}")
                    
                    # Add any additional team stats from properties
                    for prop in ['wins', 'losses', 'pct', 'gb', 'home', 'away', 'l10']:
                        value = selected_item.getProperty(prop)
                        if value:
                            stats_info.append(f"{prop.upper()}: {value}")
                    
                    debug_log(f"SportsGrid: About to set GameTitle and GameInfo properties", xbmc.LOGINFO)
                    self.setProperty('GameTitle', f"[B]{team_name.upper()} Statistics[/B]")
                    self.setProperty('GameInfo', '\n'.join(stats_info) if stats_info else 'No statistics available')
                    debug_log(f"SportsGrid: Set GameTitle property", xbmc.LOGINFO)
                    # Force property update by reading it back
                    _ = self.getProperty('GameTitle')
                    # Force UI refresh
                    xbmc.sleep(10)
                    debug_log(f"SportsGrid: Set GameInfo: {stats_info}", xbmc.LOGINFO)
                    
            elif focused_id == 101 and self.time_panel:  # Games/Races panel
                # Show game/race information
                selected_pos = self.time_panel.getSelectedPosition()
                debug_log(f"SportsGrid: Time panel pos={selected_pos}, size={self.time_panel.size()}", xbmc.LOGINFO)
                if 0 <= selected_pos < self.time_panel.size():
                    selected_item = self.time_panel.getSelectedItem()
                    game_info_text = selected_item.getProperty('game')
                    description = selected_item.getProperty('description')
                    
                    if game_info_text:
                        self.setProperty('GameTitle', '[B]Race Information[/B]' if self.sport.lower() in ['f1', 'irl', 'nascar'] else '[B]Game Information[/B]')
                        # Show description if available (includes circuit, location, lap info)
                        if description:
                            self.setProperty('GameInfo', description)
                        else:
                            self.setProperty('GameInfo', game_info_text)
                    else:
                        self.setProperty('GameTitle', '[B]No Race Selected[/B]' if self.sport.lower() in ['f1', 'irl', 'nascar'] else '[B]No Game Selected[/B]')
                        self.setProperty('GameInfo', 'Select a race to view details' if self.sport.lower() in ['f1', 'irl', 'nascar'] else 'Select a game to view details')
            else:
                # Clear info if no valid selection
                self.setProperty('GameTitle', '')
                self.setProperty('GameInfo', '')
                
        except Exception as e:
            debug_log(f"SportsGrid: update_selected_info error: {str(e)}", xbmc.LOGERROR)
            # Clear properties on error
            self.setProperty('GameTitle', '')
            self.setProperty('GameInfo', '')

    def update_program_info(self, selected_item, channel=None):
        """Update the info panel with sports-specific information"""
        try:
            # Clear properties if no item
            self.setProperty('GameTitle', '')
            self.setProperty('GameInfo', '')
            if not selected_item:
                return
            # Get game title from item or dict
            game = (selected_item.getProperty('game') if isinstance(selected_item, xbmcgui.ListItem)
                   else selected_item.get('title', ''))
            if not game:
                return
            # Build info display
            game_info = []
            self.setProperty('GameTitle', f"[B]{game}[/B]")
            # Add channel/team info if available
            if channel and isinstance(channel, dict):
                record = channel.get('record', '')
                team_name = channel.get('name', '')
                if record and team_name:
                    game_info.append(f"[COLOR yellow]{team_name.upper()}:[/COLOR] {record}")
            # Add program details
            for program in self.programs:
                if program.get('title') == game and program.get('desc'):
                    for line in program['desc'].split('\n'):
                        game_info.append(line)
            self.setProperty('GameInfo', '\n'.join(game_info))
            
        except Exception as e:
            # debug_log(f"SportsGrid: Error updating program info: {str(e)}", xbmc.LOGERROR)
            # Clear properties on error
            self.setProperty('GameTitle', '')
            self.setProperty('GameInfo', '')
    
    def show_loading(self):
        """Show the loading overlay"""
        try:
            # Fix: Check if control 999 exists before trying to use it
            loading_control = self.getControl(999)
            if loading_control:
                loading_control.setVisible(True)
        except:
            # Control 999 doesn't exist in skin - silently ignore
            pass
    
    def hide_loading(self):
        """Hide the loading overlay"""
        try:
            # Fix: Check if control 999 exists before trying to use it
            loading_control = self.getControl(999)
            if loading_control:
                loading_control.setVisible(False)
        except:
            # Control 999 doesn't exist in skin - silently ignore
            pass
     
    def doClose(self):
        """Clean up resources before closing."""
        self.slideshow.stop()
        super().doClose()
    
    def update_loading_progress(self, progress_text="", percent=0):
        """Update loading progress text and bar"""
        try:
            # Update loading text
            if progress_text:
                text_control = self.getControl(998)
                text_control.setLabel(progress_text)
            
            # Update progress bar width (360px max width)
            if 0 <= percent <= 100:
                progress_width = int((percent / 100) * 360)
                progress_control = self.getControl(997)
                progress_control.setWidth(progress_width)
        except Exception as e:
            pass

    def handleGameSelection(self):
            """Handle game selection and stream dialog. Only trigger playback after explicit user selection."""
            # Fix: Use dialog lock to prevent concurrent busydialog crash
            if not self.acquire_dialog_lock('handleGameSelection'):
                debug_log("SportsGrid: Dialog already in use, skipping", xbmc.LOGWARNING)
                return
            
            try:
                item = self.time_panel.getSelectedItem()
                if not item:
                    self.release_dialog_lock('handleGameSelection')
                    return

                game_title = item.getProperty('game')
                if not game_title:
                    self.release_dialog_lock('handleGameSelection')
                    return
                
                # Check if this is racing
                is_racing = self.sport.lower() in ['f1', 'irl', 'nascar']
                
                if is_racing:
                    # Racing uses different stream matching - match by event name
                    race_name = game_title
                    # Remove status prefix
                    race_name = re.sub(r'\[/?[a-z]+\]', '', race_name, flags=re.IGNORECASE).strip()
                    # Extract just the race name without winner info
                    if ' - Winner:' in race_name:
                        race_name = race_name.split(' - Winner:')[0].strip()
                    
                    progress = xbmcgui.DialogProgress()
                    progress.create('Searching', f'Searching for {race_name}...')
                    
                    # Use appropriate racing stream URL
                    url = get_sport_url("motorsport")
                    
                    try:
                        response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=10)
                        response.raise_for_status()
                        data = response.json()
                    except Exception as e:
                        progress.close()
                        xbmcgui.Dialog().ok("Error", f"Failed to load race data: {str(e)}")
                        self.release_dialog_lock('handleGameSelection')
                        return
                    
                    progress.update(50, 'Loading race streams...')
                    stream_links = []
                    stream_names = []
                    
                    for game in data.get('items', []):
                        if game.get('type') == 'item' and game.get('title'):
                            title_lower = game['title'].lower()
                            # Match race name keywords
                            race_keywords = race_name.lower().split()
                            if any(kw in title_lower for kw in race_keywords if len(kw) > 3):
                                if 'link' in game:
                                    links = game.get('link', [])
                                    for i, link in enumerate(links):
                                        clean_url = link
                                        stream_name = f"Stream {i + 1}"
                                        if '(' in link and ')' in link:
                                            stream_name = link[link.rindex('(')+1:link.rindex(')')].strip()
                                            clean_url = link[:link.rindex('(')].strip()
                                        stream_links.append(clean_url)
                                        stream_names.append(stream_name)
                    
                    progress.close()
                    
                    if not stream_links:
                        xbmcgui.Dialog().ok("No Streams", "No streams found for this race")
                        self.release_dialog_lock('handleGameSelection')
                        return
                    
                    from ai_assistant import is_enabled as ai_enabled
                    dialog_options = list(stream_names)
                    if ai_enabled():
                        dialog_options.append('AI Analyze Race')
                    
                    dialog = xbmcgui.Dialog()
                    choice = dialog.select('Choose Stream', dialog_options)
                    
                    if choice is not None and choice >= 0:
                        if ai_enabled() and choice == len(stream_links):
                            # AI Analyze selected
                            try:
                                from ai_ui import show_ai_with_context
                                game_data = {
                                    'team1': race_name,
                                    'team2': '',
                                    'league': self.sport,
                                    'title': race_name
                                }
                                show_ai_with_context(game_data)
                            except Exception as e:
                                debug_log(f"AI Error: {str(e)}", xbmc.LOGERROR)
                                xbmcgui.Dialog().notification('Error', f'AI error: {str(e)}', xbmcgui.NOTIFICATION_ERROR, 3000)
                        else:
                            selected_url = stream_links[choice]
                            list_item = xbmcgui.ListItem(game_title)
                            list_item.setInfo('video', {'title': game_title})
                            list_item.setProperty('IsPlayable', 'true')
                            track(event="play", channel_name=game_title, url=selected_url, source="sportsgrid", sport=getattr(self, 'sport', ''))
                            try:
                                from youtube_guide import cancel_youtube_playback
                                cancel_youtube_playback()
                            except ImportError:
                                pass
                            xbmc.Player().play(selected_url, list_item)
                    
                    self.release_dialog_lock('handleGameSelection')
                    return
                
                # Traditional sports handling
                parts = game_title.split('|')
                if len(parts) < 2:
                    self.release_dialog_lock('handleGameSelection')
                    return
                teams_part = parts[1].strip()
                teams = teams_part.split('vs')
                if len(teams) != 2:
                    self.release_dialog_lock('handleGameSelection')
                    return

                team1_full = teams[0].strip()
                team2_full = teams[1].strip()
                # Remove records or extra info in parentheses
                team1_name = re.sub(r"\s*\(.*?\)", "", team1_full).lower().strip()
                team2_name = re.sub(r"\s*\(.*?\)", "", team2_full).lower().strip()
                
                # Try fuzzy matching for team names in stream titles
                def fuzzy_match(title, team1, team2):
                    t = re.sub(r'\[/?[a-z]+.*?\]', '', title.lower())
                    t = re.sub(r'[^a-z0-9 ]', '', t)
                    team1_clean = re.sub(r'[^a-z0-9 ]', '', team1)
                    team2_clean = re.sub(r'[^a-z0-9 ]', '', team2)
                    debug_log(f"[DEBUG] Matching '{team1_clean}' and '{team2_clean}' against '{t}'", xbmc.LOGINFO)
                    result = (team1_clean in t or team1_clean.split()[-1] in t) and (team2_clean in t or team2_clean.split()[-1] in t)
                    debug_log(f"[DEBUG] Result: {result}", xbmc.LOGINFO)
                    return result
                
                # Use lock-aware progress dialog
                progress = xbmcgui.DialogProgress()
                progress.create('Searching', f'Searching for {team1_name} vs {team2_name}...\n{game_title}')
                if "wnba" in self.sport:
                    url = get_sport_url("nba")
                elif "nba-summer" in self.sport:
                    url = get_sport_url("nba")
                elif "ncaabase" in self.sport:
                    url = get_sport_url("college_baseball")
               
                elif "mls" in self.sport or "premierleague" in self.sport or "laliga" in self.sport or "bundesliga" in self.sport or "seriea" in self.sport or "ligue1" in self.sport or "championsleague" in self.sport:
                    url = get_sport_url("soccer")
                else:
                    url = get_sport_url(self.sport)
                try:
                    response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=10)
                    response.raise_for_status()
                    data = response.json()
                except Exception as e:
                    progress.close()
                    xbmcgui.Dialog().ok("Error", f"Failed to load game data: {str(e)}")
                    self.release_dialog_lock('handleGameSelection')
                    return
                progress.update(25, 'Loading game data...')
                stream_links = []
                stream_names = []
                total_items = len(data.get('items', []))
                if total_items == 0:
                    progress.close()
                    xbmcgui.Dialog().ok("Error", "No games found in data source")
                    self.release_dialog_lock('handleGameSelection')
                    return
                for idx, game in enumerate(data['items']):
                    if progress.iscanceled():
                        progress.close()
                        self.release_dialog_lock('handleGameSelection')
                        return
                    progress.update(50 + int((idx / total_items) * 50), f'Checking game {idx + 1} of {total_items}...')
                    if (game.get('type') == 'item' and
                        game.get('title') and
                        fuzzy_match(game.get('title',''), team1_name, team2_name)):
                        
                        if 'link' in game:
                            links = game.get('link', [])
                            for i, link in enumerate(links):
                                clean_url = link
                                stream_name = f"Stream {i + 1}"
                                if '(' in link and ')' in link:
                                    stream_name = link[link.rindex('(')+1:link.rindex(')')].strip()
                                    clean_url = link[:link.rindex('(')].strip()
                                elif '[COLOR' in link and '[/COLOR]' in link:
                                    clean_url = re.sub(r'\[COLOR.*?\](.*?)\[/COLOR\]', r'\1', link).strip()
                                    if '(' in clean_url and ')' in clean_url:
                                        stream_name = clean_url[clean_url.rindex('(')+1:clean_url.rindex(')')].strip()
                                        clean_url = clean_url[:clean_url.rindex('(')].strip()

                                stream_links.append(clean_url)
                                stream_names.append(stream_name)
                progress.close()
                if not stream_links:
                    self.release_dialog_lock('handleGameSelection')
                    return
                
                from ai_assistant import is_enabled as ai_enabled
                dialog_options = list(stream_names)
                if ai_enabled():
                    dialog_options.append('AI Analyze Game')
                
                dialog = xbmcgui.Dialog()
                choice = dialog.select('Choose Stream', dialog_options)
            
                if choice is not None and choice >= 0:
                    if ai_enabled() and choice == len(stream_links):
                        # AI Analyze selected
                        try:
                            from ai_ui import show_ai_with_context
                            game_data = {
                                'team1': team1_name,
                                'team2': team2_name,
                                'league': self.sport,
                                'title': game_title
                            }
                            show_ai_with_context(game_data)
                        except Exception as e:
                            debug_log(f"AI Error: {str(e)}", xbmc.LOGERROR)
                            xbmcgui.Dialog().notification('Error', f'AI error: {str(e)}', xbmcgui.NOTIFICATION_ERROR, 3000)
                    else:
                        selected_url = stream_links[choice]
                        list_item = xbmcgui.ListItem(game_title)
                        list_item.setInfo('video', {'title': game_title})
                        list_item.setProperty('IsPlayable', 'true')
                        track(event="play", channel_name=game_title, url=selected_url, source="sportsgrid", sport=getattr(self, 'sport', ''))
                        try:
                            from youtube_guide import cancel_youtube_playback
                            cancel_youtube_playback()
                        except ImportError:
                            pass
                        xbmc.Player().play(selected_url, list_item)
                # Only trigger playback if a stream was explicitly selected
                self.release_dialog_lock('handleGameSelection')

            except Exception as e:
                xbmcgui.Dialog().ok("Error", f"Failed to play stream: {str(e)}")
                self.release_dialog_lock('handleGameSelection')
    def onClick(self, control_id):
        """Handle click events"""
        # Handle sport selection buttons
        if control_id in range(900, 911):
            self.handle_sport_button(control_id)
            return
        # Handle click on time panel (right side)
        if control_id == 101 and self.time_panel:
            if hasattr(self, 'handleGameSelection') and callable(getattr(self, 'handleGameSelection')):
                self.handleGameSelection()
            elif hasattr(self, 'handleGameSelection__patched') and callable(getattr(self, 'handleGameSelection__patched')):
                self.handleGameSelection__patched()
            else:
                # Try to call as a method of the class (handleGameSelection may be indented inside another method)
                try:
                    type(self).handleGameSelection(self)
                except Exception as e:
                    xbmcgui.Dialog().ok("Error", f"Could not trigger game selection: {str(e)}")
            return
        # Handle click on channel panel (left side, teams/drivers)
        if control_id == 100 and self.channel_panel:
            selected_pos = self.channel_panel.getSelectedPosition()
            if 0 <= selected_pos < self.channel_panel.size():
                selected_item = self.channel_panel.getSelectedItem()
                team_name = selected_item.getLabel()
                team_logo = selected_item.getProperty('logo')
                team_record = selected_item.getProperty('record')
                
                # Check if this is racing - racing doesn't have team info windows
                is_racing = self.sport.lower() in ['f1', 'irl', 'nascar']
                if is_racing:
                    # For racing, just show driver info in a simple dialog
                    driver_points = selected_item.getProperty('points')
                    driver_wins = selected_item.getProperty('wins')
                    driver_podiums = selected_item.getProperty('podiums')
                    driver_country = selected_item.getProperty('country')
                    
                    info_lines = [f"Driver: {team_name}"]
                    if driver_country:
                        info_lines.append(f"Country: {driver_country}")
                    if driver_points:
                        info_lines.append(f"Points: {driver_points}")
                    if driver_wins:
                        info_lines.append(f"Wins: {driver_wins}")
                    if driver_podiums:
                        info_lines.append(f"Podiums: {driver_podiums}")
                    
                    xbmcgui.Dialog().ok("Driver Info", "\n".join(info_lines))
                    return
                
                # Get all available stats for traditional sports
                team_wins = selected_item.getProperty('wins')
                team_losses = selected_item.getProperty('losses')
                team_pct = selected_item.getProperty('pct')
                team_gb = selected_item.getProperty('gb')
                team_home = selected_item.getProperty('home')
                team_away = selected_item.getProperty('away')
                team_l10 = selected_item.getProperty('l10')
                team_strk = selected_item.getProperty('strk')
                team_division_record = selected_item.getProperty('division_record')
                team_conference_record = selected_item.getProperty('conference_record')
                team_differential = selected_item.getProperty('differential')
                
                # Get latest game stats for this team
                game_stats = self.get_team_game_stats(team_name)
                
                # Get recent games using date range API
                recent_games_list = self.get_team_recent_games(team_name)
                
                # Check if recent games are actually recent (within last 14 days)
                use_recent_games = False
                if recent_games_list:
                    from datetime import timedelta
                    now = datetime.now()
                    for game in recent_games_list:
                        game_date_str = game.get('date', '')
                        if game_date_str:
                            try:
                                game_date = datetime.strptime(game_date_str[:10], '%Y-%m-%d')
                                days_ago = (now - game_date).days
                                if days_ago <= 14:
                                    use_recent_games = True
                                    break
                            except:
                                pass
                
                if use_recent_games:
                    # Format as: W vs Opponent: score, L vs Opponent: score, etc.
                    recent_games_str = ' | '.join([f"{g['result']} {g['opponent']}: {g['score']}" for g in recent_games_list])
                else:
                    # Fall back to L10 from standings
                    recent_games_str = team_l10 if team_l10 else ''
                
                # Get next upcoming game
                next_game = self.get_next_game(team_name)
                next_game_str = ''
                if next_game:
                    next_game_str = f"vs {next_game.get('opponent', 'TBD')} -\n {next_game.get('time', 'TBD')} ({next_game.get('network', 'TBD')})"
                
                # Get team season leaders/rankings
                debug_log(f"SportsGrid: Getting season leaders for {team_name}", xbmc.LOGINFO)
                season_leaders = self.get_team_season_leaders(team_name)
                debug_log(f"SportsGrid: Season leaders result: {season_leaders}", xbmc.LOGINFO)
                
                # Extract team colors
                team_color = season_leaders.get('team_color', '') if season_leaders else ''
                debug_log(f"SportsGrid: Team color for {team_name}: {team_color}", xbmc.LOGINFO)
                
                # Open team info window with all stats
                TeamInfoWindow.show_team_info(
                    team_name, team_logo, team_record,
                    team_wins, team_losses, team_pct, team_gb,
                    team_home, team_away, team_l10, team_strk,
                    game_stats, self.sport, recent_games_str,
                    team_division_record, team_conference_record,
                    next_game_str, season_leaders, team_differential,
                    team_color
                )
            return

    def switch_sport(self, new_sport):
        """Switch to a different sport by using executebuiltin to properly navigate"""
        try:
            debug_log(f"SportsGrid: Switching to sport: {new_sport}", xbmc.LOGINFO)
            self.close()
            xbmc.executebuiltin('Dialog.Close(all,true)')
            xbmc.sleep(200)
            xbmc.executebuiltin(f'RunPlugin(plugin://plugin.video.jet_guide/?action=sports_grid&sport={new_sport})')
        except Exception as e:
            debug_log(f"SportsGrid: Error in switch_sport: {str(e)}", xbmc.LOGERROR)
            xbmcgui.Dialog().ok("Error", f"Failed to switch sport: {str(e)}")
    
    def show_more_sports(self):
        """Show dialog with more sports options"""
        sport_names = [name for name, _ in MORE_SPORTS]
        dialog = xbmcgui.Dialog()
        choice = dialog.select('Select Sport', sport_names)
        
        if choice >= 0:
            sport_code = MORE_SPORTS[choice][1]
            self.switch_sport(sport_code)

    def handle_sport_button(self, control_id):
        """Handle sport selection button clicks"""
        sport = SPORT_BUTTON_MAP.get(control_id)
        
        if sport == 'exit':
            self.close()
            xbmc.sleep(100)
            xbmc.executebuiltin('Dialog.Close(all,true)')
            xbmc.sleep(100)
            xbmc.executebuiltin('ActivateWindow(10024)')
            return
        elif sport == 'more':
            self.show_more_sports()
            return
        elif sport:
            if sport == self.sport:
                return
            self.switch_sport(sport)

# --- TeamInfoWindow stub ---
class TeamInfoWindow(xbmcgui.WindowXML):
    def onInit(self):
        debug_log(f"[TeamInfoWindow] onInit called", xbmc.LOGINFO)
        self.setProperty('TeamName', getattr(self, '_team_name', ''))
        self.setProperty('TeamLogo', getattr(self, '_team_logo', ''))
        self.setProperty('TeamRecord', getattr(self, '_team_record', ''))
        # Try to set focus to a control to receive actions
        try:
            self.setFocusId(10)
        except:
            pass

    def onAction(self, action):
        action_id = action.getId()
        debug_log(f"[TeamInfoWindow] onAction: action_id={action_id}", xbmc.LOGINFO)
        if action_id == xbmcgui.ACTION_PREVIOUS_MENU or action_id == xbmcgui.ACTION_NAV_BACK:
            debug_log("[TeamInfoWindow] Closing due to back action", xbmc.LOGINFO)
            xbmc.executebuiltin('Dialog.Close(all,true)')

    @staticmethod
    def show_team_info(team_name, team_logo, team_record, wins='', losses='', pct='', gb='', home='', away='', l10='', strk='', game_stats=None, sport='nba', recent_games='', division_record='', conference_record='', next_game='', season_leaders=None, differential='', team_color=''):
        debug_log(f"[TeamInfoWindow] show_team_info: name={team_name}, sport={sport}, differential={differential}, team_color={team_color}", xbmc.LOGINFO)
        gs = game_stats or {}
        config = get_sport_stats_config(sport)
        stat1_val = gs.get(config['stat_keys'][0], '')
        stat2_val = gs.get(config['stat_keys'][1], '')
        stat3_val = gs.get(config['stat_keys'][2], '')
        stat4_val = gs.get(config['stat_keys'][3], '')
        stat5_val = gs.get(config['stat_keys'][4], '')
        leader1_val = gs.get(config['leader_keys'][0], '')
        leader2_val = gs.get(config['leader_keys'][1], '')
        leader3_val = gs.get(config['leader_keys'][2], '')
        leader4_val = gs.get(config['leader_keys'][3], '') if len(config['leader_keys']) > 3 else ''
        leader5_val = gs.get(config['leader_keys'][4], '') if len(config['leader_keys']) > 4 else ''
        leader1_headshot = gs.get(f"{config['leader_keys'][0]}_headshot", '')
        leader2_headshot = gs.get(f"{config['leader_keys'][1]}_headshot", '')
        leader3_headshot = gs.get(f"{config['leader_keys'][2]}_headshot", '')
        leader4_headshot = gs.get(f"{config['leader_keys'][3]}_headshot", '') if len(config['leader_keys']) > 3 else ''
        leader5_headshot = gs.get(f"{config['leader_keys'][4]}_headshot", '') if len(config['leader_keys']) > 4 else ''
        opponent = gs.get('opponent', '')
        team_score = gs.get('team_score', '')
        opponent_score = gs.get('opponent_score', '')
        
        def safe_arg(val):
            if val is None:
                return ''
            s = str(val)
            # Remove ALL % characters completely
            s = s.replace('%', '')
            # Replace commas with pipe
            return s.replace(',', '|')
        
        num_placeholders = 40
        args = [
            safe_arg(team_name),
            safe_arg(team_logo or ''),
            safe_arg(team_record or ''),
            safe_arg(wins or ''),
            safe_arg(losses or ''),
            safe_arg(pct or ''),
            safe_arg(gb or ''),
            safe_arg(home or ''),
            safe_arg(away or ''),
            safe_arg(l10 or ''),
            safe_arg(strk or ''),
            safe_arg(stat1_val),
            safe_arg(stat2_val),
            safe_arg(stat3_val),
            safe_arg(stat4_val),
            safe_arg(stat5_val),
            safe_arg(leader1_val),
            safe_arg(leader2_val),
            safe_arg(leader3_val),
            safe_arg(leader4_val),
            safe_arg(leader5_val),
            safe_arg(sport or ''),
            safe_arg(opponent),
            safe_arg(team_score),
            safe_arg(opponent_score),
            safe_arg(leader1_headshot),
            safe_arg(leader2_headshot),
            safe_arg(leader3_headshot),
            safe_arg(leader4_headshot),
            safe_arg(leader5_headshot),
            safe_arg(recent_games or ''),
            safe_arg(division_record or ''),
            safe_arg(conference_record or ''),
            safe_arg(next_game or ''),
            safe_arg(str(season_leaders) if season_leaders else ''),
            safe_arg(differential or ''),
            safe_arg(team_color or ''),
            safe_arg(gs.get('leader_era_alt', '')),
            safe_arg(gs.get('leader_strikeouts_alt', '')),
            safe_arg(gs.get('leader_era_alt_headshot', '')),
            safe_arg(gs.get('leader_strikeouts_alt_headshot', ''))
        ]
        debug_log("[TeamInfoWindow] Args: team_name=" + str(team_name) + ", sport=" + str(sport) + ", args_count=" + str(len(args)) + ", season_leaders=" + str(season_leaders)[:200] if season_leaders else "None", xbmc.LOGINFO)
        
        # Build the argument string manually to avoid format issues
        arg_parts = []
        for a in args:
            # Escape any pipe characters in the value (they're our delimiter)
            arg_parts.append(str(a).replace('|', '___PIPE___'))
        
        arg_string = ','.join(arg_parts)
        xbmc.executebuiltin('RunScript(special://home/addons/plugin.video.jet_guide/team_info_window.py,' + arg_string + ')')
