import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import requests
import re
import os
import random
import subprocess
import base64
import json
import time
import urllib.parse
import threading
from analytics import track
from endpoints import SPORTS_LEAGUE_BASE, get_sport_url
import sys

xbc=xbmc
xbcaddon=xbmcaddon
xbcgui=xbmcgui
xbcvfs=xbmcvfs
from jet_parser import parse_m3u8, parse_xmltv
from addon import M3U8_PATH, XMLTV_PATH
from addon import (show_sports_guide,
                   last_watched_manager, show_sports_grid)
from searchwindow import SearchWindow
from favorites import favorites_manager
from reminders import RemindersManager
from tools import show_tools, debug_log, save_widget_link, browse_addon_plugin_folders, import_channels_from_favourites, browse_installed_video_addons
from widgets import clear_scores_cache
from themes import apply_theme_to_window
from tenbuttons_helpers import (
    play_saved_jet_guide_link,
)
from tenbuttons_live_scores import (
    load_live_scores_widget as _load_live_scores_widget,
    fetch_espn_scores as _fetch_espn_scores,
    show_live_scores_details as _show_live_scores_details,
)
from sportsgrid_slideshow import LiveGamesSlideshow, is_game_live, filter_live_games
from tenbuttons_ticker import (
    fetch_games as _fetch_games,
    get_game_status as _get_game_status,
)
from tenbuttons_sports_news import apply_news_to_window
from tenbuttons_window_misc import (
    start_ticker_updates as _start_ticker_updates,
    remove_widget_by_link as _remove_widget_by_link,
    update_ticker as _update_ticker,
    load_last_watched_channels as _load_last_watched_channels,
    load_favorites_preview as _load_favorites_preview,
    play_last_watched as _play_last_watched,
)
from tenbuttons_click_basic import handle_basic_button_actions
from tenbuttons_click_links import handle_links_and_widgets
from tenbuttons_click_window import handle_window_click_actions
from tenbuttons_click_streams import handle_stream_actions, handle_stream_action_choice
from widgets import fetch_all_scores_with_cache

ACTIVE_WINDOW = None
LAST_TENBUTTONS_OPEN_TS = 0.0

ADDON = xbmcaddon.Addon()
addon__id = ADDON.getAddonInfo('id')

class TenButtonsWindow(xbmcgui.WindowXML):
    def __init__(self, xml_file, addon_path):
        super().__init__(xml_file, addon_path)
        global ACTIVE_WINDOW
        ACTIVE_WINDOW = self
        self.buttons = {
            101: "EPG Guide",# EPG GUIDE
            102: "Sports Events",# SPORTS GUIDE
            # 103: "",# Channel List
            150: "Tools", # Tools
            104: "Search", # Search Channel or Programs
            105: "Favorites", # Favorites
            106: "Sports Stats", # Sports Stats
            # 107: "", # Alerts
            108: "Main Menu", # Main Menu List
            # 109: "", # Clear Cache
            # 110: "", # Settings
            # 111: "", # Search Kodi Addons
            # 112: "", # Import Kodi Favourites
            # 113: "", # Clear Reminders
            114: "Exit", # EXIT
            # 115: "", # Folders and Links
            # 120: "Manage Widgets", # Manage Widgets
            130: "Tap to restore", # Picture-in-Picture / Now Playing
            199: "Record / Players", # Record/Play Stream
            # 960: "", # Youtube Search
        }
        self.buttons[120] = "Manage Widgets"
        self.last_watched_list = None
        self.last_watched_preview_list = None
        self.favorites_preview_list = None
        self.widget_list = None
        self.widget_list2 = None
        self.ticker_control = None
        self.ticker_thread = None
        self.ticker_running = False
        self.ticker_thread = None
        self.ticker_interval = int(xbmcaddon.Addon().getSetting("ticker_interval"))
        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_action_ts = 0
        self.is_closing = False
        self.slideshow = LiveGamesSlideshow(self)
        # Auto-scroll state for preview-panel fixedlists
        self.preview_scroll_timers = {}
        self.preview_resume_timers = {}
        self.preview_scroll_delay_ms = 4000 # 4 seconds before first scroll
        self.preview_scroll_interval_ms = 5000 # 3.5 seconds between scrolls
        self.preview_resume_delay_ms = 5000 # 5 seconds idle before resuming scroll
        # Sports news rotation state for Sports Stats preview panel
        self.sport_news_items = []
        self.sport_news_index = 0
        self.sport_news_timer = None
        self.sport_news_rotation_interval_ms = 6000  # 6 seconds per headline
        self.sports_stats_leagues = [
            ("MLB", "mlb"),
            ("NBA", "nba"),
            ("NCAAB", "ncaab"),
            ("WNBA", "wnba"),
            ("NHL", "nhl"),
            ("NFL", "nfl"),
            ("MLS", "mls"),
            ("Premier League", "premierleague"),
            ("La Liga", "laliga"),
            ("Bundesliga", "bundesliga"),
            ("Serie A", "seriea"),
            ("Ligue 1", "ligue1"),
            ("Champions League", "championsleague"),
        ]
        self.tools_preview_items = [
            ("Settings", "settings"),
            ("Clear Cache", "clear_cache"),
            ("Manage Widgets", "manage_widgets"),
            ("Reminders", "open_tools"),
            ("Saved Links", "open_tools"),
            ("Custom Groups", "open_tools"),
            ("What's New", "whats_new"),
        ]
        self.record_players_preview_items = [
            ("Play Stream in mpv", 0),
            ("Record Stream", 1),
            ("Multi-Stream mpv", 2),
            ("Multi-Stream Choose", 3),
            ("Stop Recording", 4),
            ("Watch Recording", 5),
            ("Play in VLC", 6),
            ("Play in MPC-HC", 7),
            ("Android External", 8),
            ("Directions", 9),
        ]

        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



    def onInit(self):
        debug_log("DEBUG: TenButtonsWindow onInit called", level=xbmc.LOGINFO)
        
        # Apply theme colors to window
        try:
            apply_theme_to_window(self)
        except Exception as e:
            debug_log(f"DEBUG: Error applying theme: {str(e)}", level=xbmc.LOGERROR)
        
        try:
            xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
            xbmc.executebuiltin('Dialog.Close(busydialog)')
            xbmc.sleep(50)
            self.ticker_running = False
            if self.ticker_thread and self.ticker_thread.is_alive():
                self.ticker_thread.join(timeout=1)
            debug_log("DEBUG: Initializing buttons", level=xbmc.LOGINFO)
            for btn_id in self.buttons:
                try:
                    control = self.getControl(btn_id)
                    if control:
                        control.setLabel(self.buttons[btn_id])
                        control.setEnabled(True)
                except Exception as e:
                    # Silently skip non-existent controls - they may be defined in code but not in skin
                    pass

            self.load_sports_stats_preview()
            self.load_sports_events_preview()
            self.load_tools_preview()
            self.load_record_players_preview()
            self.load_search_quick_filters()
            self.update_preview_context(101)
            self._start_preview_auto_scroll()
            
            # Update button 130 to show current channel info if video is playing
            try:
                control_130 = self.getControl(130)
                if control_130:
                    player = xbmc.Player()
                    debug_log(f"DEBUG: Button 130 - player.isPlaying() = {player.isPlaying()}", level=xbmc.LOGINFO)
                    if player.isPlaying():
                        # Get the currently playing channel info
                        try:
                            # Try to get the channel name from JSON-RPC
                            playing_file = player.getPlayingFile()
                            debug_log(f"DEBUG: Button 130 - playing_file = {playing_file}", level=xbmc.LOGINFO)
                            
                            # Use JSON-RPC to get current player item
                            jsonrpc = {
                                'jsonrpc': '2.0',
                                'method': 'Player.GetItem',
                                'params': {'playerid': 1},
                                'id': 1
                            }
                            import json
                            result = xbmc.executeJSONRPC(json.dumps(jsonrpc))
                            data = json.loads(result)
                            item = data.get('result', {}).get('item', {})
                            channel_name = item.get('label', '')
                            debug_log(f"DEBUG: Button 130 - channel_name from JSON-RPC = {channel_name}", level=xbmc.LOGINFO)
                            
                            if not channel_name:
                                # Use filename/URL as fallback
                                channel_name = playing_file.split('/')[-1][:20]
                                debug_log(f"DEBUG: Button 130 - channel_name from filename = {channel_name}", level=xbmc.LOGINFO)
                            
                            if channel_name:
                                control_130.setLabel(f"{channel_name}\nTap to restore")
                            else:
                                control_130.setLabel("Tap to restore")
                        except Exception as e:
                            debug_log(f"DEBUG: Button 130 - error getting channel: {str(e)}", level=xbmc.LOGINFO)
                            control_130.setLabel("Tap to restore")
                    else:
                        control_130.setLabel("Tap to restore")
            except Exception as e:
                debug_log(f"DEBUG: Error updating button 130: {str(e)}", level=xbmc.LOGINFO)
            debug_log("DEBUG: Initializing widget list", level=xbmc.LOGINFO)
            try:
                self.widget_list = self.getControl(310)
                self.widget_list2 = self.getControl(311)
                
                # Force reload widget settings (in case changed in Tools window)
                widgets_enabled = ADDON.getSetting('widgets_enabled')
                widgets_auto_load = ADDON.getSetting('widgets_auto_load')
                debug_log(f"DEBUG: Widgets enabled: {widgets_enabled}, Auto-load: {widgets_auto_load}", level=xbmc.LOGINFO)
                
                # Check widget settings and load widgets if enabled
                widgets_enabled = ADDON.getSettingBool("widgets_enabled")
                widgets_auto_load = ADDON.getSettingBool("widgets_auto_load")
                
                if widgets_enabled and widgets_auto_load:
                    self.load_pinned_widgets(self.widget_list)
                    # Load Live Scores if enabled (use cache for faster startup)
                    current_addon = xbmcaddon.Addon()
                    live_scores_enabled = current_addon.getSettingBool("live_scores_enabled")
                    debug_log(f"DEBUG: Row 2 - live_scores_enabled: {live_scores_enabled}", level=xbmc.LOGINFO)
                    self.load_pinned_widgets(self.widget_list2, widget_row=2, force_refresh=False)
                elif not widgets_enabled:
                    debug_log("DEBUG: Widgets disabled in settings, skipping load", level=xbmc.LOGINFO)
                else:
                    debug_log("DEBUG: Widgets auto-load disabled, use manual refresh", level=xbmc.LOGINFO)
            except Exception as e:
                debug_log(f"Error initializing widget lists: {str(e)}", level=xbmc.LOGERROR)
            debug_log("DEBUG: Initializing last watched list", level=xbmc.LOGINFO)
            try:
                self.last_watched_list = self.getControl(200)
                try:
                    self.last_watched_preview_list = self.getControl(362)
                except Exception:
                    self.last_watched_preview_list = None
                self.load_last_watched_channels()
            except Exception as e:
                debug_log(f"Error initializing last watched list: {str(e)}", level=xbmc.LOGERROR)
            debug_log("DEBUG: Initializing favorites preview list", level=xbmc.LOGINFO)
            try:
                try:
                    self.favorites_preview_list = self.getControl(365)
                except Exception:
                    self.favorites_preview_list = None
                self.load_favorites_preview()
            except Exception as e:
                debug_log(f"Error initializing favorites preview list: {str(e)}", level=xbmc.LOGERROR)
            debug_log("DEBUG: Initializing ticker", level=xbmc.LOGINFO)
            try:
                self.ticker_control = self.getControl(300)
                self.ticker_running = True
                self.start_ticker_updates()
            except Exception as e:
                debug_log(f"Error initializing ticker: {str(e)}", level=xbmc.LOGERROR)
            debug_log("DEBUG: Initializing live games slideshow", level=xbmc.LOGINFO)
            try:
                # Check if slideshow is enabled in settings
                slideshow_enabled = ADDON.getSettingBool('slideshow_enabled')
                if slideshow_enabled:
                    self._start_live_games_slideshow()
                else:
                    debug_log("DEBUG: Slideshow disabled in settings", level=xbmc.LOGINFO)
            except Exception as e:
                debug_log(f"Error initializing slideshow: {str(e)}", level=xbmc.LOGERROR)
        except Exception as e:
            debug_log(f"Failed to initialize TenButtonsWindow: {str(e)}", level=xbmc.LOGERROR)
            xbmcgui.Dialog().ok("Error", f"Failed to initialize window: {str(e)}")

    def load_pinned_widgets(self, widget_list=None, widget_row=1, force_refresh=False):
        """
        Load pinned widget links from widgets.json and display in the given widget_list.
        widget_row can be used to filter or split widgets between rows if needed.
        Also loads Live Scores widget if enabled in settings.
        
        Args:
            widget_list: The widget list to populate
            widget_row: Which row (1 or 2) to load
            force_refresh: If True, force refresh Live Scores from API
        """
        debug_log(f"DEBUG: load_pinned_widgets called for row {widget_row}, force_refresh={force_refresh}", level=xbmc.LOGINFO)
        
        # Check for Live Scores widget (row 2 only)
        if widget_row == 2:
            # Get fresh addon instance to read latest settings
            current_addon = xbmcaddon.Addon()
            live_scores_enabled = current_addon.getSetting('live_scores_enabled')
            debug_log(f"DEBUG: Row 2 - live_scores_enabled setting: {live_scores_enabled}, force_refresh: {force_refresh}", level=xbmc.LOGINFO)
            if live_scores_enabled == 'true':
                sport_pref = current_addon.getSetting('live_scores_sport')
                debug_log(f"DEBUG: Row 2 - loading live scores with sport: {sport_pref}, force_refresh: {force_refresh}", level=xbmc.LOGINFO)
                self.load_live_scores_widget(widget_list, force_refresh=force_refresh)
                return  # Don't load both pinned widgets and live scores in row 2
        
        if widget_list is None:
            widget_list = self.widget_list
        if not widget_list:
            debug_log(f"DEBUG: widget_list (row {widget_row}) is None", level=xbmc.LOGINFO)
            return
        try:
            widget_list.reset()
            try:
                WIDGETS_PATH = xbmcvfs.translatePath("special://userdata/addon_data/plugin.video.jet_guide/widgets.json")
                debug_log(f"DEBUG: Resolved widgets.json path: {WIDGETS_PATH}", level=xbmc.LOGINFO)
                exists = xbmcvfs.exists(WIDGETS_PATH)
                debug_log(f"DEBUG: widgets.json exists? {exists}", level=xbmc.LOGINFO)
                if not exists:
                    debug_log("DEBUG: widgets.json does not exist", level=xbmc.LOGINFO)
                    return
                with xbmcvfs.File(WIDGETS_PATH, "r") as f:
                    links = json.loads(f.read())
                debug_log(f"DEBUG: widgets.json loaded, {len(links)} widgets", level=xbmc.LOGINFO)
            except Exception as e:
                debug_log(f"DEBUG: Exception in load_pinned_widgets: {e}", level=xbmc.LOGERROR)
                return
            debug_log(f"DEBUG: widgets.json loaded, {len(links)} widgets", level=xbmc.LOGINFO)
            count = 0
            # Assign widgets to rows based on "row" property if present, else fallback to even/odd split
            for idx, link in enumerate(links):
                row_prop = link.get("row")
                if row_prop:
                    if int(row_prop) != widget_row:
                        continue
                else:
                    if widget_row == 2:
                        if idx % 2 == 0:
                            continue
                    elif widget_row == 1:
                        if idx % 2 == 1:
                            continue
                # --- original widget item creation logic below ---
                if "source_folder" in link:
                    try:
                        params = {
                            "jsonrpc": "2.0",
                            "method": "Files.GetDirectory",
                            "id": 1,
                            "params": {
                                "directory": link["source_folder"],
                                "media": "files"
                            }
                        }
                        response = xbmc.executeJSONRPC(json.dumps(params))
                        data = json.loads(response)
                        items = data.get("result", {}).get("files", [])
                        for item in items:
                            widget_link = item.get("file", "")
                            label = item.get("label", widget_link)
                            # Try to extract thumbnail from base64-encoded plugin links
                            thumb = ""
                            import base64
                            import json as _json
                            import re as _re
                            # Improved base64 extraction for plugin links
                            b64_match = _re.search(r"plugin://[^/]+/[^/]+/([^/?]+)", widget_link)
                            if b64_match:
                                try:
                                    b64_str = b64_match.group(1)
                                    # Remove any trailing path or query
                                    b64_str = b64_str.split('/')[0].split('?')[0]
                                    # Remove non-base64 chars and whitespace
                                    import re as _re2
                                    b64_str = _re2.sub(r'[^A-Za-z0-9_\-+=]', '', b64_str)
                                    b64_str = b64_str.strip().replace('\n', '').replace('\r', '')
                                    # Pad base64 if needed
                                    missing_padding = len(b64_str) % 4
                                    if missing_padding:
                                        b64_str += "=" * (4 - missing_padding)
                                    try:
                                        decoded = base64.urlsafe_b64decode(b64_str).decode("utf-8")
                                        decoded_json = _json.loads(decoded)
                                        thumb = decoded_json.get("thumbnail") or decoded_json.get("fanart") or ""
                                        if thumb:
                                            debug_log(f"DEBUG: Extracted thumbnail from base64: {thumb}", level=xbmc.LOGINFO)
                                    except Exception as e2:
                                        debug_log(f"DEBUG: Secondary decode fail: {e2}", level=xbmc.LOGERROR)
                                except Exception as e:
                                    debug_log(f"DEBUG: Failed to decode base64 from widget_link: {e}", level=xbmc.LOGERROR)
                            if not thumb:
                                thumb = (
                                    item.get("thumbnail")
                                    or item.get("fanart")
                                    or item.get("icon")
                                    or (item.get("art", {}).get("thumb") if "art" in item else None)
                                    or (item.get("art", {}).get("icon") if "art" in item else None)
                                    or ""
                                )
                            icon = thumb if thumb else "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/folders.PNG"
                            if not thumb and widget_link.startswith("plugin://"):
                                m = re.match(r"plugin://([^/]+)/", widget_link)
                                if m:
                                    addon_id = m.group(1)
                                    icon_path = f"special://home/addons/{addon_id}/icon.png"
                                    if xbmcvfs.exists(icon_path):
                                        icon = icon_path
                            # Custom sports icon logic with debug (must be last)
                            sports_keywords = ["NBA","NFL","Soccer"]
                            label_lower = label.lower()
                            if "mlb" in label_lower or "mlb" in widget_link.lower():
                                debug_log(f"DEBUG: MLB found in label or widget_link, setting MLB icon", level=xbmc.LOGINFO)
                                icon = "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/mlb.jpg"
                            # elif "nba" in label_lower or "nba" in widget_link.lower():
                            #     debug_log(f"DEBUG: NBA found in label or widget_link, setting NBA icon", level=xbmc.LOGINFO)
                            #     icon = "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/mlb.jpg"
                            elif any(word.lower() in label_lower or word.lower() in widget_link.lower() for word in sports_keywords):
                                debug_log(f"DEBUG: Other sports keyword found in label='{label}' or widget_link='{widget_link}', setting default sports icon", level=xbmc.LOGINFO)
                                icon = "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/sports_icon.png"
                            else:
                                debug_log(f"DEBUG: No sports keyword in label='{label}' or widget_link='{widget_link}'", level=xbmc.LOGINFO)
                            list_item = xbmcgui.ListItem(label)
                            debug_log(f"Widget Item Debug: label={label}, widget_link={widget_link}, item={item}", level=xbmc.LOGINFO)
                            # If a thumbnail was extracted, always use it and skip sports icon logic
                            if thumb:
                                icon = thumb
                            else:
                                sports_keywords = ["NFL","Soccer"]
                                if any(word.lower() in label.lower() or word.lower() in widget_link.lower() for word in sports_keywords):
                                    debug_log(f"DEBUG: Sports keyword found and no thumbnail, setting sports icon", level=xbmc.LOGINFO)
                                    icon = "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/sports_icon.png"
                            # If sports icon is used, set all art fields to it
                            if icon == "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/sports_icon.png":
                                art_dict = {
                                    'icon': icon,
                                    'thumb': icon,
                                    'poster': icon,
                                    'banner': icon,
                                    'fanart': icon,
                                    'landscape': icon,
                                    'clearart': icon,
                                    'clearlogo': icon
                                }
                            else:
                                art_dict = {
                                    'icon': icon,
                                    'thumb': icon,
                                    'poster': icon,
                                    'banner': icon,
                                    'fanart': item.get("fanart", icon),
                                    'landscape': icon,
                                    'clearart': icon,
                                    'clearlogo': icon
                                }
                            debug_log(f"Widget Art Debug: label={label}, widget_link={widget_link}, art={art_dict}, icon={icon}", level=xbmc.LOGINFO)
                            list_item.setArt(art_dict)
                            list_item.setProperty("widget_link", widget_link)
                            widget_list.addItem(list_item)
                            count += 1
                    except Exception as e:
                        debug_log(f"Widget dynamic refresh failed: {e}", level=xbmc.LOGERROR)
                elif "links" in link and isinstance(link["links"], list):
                    for item in link["links"]:
                        widget_link = item["file"] if isinstance(item, dict) else item
                        label = item["label"] if isinstance(item, dict) and "label" in item else link.get("title", "Widget")
                        icon = "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/folders.PNG"
                        # Custom sports icon logic for static links with debug
                        sports_keywords = ["NFL", "Soccer"]
                        if any(word.lower() in label.lower() or word.lower() in widget_link.lower() for word in sports_keywords):
                            debug_log(f"DEBUG: Sports keyword found in static label='{label}' or widget_link='{widget_link}', setting sports icon", level=xbmc.LOGINFO)
                            icon = "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/sports_icon.png"
                        else:
                            debug_log(f"DEBUG: No sports keyword in static label='{label}' or widget_link='{widget_link}'", level=xbmc.LOGINFO)
                        if widget_link.startswith("plugin://"):
                            m = re.match(r"plugin://([^/]+)/", widget_link)
                            if m:
                                addon_id = m.group(1)
                                icon_path = f"special://home/addons/{addon_id}/icon.png"
                                if xbmcvfs.exists(icon_path):
                                    icon = icon_path
                        list_item = xbmcgui.ListItem(label)
                        list_item.setArt({'icon': icon, 'thumb': icon})
                        list_item.setProperty("widget_link", widget_link)
                        widget_list.addItem(list_item)
                        count += 1
                else:
                    label = link.get("title", "Widget")
                    widget_link = link.get("link", "")
                    if (not label or label == "Widget") and widget_link.startswith("plugin://plugin.video."):
                        label = widget_link.replace("plugin://plugin.video.", "").split("/")[0]
                    icon = "special://home/addons/plugin.video.jet_guide/resources/skins/default/720p/media/folders.PNG"
                    if widget_link.startswith("plugin://"):
                        m = re.match(r"plugin://([^/]+)/", widget_link)
                        if m:
                            addon_id = m.group(1)
                            icon_path = f"special://home/addons/{addon_id}/icon.png"
                            if xbmcvfs.exists(icon_path):
                                icon = icon_path
                    list_item = xbmcgui.ListItem(label)
                    list_item.setArt({'icon': icon, 'thumb': icon})
                    list_item.setProperty("widget_link", widget_link)
                    widget_list.addItem(list_item)
                    count += 1
        except Exception as e:
            debug_log(f"Error loading pinned widgets: {str(e)}", level=xbmc.LOGERROR)

    def load_live_scores_widget(self, widget_list=None, force_refresh=False):
        return _load_live_scores_widget(self, widget_list=widget_list, force_refresh=force_refresh)

    def fetch_espn_scores(self, sport_endpoint, sport_name):
        return _fetch_espn_scores(self, sport_endpoint, sport_name)

    def show_live_scores_details(self, item):
        return _show_live_scores_details(self, item)


    def start_ticker_updates(self):
        return _start_ticker_updates(self)

    def remove_widget_by_link(self, widget_link):
        return _remove_widget_by_link(self, widget_link)

    def update_ticker(self):
        return _update_ticker(self)

    def load_last_watched_channels(self):
        return _load_last_watched_channels(self, last_watched_manager)

    def load_favorites_preview(self):
        return _load_favorites_preview(self, favorites_manager, parse_m3u8, M3U8_PATH)

    def _start_live_games_slideshow(self):
        """Start the live games slideshow with current scores."""
        from widgets import fetch_all_scores_with_cache
        
        # Get enabled sports from settings
        current_addon = xbmcaddon.Addon()
        enabled_sports = []
        sport_settings = {
            'ls_nfl': 'NFL',
            'ls_mlb': 'MLB',
            'ls_ncaabase': 'NCAABASE',
            'ls_nba': 'NBA',
            'ls_nba_summer_league': 'NBA Summer League',
            'ls_nba_utah_summer': 'NBA Utah Summer League',
            'ls_nba_california_classic': 'NBA California Classic',
            'ls_ncaab': 'NCAAB',
            'ls_wnba': 'WNBA',
            'ls_nhl': 'NHL',
            'ls_mls': 'MLS',
            'ls_cfl': 'CFL',
            'ls_la_liga': 'La Liga',
            'ls_bundesliga': 'Bundesliga',
            'ls_serie_a': 'Serie A',
            'ls_ligue_1': 'Ligue 1',
            'ls_champions_league': 'Champions League',
        }
        for setting_id, sport_name in sport_settings.items():
            try:
                if current_addon.getSettingBool(setting_id):
                    enabled_sports.append(sport_name)
            except Exception:
                pass
        
        if not enabled_sports:
            enabled_sports = ['NFL', 'MLB', 'NBA', 'NBA Summer League', 'NBA Utah Summer League', 'NBA California Classic', 'NHL', 'MLS', 'NCAAB', 'WNBA']
        
        debug_log(f"DEBUG: Slideshow enabled sports: {enabled_sports}", level=xbmc.LOGINFO)
        
        # Fetch all scores (uses cache if available)
        games = fetch_all_scores_with_cache(enabled_sports, force_refresh=False)
        debug_log(f"DEBUG: Fetched {len(games)} games for slideshow", level=xbmc.LOGINFO)
        
        # Format ALL games for slideshow (slideshow will filter for live games)
        formatted_games = []
        for game in games:
            # Build a sportsgrid-style detailed status. The widget layer normally
            # pre-builds this into game['status'], but rebuild it here as a safety
            # net in case the cache (or another caller) only has the short form.
            base_status = game.get('status', '') or game.get('full_status', '')
            period = str(game.get('period', '') or '').strip()
            clock = str(game.get('clock', '') or '').strip()
            # If the base already has any period/clock hint (e.g. "5 inning",
            # "2Q 5:30", "OT 1:00", "Top 3rd", "Halftime"), trust it and don't
            # append period/clock again. Otherwise enrich.
            base_lower = base_status.lower()
            already_enhanced = any(h in base_lower for h in [
                'q', 'quarter', 'inning', 'half', ' ot', 'period',
                'top ', 'bot ', 'middle', 'end of', '1st', '2nd', '3rd', '4th',
                '5th', '6th', '7th', '8th', '9th'
            ])
            if already_enhanced:
                status_text = base_status
            elif period and clock:
                status_text = f"{base_status} - {period}Q {clock}"
            elif period:
                status_text = f"{base_status} - {period}"
            else:
                status_text = base_status
            
            formatted_games.append({
                'team1': game['team1'],
                'team2': game['team2'],
                'score1': game['score'].split(' - ')[0] if ' - ' in game['score'] else '0',
                'score2': game['score'].split(' - ')[1] if ' - ' in game['score'] else '0',
                'status': status_text,
                'network': game.get('sport', ''),
                'broadcaster': game.get('broadcast', 'TBD'),
                'time': None,
                'logo1': game.get('team1_logo', ''),
                'logo2': game.get('team2_logo', '')
            })
        
        # Log first few games for debugging
        for i, game in enumerate(formatted_games[:3]):
            debug_log(f"DEBUG: Game {i}: {game['team1']} vs {game['team2']}, status={game['status']}, logo1={game['logo1'][:60] if game['logo1'] else 'NONE'}", level=xbmc.LOGINFO)
        
        # Start slideshow - it will filter for live games internally
        self.slideshow.start(formatted_games, [])
        debug_log("DEBUG: Live games slideshow started", level=xbmc.LOGINFO)

    def onClick(self, control_id):
        # Stop auto-scroll when user clicks a preview list
        if control_id in (360, 361, 362, 363, 364, 365, 370):
            self._cancel_preview_scroll(control_id)
            self._cancel_preview_resume(control_id)
        """Handle click events on controls"""
        if control_id == 370:
            try:
                qf_list = self.getControl(370)
                if qf_list:
                    item = qf_list.getSelectedItem()
                    if item:
                        quick_filter = item.getProperty("quick_filter") or "all"
                        # from searchwindow import SearchWindow
                        try:
                            channels = parse_m3u8(M3U8_PATH)
                            programs = parse_xmltv(XMLTV_PATH)
                            addon_path = ADDON.getAddonInfo('path')
                            search_window = SearchWindow(
                                'search_window.xml',
                                addon_path,
                                channels=channels,
                                programs=programs,
                                quick_filter=quick_filter,
                            )
                            search_window.doModal()
                            del search_window
                        except Exception as e:
                            xbmcgui.Dialog().ok("Search Error", f"Error during search: {str(e)}")
                        return
            except Exception as e:
                xbmcgui.Dialog().ok("Search", f"Error opening quick filter: {str(e)}")
                return

        if control_id == 363:
            try:
                tools_list = self.getControl(363)
                if tools_list:
                    item = tools_list.getSelectedItem()
                    if item:
                        action = item.getProperty("tools_action")
                        if action == "settings":
                            xbmc.executebuiltin(f'Addon.OpenSettings({addon__id})')
                            return
                        if action == "clear_cache":
                            cache_path = xbmcvfs.translatePath("special://home/userdata/addon_data/plugin.video.jet_guide/cache")
                            if xbmcvfs.exists(cache_path):
                                for root, dirs, files in os.walk(cache_path):
                                    for f in files:
                                        try:
                                            os.remove(os.path.join(root, f))
                                        except Exception:
                                            pass
                                xbmcgui.Dialog().ok("Clear Cache", "Cache cleared.")
                            else:
                                xbmcgui.Dialog().ok("Clear Cache", "No cache found.")
                            return
                        if action == "manage_widgets":
                            self.onClick(120)
                            return
                        if action == "whats_new":
                            try:
                                from changelog_window import show_changelog_from_tools
                                show_changelog_from_tools(ADDON.getAddonInfo('path'))
                            except Exception as e:
                                xbmcgui.Dialog().ok("Error", f"Failed to show changelog: {str(e)}")
                            return
                        show_tools()
                        return
            except Exception as e:
                xbmcgui.Dialog().ok("Tools", f"Error opening tools action: {str(e)}")
                return

        if control_id == 364:
            try:
                rp_list = self.getControl(364)
                if rp_list:
                    item = rp_list.getSelectedItem()
                    if item:
                        choice_str = item.getProperty("record_players_choice")
                        if choice_str != "":
                            handle_stream_action_choice(int(choice_str))
                            return
            except Exception as e:
                xbmcgui.Dialog().ok("Record / Players", f"Error opening action: {str(e)}")
                return

        if control_id == 361:
            try:
                events_list = self.getControl(361)
                if events_list:
                    item = events_list.getSelectedItem()
                    if item and item.getProperty("game_team1"):
                        self.play_sports_event_from_preview(item)
                        return
            except Exception as e:
                xbmcgui.Dialog().ok("Sports Events", f"Error opening game details: {str(e)}")
                return

        if control_id == 360:
            try:
                stats_list = self.getControl(360)
                if stats_list:
                    item = stats_list.getSelectedItem()
                    if item:
                        sport_code = item.getProperty("sport_code")
                        sport_name = item.getLabel()
                        if sport_code:
                            progress = xbmcgui.DialogProgress()
                            progress.create(f"Loading {sport_name} Stats", f"Loading {sport_name} statistics...")
                            progress.update(0)
                            try:
                                progress.update(30, f"Fetching {sport_name} team data...")
                                progress.update(70, f"Preparing {sport_name} interface...")
                                show_sports_grid(sport_code)
                                progress.update(100, f"{sport_name} stats loaded successfully!")
                            finally:
                                progress.close()
                            return
            except Exception as e:
                xbmcgui.Dialog().ok("Sports Error", f"Error opening league stats: {str(e)}")
                return

        if control_id == 150:  # Tools button
            show_tools()
            return

        if control_id in (310, 311):  # Widget bar clicked (support both rows)
            widget_list = self.widget_list if control_id == 310 else self.widget_list2
            if widget_list:
                item = widget_list.getSelectedItem()
                if item:
                    widget_link = item.getProperty("widget_link")
                    
                    # Check if this is a Live Scores widget item (has game properties)
                    game_team1 = item.getProperty("game_team1")
                    if game_team1:
                        # This is a Live Scores item - show game details dialog
                        self.show_live_scores_details(item)
                        return
                    
                    if widget_link:
                        xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
                        xbmc.executebuiltin('Dialog.Close(busydialog)')
                        xbmc.executebuiltin('Dialog.Close(all,true)')
                        xbmc.sleep(250)
                        if widget_link.startswith("plugin://"):
                            xbmc.executebuiltin(f'RunPlugin({widget_link})')
                            xbmc.sleep(100)
                            xbmc.executebuiltin(f'ActivateWindow(Videos,"{widget_link}",return)')
                        elif widget_link.startswith("http") or widget_link.endswith(".m3u8"):
                            xbmc.executebuiltin(f'PlayMedia("{widget_link}")')
                        else:
                            xbmc.executebuiltin(f'ActivateWindow(Videos,"{widget_link}",return)')
                        return
                

        if control_id == 120:  # Manage Widgets button
            # First check if widgets are enabled
            widgets_enabled = ADDON.getSettingBool("widgets_enabled")
            if not widgets_enabled:
                xbmcgui.Dialog().ok("Widgets Disabled", "Widgets are currently disabled.\nEnable them in Settings > Widgets to use this feature.")
                return
            
            action = xbmcgui.Dialog().select(
                "Manage Widgets",
                [
                    "Refresh Widgets",
                    "Create Widget",
                    "Create Widget (All Items from Addon Page)",
                    "Delete Widget",
                    "Change Layout Style",
                    "Clear Scores Cache",
                    "Cancel"
                ]
            )

            if action == 0:
                debug_log("DEBUG: Manual widget refresh triggered", level=xbmc.LOGINFO)
                current_addon = xbmcaddon.Addon()
                live_scores_enabled = current_addon.getSetting('live_scores_enabled')
                if live_scores_enabled == 'true':
                    self.load_live_scores_widget(self.widget_list2, force_refresh=True)
                    xbmcgui.Dialog().notification("Live Scores", "Scores refreshed from API", xbmcgui.NOTIFICATION_INFO, 2000)
                self.load_pinned_widgets(self.widget_list)
                self.load_pinned_widgets(self.widget_list2, widget_row=2)
                xbmcgui.Dialog().notification("Widgets", "Widgets refreshed", xbmcgui.NOTIFICATION_INFO, 2000)
                return
            elif action == 1:
                selected_addon = browse_installed_video_addons()
                if not selected_addon:
                    return
                row_choice = xbmcgui.Dialog().select("Select Widget Row", ["First Row", "Second Row"])
                if row_choice == -1:
                    return
                widget_row = row_choice + 1

                def pin_flow(addon_id, start_path=None):
                    if not start_path:
                        plugin_url = f"plugin://{addon_id}/"
                    else:
                        plugin_url = start_path
                    params = {
                        "jsonrpc": "2.0",
                        "method": "Files.GetDirectory",
                        "id": 1,
                        "params": {
                            "directory": plugin_url,
                            "media": "files"
                        }
                    }
                    response = xbmc.executeJSONRPC(json.dumps(params))
                    data = json.loads(response)
                    items = data.get("result", {}).get("files", [])
                    if not items:
                        if start_path:
                            plugin_url = start_path
                        else:
                            plugin_url = f"plugin://{addon_id}/"
                        if xbmcgui.Dialog().yesno("No Items Found", "No items found in this folder.\nWould you like to pin the root plugin as a widget?"):
                            title = xbmcgui.Dialog().input("Enter a name for this widget", defaultt=plugin_url)
                            if title:
                                save_widget_link({"title": title, "link": plugin_url, "row": widget_row})
                                xbmcgui.Dialog().ok("Widget Saved", f"Widget pinned:\n{title}")
                                self.load_pinned_widgets(self.widget_list if widget_row == 1 else self.widget_list2, widget_row=widget_row)
                        return
                    display_names = []
                    item_urls = []
                    is_folder = []
                    for item in items:
                        display_names.append(item.get("label", item.get("file", "")))
                        item_urls.append(item.get("file", ""))
                        is_folder.append(item.get("filetype", "") == "directory")
                    while True:
                        choice = xbmcgui.Dialog().select("Browse Addon", display_names + ["[Pin this folder as Widget]", "[Go Back]"])
                        if choice < 0 or choice == len(display_names) + 1:
                            return
                        if choice == len(display_names):
                            title = xbmcgui.Dialog().input("Enter a name for this widget", defaultt=plugin_url)
                            if title:
                                save_widget_link({"title": title, "link": plugin_url, "row": widget_row})
                                xbmcgui.Dialog().ok("Widget Saved", f"Widget pinned:\n{title}")
                                self.load_pinned_widgets(self.widget_list if widget_row == 1 else self.widget_list2, widget_row=widget_row)
                            continue
                        if is_folder[choice]:
                            pin_flow(addon_id, item_urls[choice])
                            return
                        else:
                            title = xbmcgui.Dialog().input("Enter a name for this widget", defaultt=display_names[choice])
                            if title:
                                save_widget_link({"title": title, "link": item_urls[choice], "row": widget_row})
                                xbmcgui.Dialog().ok("Widget Saved", f"Widget pinned:\n{title}")
                                self.load_pinned_widgets(self.widget_list if widget_row == 1 else self.widget_list2, widget_row=widget_row)
                            return
                pin_flow(selected_addon)
                return
            elif action == 2:
                selected_addon = browse_installed_video_addons()
                if not selected_addon:
                    return
                row_choice = xbmcgui.Dialog().select("Select Widget Row", ["First Row", "Second Row"])
                if row_choice == -1:
                    return
                widget_row = row_choice + 1

                def browse_and_select_folder(addon_id, start_path=None):
                    if not start_path:
                        plugin_url = f"plugin://{addon_id}/"
                    else:
                        plugin_url = start_path
                    params = {
                        "jsonrpc": "2.0",
                        "method": "Files.GetDirectory",
                        "id": 1,
                        "params": {
                            "directory": plugin_url,
                            "media": "files"
                        }
                    }
                    response = xbmc.executeJSONRPC(json.dumps(params))
                    data = json.loads(response)
                    items = data.get("result", {}).get("files", [])
                    if not items:
                        xbmcgui.Dialog().ok("No Items", "No items found in this folder.")
                        return None, None
                    display_names = []
                    item_urls = []
                    is_folder = []
                    for item in items:
                        display_names.append(item.get("label", item.get("file", "")))
                        item_urls.append(item.get("file", ""))
                        is_folder.append(item.get("filetype", "") == "directory")
                    while True:
                        choice = xbmcgui.Dialog().select("Select Addon Page", display_names + ["[Select this folder]", "[Go Back]"])
                        if choice < 0 or choice == len(display_names) + 1:
                            return None, None
                        if choice == len(display_names):
                            return plugin_url, items
                        if is_folder[choice]:
                            return browse_and_select_folder(addon_id, item_urls[choice])
                        else:
                            xbmcgui.Dialog().ok("Select a folder", "Please select a folder, not a file.")
                folder_url, folder_items = browse_and_select_folder(selected_addon)
                if folder_url and folder_items:
                    all_items = [item for item in folder_items if item.get("file", "")]
                    if not all_items:
                        xbmcgui.Dialog().ok("No Items", "No items found in this folder.")
                        return
                    title = xbmcgui.Dialog().input("Enter a name for this widget", defaultt=folder_url)
                    if title:
                        widget_data = {
                            "title": title,
                            "source_folder": folder_url,
                            "row": widget_row
                        }
                        save_widget_link(widget_data)
                        xbmcgui.Dialog().ok("Widget Saved", f"Widget created with {len(all_items)} items:\n{title}")
                        self.load_pinned_widgets(self.widget_list if widget_row == 1 else self.widget_list2, widget_row=widget_row)
                return
            elif action == 3:
                WIDGETS_PATH = xbmcvfs.translatePath("special://userdata/addon_data/plugin.video.jet_guide/widgets.json")
                try:
                    with xbmcvfs.File(WIDGETS_PATH, "r") as f:
                        links = json.loads(f.read())
                    if not links:
                        xbmcgui.Dialog().ok("Delete Widget", "No widgets to delete.")
                        return
                    display_names = [w.get("title") or w.get("link") for w in links]
                    idx = xbmcgui.Dialog().select("Delete Widget", display_names + ["Cancel"])
                    if idx < 0 or idx >= len(links):
                        return
                    widget = links[idx]
                    widget_link = widget.get("link") or widget.get("source_folder")
                    if widget_link:
                        self.remove_widget_by_link(widget_link)
                except Exception as e:
                    xbmcgui.Dialog().notification("Error", f"Failed to delete widget: {e}", xbmcgui.NOTIFICATION_ERROR, 3000)
                return
            elif action == 4:
                xbmcgui.Dialog().ok("Change Layout Style", "Layout style selection not yet implemented.")
                return
            elif action == 5:
                if clear_scores_cache():
                    xbmcgui.Dialog().ok("Cache Cleared", "Live Scores cache has been cleared.\n\nNext widget load will fetch fresh data from the API.")
                else:
                    xbmcgui.Dialog().ok("Cache Error", "Failed to clear cache or no cache exists.")
                return
            elif action == 6:
                return

        if handle_links_and_widgets(
            self,
            control_id,
            RemindersManager,
            browse_installed_video_addons,
            browse_addon_plugin_folders,
            import_channels_from_favourites,
            play_saved_jet_guide_link,
        ):
            return

        if handle_window_click_actions(self, control_id):
            return

        if handle_stream_actions(control_id):
            return

        try:

            # Legacy commented-out LiveScore alerts block removed during refactor.

            if handle_basic_button_actions(
                self,
                control_id,
                parse_m3u8,
                parse_xmltv,
                M3U8_PATH,
                XMLTV_PATH,
                ADDON,
                favorites_manager,
                SearchWindow,
                show_sports_guide,
                show_sports_grid,
            ):
                return
                
            elif control_id in self.buttons:
                # Skip notifications for buttons 8 and 9
                if control_id not in [108, 109]:
                    xbmcgui.Dialog().notification('Button Click', f'Clicked {self.buttons[control_id]}')

        except Exception as e:
            xbmcgui.Dialog().ok("Error", f"Error handling click: {str(e)}")

    def play_last_watched(self, channel_id):
        return _play_last_watched(self, channel_id, parse_m3u8, M3U8_PATH)

    def onAction(self, action):
        try:
            action_id = action.getId()
            self.update_preview_context()

            # Only stop the currently focused preview list's auto-scroll on interaction
            if action_id in [1, 2, 3, 4, 7, 10, 92, 117]:
                try:
                    focus_id = self.getFocusId()
                    if focus_id in (360, 361, 362, 363, 364, 365, 370):
                        self._cancel_preview_scroll(focus_id)
                        self._schedule_preview_resume(focus_id)
                    if focus_id == 360:
                        self.update_sports_stats_news()
                except Exception:
                    pass

            if self.is_closing:
                return

            if self.is_android and self.android_optimize_mode:
                now_ms = int(time.time() * 1000)
                if now_ms - self.last_action_ts < self.android_input_debounce_ms:
                    return
                self.last_action_ts = now_ms

            # debug_log(f"Action received: {action_id}", level=xbmc.LOGDEBUG)
            if action_id in [9, 10, 92, 117, 216, 247, 257, 275, 61467, 61448]:
                self.is_closing = True
                self.ticker_running = False
                self._stop_preview_auto_scroll()
                self._stop_sport_news_rotation()
                if self.ticker_thread and self.ticker_thread.is_alive():
                    self.ticker_thread.join(timeout=1.0)
                # Stop slideshow
                try:
                    self.slideshow.stop()
                except Exception:
                    pass
                self.close()
                # debug_log("Closed TenButtonsWindow", level=xbmc.LOGDEBUG)
        except Exception as e:
            # debug_log(f"Error handling action: {str(e)}", level=xbmc.LOGERROR)
            xbmcgui.Dialog().ok("Error", f"Error handling action: {str(e)}")

    def onFocus(self, control_id):
        try:
            self.update_preview_context(control_id)
            # Restart auto-scroll for preview lists when they gain focus
            if control_id in (360, 361, 362, 363, 364, 365, 370):
                self._cancel_preview_resume(control_id)
                try:
                    if ADDON.getSettingBool("preview_auto_scroll"):
                        self._schedule_preview_scroll(control_id)
                except Exception:
                    pass
            if control_id == 360:
                self.update_sports_stats_news()
        except Exception:
            pass

    def fetch_games(self):
        return _fetch_games(self, addon__id)

    def load_sports_stats_preview(self):
        """Populate Sports Stats preview league selector list (id 360)."""
        try:
            stats_list = self.getControl(360)
            if not stats_list:
                return
            stats_list.reset()
            for league_name, league_code in self.sports_stats_leagues:
                item = xbmcgui.ListItem(label=league_name)
                item.setProperty("sport_code", league_code)
                stats_list.addItem(item)
            # Pre-fetch news for the initially selected league
            self.update_sports_stats_news()
        except Exception as e:
            debug_log(f"Error loading sports stats preview list: {str(e)}", level=xbmc.LOGERROR)

    def update_sports_stats_news(self):
        """Fetch and display ESPN news for the currently selected Sports Stats league."""
        try:
            stats_list = self.getControl(360)
            if not stats_list:
                return
            item = stats_list.getSelectedItem()
            if not item:
                return
            sport_code = item.getProperty("sport_code")
            self._stop_sport_news_rotation()
            self.sport_news_items = apply_news_to_window(self, sport_code, index=0)
            self.sport_news_index = 0
            if self.sport_news_items:
                self._start_sport_news_rotation()
        except Exception as e:
            debug_log(f"Error updating sports stats news: {str(e)}", level=xbmc.LOGERROR)

    def _apply_sport_news_at_index(self, index):
        """Set window properties to the news item at the given index."""
        if not self.sport_news_items:
            for key in ('SportNewsHeadline', 'SportNewsDescription', 'SportNewsImage', 'SportNewsPublished'):
                self.setProperty(key, '')
            return
        item = self.sport_news_items[index % len(self.sport_news_items)]
        self.setProperty('SportNewsHeadline', item.get('headline', ''))
        self.setProperty('SportNewsDescription', item.get('description', ''))
        self.setProperty('SportNewsImage', item.get('image', ''))
        self.setProperty('SportNewsPublished', item.get('published', ''))

    def _start_sport_news_rotation(self):
        """Start the timer that rotates through multiple headlines."""
        if not self.sport_news_items or len(self.sport_news_items) <= 1 or self.is_closing:
            return
        self._stop_sport_news_rotation()
        self.sport_news_timer = threading.Timer(
            self.sport_news_rotation_interval_ms / 1000.0,
            self._rotate_sport_news
        )
        self.sport_news_timer.daemon = True
        self.sport_news_timer.start()

    def _rotate_sport_news(self):
        """Advance to the next headline and restart the rotation timer."""
        if self.is_closing or not self.sport_news_items:
            return
        self.sport_news_index = (self.sport_news_index + 1) % len(self.sport_news_items)
        self._apply_sport_news_at_index(self.sport_news_index)
        self._start_sport_news_rotation()

    def _stop_sport_news_rotation(self):
        """Cancel any active news rotation timer."""
        timer = getattr(self, 'sport_news_timer', None)
        if timer:
            try:
                timer.cancel()
            except Exception:
                pass
        self.sport_news_timer = None

    def load_sports_events_preview(self):
        """Populate Sports Events preview game selector list (id 361)."""
        try:
            events_list = self.getControl(361)
            if not events_list:
                return
            events_list.reset()

            current_addon = xbmcaddon.Addon()
            enabled_sports = []
            sport_settings = {
                'ls_nfl': 'NFL',
                'ls_mlb': 'MLB',
                'ls_ncaabase': 'NCAABASE',
                'ls_nba': 'NBA',
                'ls_nba_summer_league': 'NBA Summer League',
                'ls_nba_utah_summer': 'NBA Utah Summer League',
                'ls_nba_california_classic': 'NBA California Classic',
                'ls_ncaab': 'NCAAB',
                'ls_wnba': 'WNBA',
                'ls_nhl': 'NHL',
                'ls_mls': 'MLS',
                'ls_cfl': 'CFL',
                'ls_la_liga': 'La Liga',
                'ls_bundesliga': 'Bundesliga',
                'ls_serie_a': 'Serie A',
                'ls_ligue_1': 'Ligue 1',
                'ls_champions_league': 'Champions League',
            }
            for setting_id, sport_name in sport_settings.items():
                try:
                    if current_addon.getSettingBool(setting_id):
                        enabled_sports.append(sport_name)
                except Exception:
                    pass

            if not enabled_sports:
                sport_preference = current_addon.getSetting('live_scores_sport') or 'All'
                if sport_preference != 'All':
                    enabled_sports = [sport_preference]
                else:
                    enabled_sports = ['NFL', 'MLB', 'NCAABASE', 'NBA', 'NBA Summer League', 'NBA Utah Summer League', 'NBA California Classic', 'NCAAB', 'WNBA', 'NHL', 'MLS', 'CFL', 'La Liga', 'Bundesliga', 'Serie A', 'Ligue 1', 'Champions League']

            games = fetch_all_scores_with_cache(enabled_sports, force_refresh=False)
            if not games:
                empty_item = xbmcgui.ListItem(label="No games")
                events_list.addItem(empty_item)
                return

            for game in games[:50]:
                team1 = game.get('team1', '')
                team2 = game.get('team2', '')
                score = game.get('score', '0 - 0')
                status = game.get('full_status', game.get('status', 'Scheduled'))
                sport = game.get('sport', '')
                label = f"{team1} vs {team2}"
                item = xbmcgui.ListItem(label=label)
                item.setProperty("game_team1", team1)
                item.setProperty("game_team2", team2)
                item.setProperty("game_score", score)
                item.setProperty("game_status", status)
                item.setProperty("game_period", str(game.get('period', '')))
                item.setProperty("game_clock", str(game.get('clock', '')))
                item.setProperty("game_venue", game.get('venue', ''))
                item.setProperty("game_sport", sport)
                item.setProperty("game_event_id", game.get('event_id', ''))
                item.setProperty("game_team1_stats", json.dumps(game.get('team1_stats', {})))
                item.setProperty("game_team2_stats", json.dumps(game.get('team2_stats', {})))
                item.setProperty("game_team1_leaders", json.dumps(game.get('team1_leaders', {})))
                item.setProperty("game_team2_leaders", json.dumps(game.get('team2_leaders', {})))
                item.setProperty("game_meta", f"{sport} | {score} |{status} | {game.get('broadcast', 'TBD')}")
                events_list.addItem(item)
        except Exception as e:
            debug_log(f"Error loading sports events preview list: {str(e)}", level=xbmc.LOGERROR)

    def play_sports_event_from_preview(self, item):
        """Open stream sources for selected Sports Events preview game."""
        team1_name = (item.getProperty("game_team1") or "").lower().strip()
        team2_name = (item.getProperty("game_team2") or "").lower().strip()
        sport = (item.getProperty("game_sport") or "").lower().strip()
        if not team1_name or not team2_name:
            return

        def _clean_team(name):
            return re.sub(r"\s*\(.*?\)", "", name).strip().lower()

        team1_name = _clean_team(team1_name)
        team2_name = _clean_team(team2_name)

        def _fuzzy_match(title, t1, t2):
            t = re.sub(r'\[/?[a-z]+.*?\]', '', title.lower())
            t = re.sub(r'[^a-z0-9 ]', '', t)
            t1c = re.sub(r'[^a-z0-9 ]', '', t1)
            t2c = re.sub(r'[^a-z0-9 ]', '', t2)
            return (t1c in t or (t1c.split() and t1c.split()[-1] in t)) and (t2c in t or (t2c.split() and t2c.split()[-1] in t))

        if "wnba" in sport:
            url = get_sport_url("nba")
        elif "ncaabase" in sport:
            url = get_sport_url("college_baseball")
        elif sport in ["mls", "premier league", "la liga", "bundesliga", "serie a", "ligue 1", "champions league"]:
            url = get_sport_url("soccer")
        else:
            sport_key = sport.replace(" ", "")
            url = get_sport_url(sport_key)

        progress = xbmcgui.DialogProgress()
        progress.create('Searching Streams', f'Searching for {team1_name} vs {team2_name}...')
        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)}")
            return

        stream_links = []
        stream_names = []
        items = data.get('items', [])
        if not items:
            progress.close()
            xbmcgui.Dialog().ok("No Streams", "No games found in data source")
            return

        for idx, game in enumerate(items):
            if progress.iscanceled():
                progress.close()
                return
            progress.update(25 + int((idx / float(max(1, len(items)))) * 70), f'Checking game {idx + 1} of {len(items)}...')
            if game.get('type') == 'item' and game.get('title') and _fuzzy_match(game.get('title', ''), team1_name, team2_name):
                for i, link in enumerate(game.get('link', [])):
                    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:
            xbmcgui.Dialog().ok("No Streams", "No streams found for this game")
            return

        choice = xbmcgui.Dialog().select('Choose Stream', stream_names)
        if choice is not None and choice >= 0:
            selected_url = stream_links[choice]
            game_title = item.getLabel() or f"{team1_name} vs {team2_name}"
            list_item = xbmcgui.ListItem(game_title)
            list_item.setInfo('video', {'title': game_title})
            list_item.setProperty('IsPlayable', 'true')
            try:
                from youtube_guide import cancel_youtube_playback
                cancel_youtube_playback()
            except Exception:
                pass
            track(event="play", channel_name=game_title, url=selected_url, source="tenbuttons")
            xbmc.Player().play(selected_url, list_item)

    def load_tools_preview(self):
        """Populate Tools preview selector list (id 363)."""
        try:
            tools_list = self.getControl(363)
            if not tools_list:
                return
            tools_list.reset()
            for label, action in self.tools_preview_items:
                item = xbmcgui.ListItem(label=label)
                item.setProperty("tools_action", action)
                tools_list.addItem(item)
        except Exception as e:
            debug_log(f"Error loading tools preview list: {str(e)}", level=xbmc.LOGERROR)

    def load_search_quick_filters(self):
        """Populate Search quick filter preview list (id 370)."""
        try:
            qf_list = self.getControl(370)
            if not qf_list:
                return
            qf_list.reset()
            # Mirrors QUICK_FILTERS in searchwindow.py.
            for key, label in (
                ("all", "All"),
                ("live", "Live"),
                ("sports", "Sports"),
                ("movies", "Movies"),
                ("kids", "Kids"),
                ("news", "News"),
            ):
                item = xbmcgui.ListItem(label=label)
                item.setProperty("quick_filter", key)
                qf_list.addItem(item)
        except Exception as e:
            debug_log(f"Error loading search quick filter list: {str(e)}", level=xbmc.LOGERROR)

    def load_record_players_preview(self):
        """Populate Record / Players preview selector list (id 364)."""
        try:
            rp_list = self.getControl(364)
            if not rp_list:
                return
            rp_list.reset()
            for label, choice in self.record_players_preview_items:
                item = xbmcgui.ListItem(label=label)
                item.setProperty("record_players_choice", str(choice))
                rp_list.addItem(item)
        except Exception as e:
            debug_log(f"Error loading record/players preview list: {str(e)}", level=xbmc.LOGERROR)

    def update_preview_context(self, focus_id=None):
        """Persist right-panel context so mouse movement doesn't blank panel."""
        try:
            if focus_id is None:
                focus_id = self.getFocusId()
        except Exception:
            focus_id = None

        preview_map = {
            101: "101",
            102: "102",
            106: "106",
            150: "150",
            104: "104",
            105: "105",
            199: "199",
            108: "108",
            114: "114",
            362: "101",
            365: "105",
            361: "102",
            360: "106",
            363: "150",
            364: "199",
            370: "104",
        }
        if focus_id in preview_map:
            self.setProperty("HomePreviewContext", preview_map[focus_id])

    def _schedule_preview_resume(self, list_id):
        """Schedule auto-scroll to resume after idle period on the focused list."""
        if self.is_closing:
            return
        try:
            if not ADDON.getSettingBool("preview_auto_scroll"):
                return
        except Exception:
            pass
        self._cancel_preview_resume(list_id)
        timer = threading.Timer(
            self.preview_resume_delay_ms / 1000.0,
            self._preview_resume_tick,
            args=[list_id]
        )
        timer.daemon = True
        self.preview_resume_timers[list_id] = timer
        timer.start()

    def _preview_resume_tick(self, list_id):
        """Resume auto-scroll for a list after idle timeout."""
        if self.is_closing:
            return
        try:
            if not ADDON.getSettingBool("preview_auto_scroll"):
                return
        except Exception:
            pass
        try:
            focus_id = self.getFocusId()
            if focus_id == list_id:
                self._schedule_preview_scroll(list_id)
        except Exception:
            pass

    def _cancel_preview_resume(self, list_id):
        """Cancel the resume timer for a specific list id."""
        timer = self.preview_resume_timers.pop(list_id, None)
        if timer:
            try:
                timer.cancel()
            except Exception:
                pass

    def _start_preview_auto_scroll(self):
        """Start auto-scroll timers for all preview-panel fixedlists with staggered delays."""
        try:
            if not ADDON.getSettingBool("preview_auto_scroll"):
                debug_log("DEBUG: Preview auto-scroll disabled in settings", level=xbmc.LOGINFO)
                return
        except Exception:
            pass
        preview_list_ids = [360, 361, 362, 363, 364, 365, 370]
        for idx, list_id in enumerate(preview_list_ids):
            # Stagger initial start times so lists don't all scroll at once
            initial_delay = self.preview_scroll_delay_ms + (idx * 800)
            self._schedule_preview_scroll(list_id, initial_delay=initial_delay)

    def _schedule_preview_scroll(self, list_id, initial_delay=None):
        """Schedule a single auto-scroll event for a fixedlist after the delay."""
        if self.is_closing:
            return
        # Cancel any existing timer for this list
        self._cancel_preview_scroll(list_id)
        delay = (initial_delay if initial_delay is not None
                 else self.preview_scroll_interval_ms)
        timer = threading.Timer(
            delay / 1000.0,
            self._preview_scroll_tick,
            args=[list_id]
        )
        timer.daemon = True
        self.preview_scroll_timers[list_id] = timer
        timer.start()

    def _preview_scroll_tick(self, list_id):
        """Advance the focused item in a preview fixedlist and reschedule."""
        if self.is_closing:
            return
        try:
            control = self.getControl(list_id)
            if control:
                current = control.getSelectedPosition()
                total = control.size()
                if total > 1:
                    next_pos = (current + 1) % total
                    control.selectItem(next_pos)
        except Exception:
            pass
        # Reschedule next tick
        self._schedule_preview_scroll(list_id)

    def _cancel_preview_scroll(self, list_id):
        """Cancel the auto-scroll timer for a specific list id."""
        timer = self.preview_scroll_timers.pop(list_id, None)
        if timer:
            try:
                timer.cancel()
            except Exception:
                pass

    def _stop_preview_auto_scroll(self):
        """Stop all preview-panel auto-scroll timers."""
        for list_id in list(self.preview_scroll_timers.keys()):
            self._cancel_preview_scroll(list_id)
        for list_id in list(self.preview_resume_timers.keys()):
            self._cancel_preview_resume(list_id)

    def get_game_status(self, gamecard):
        return _get_game_status(self, gamecard)

class ButtonState:
    window = None

def show_ten_buttons():
    """Show the ten buttons window"""
    try:
        global LAST_TENBUTTONS_OPEN_TS

        # Guard against rapid re-entry loops from repeated navigation actions
        now_ts = time.monotonic()
        if now_ts - LAST_TENBUTTONS_OPEN_TS < 0.8:
            return
        LAST_TENBUTTONS_OPEN_TS = now_ts

        # Close only busy dialogs, not every dialog/window
        xbmc.executebuiltin('Dialog.Close(busydialognocancel)')
        xbmc.executebuiltin('Dialog.Close(busydialog)')
        xbmc.sleep(100)
        
        addon_path = ADDON.getAddonInfo('path')
        
        # Check for changelog on startup (after update)
        # Removed redundant changelog check to prevent infinite loop
        # debug_log removed: no changelog check or exception here
        
        ButtonState.window = TenButtonsWindow('ten_buttons.xml', addon_path)
        ButtonState.window.doModal()
        del ButtonState.window
        ButtonState.window = None
    except Exception as e:
        xbmcgui.Dialog().ok("Error", f"Failed to show window: {str(e)}")

# Utility browsing/import helpers are provided by tools.py and imported at module top.
