# wcs/ai/DialogMyTV.py
import xbmc
import xbmcgui
import xbmcaddon
import xbmcvfs
import os
from urllib.parse import quote_plus
from wcs.ai.DialogAIChatRecommendation import AIChatRecommendationDialog, AIRecommendationPromptBuilder
from wcs.ai.MyTVChannelManager import MyTVChannelManager
from wcs import user_data, utils
from wcs.caching import get_cache_manager

# PIL import with fallback
try:
    from PIL import Image, ImageFilter, ImageDraw
    PIL_AVAILABLE = True
except ImportError:
    PIL_AVAILABLE = False
    xbmc.log("[MyTV] PIL not available - collage/effects disabled", xbmc.LOGWARNING)

# Recently played cache (lightweight, OK to keep in memory)
_recently_played_cache = None


class MyTVDialog(AIChatRecommendationDialog):
    # Background mode constants
    BG_STATIC = 0
    BG_FANART = 1
    BG_STILL = 2
    BG_COLLAGE_POSTERS = 3
    BG_COLLAGE_STILLS = 4
    BG_COLLAGE_MIX = 5
    
    def __init__(self, xml_filename, addon_path, *args, **kwargs):
        super(MyTVDialog, self).__init__(xml_filename, addon_path, *args, **kwargs)
        self._nav_section_id = 'my_tv'
        self.channel_manager = MyTVChannelManager(self.addon)
        self.current_channel_index = 0
        
        # Use centralized cache manager
        self._cache = get_cache_manager()
        
        # Run cache maintenance on startup (background, throttled)
        self._cache.maintenance()

    def onInit(self):
        self._reset_state()
        self.setProperty('WCS.AIChat.Title', 'MyTV - televize budoucnosti')
        self.setProperty('WCS.AIChat.Status', 'Pripraven')
        
        # Always start with the first channel
        self.current_channel_index = 0
        
        # Set background mode and animation properties
        self._setup_background_properties()
        
        # Load View
        self.refresh_channels_view(set_focus=True)
        
        # Trigger initial background generation (for blur/glow/collage modes)
        self._update_background()
        
        # Ensure sidebar is hidden initially, handled by base class logic or show_nav_sidebar arg
        if self._show_nav_on_init:
            self._show_nav_sidebar(animate=False)
        else:
            self.clearProperty('WCS.AIChat.Visible')

    def refresh_channels_view(self, set_focus=False):
        """Populate the bottom list with channels."""
        channels = self.channel_manager.get_all_channels()
        list_control = self.getControl(2000)
        list_control.reset()
        
        for ch in channels:
            item = xbmcgui.ListItem(label=ch.name)
            # Use channel icon or fallback
            icon = ch.icon if ch.icon else 'special://home/addons/plugin.video.milionar/resources/media/placeholder_tv_card.png'
            item.setArt({'poster': icon})
            item.setProperty('channel_id', ch.id)
            list_control.addItem(item)
            
        # Add "Create New Channel" item
        create_item = xbmcgui.ListItem(label="Vytvořit nový kanál")
        create_item.setArt({'poster': 'special://home/addons/plugin.video.milionar/resources/media/plus_card.png'}) # Fallback to placeholder if not exists
        create_item.setProperty('is_create_button', 'true')
        list_control.addItem(create_item)
            
        # Select first if available (and reset index if out of bounds)
        if self.current_channel_index >= list_control.size():
            self.current_channel_index = 0
            
        if list_control.size() > 0:
            list_control.selectItem(self.current_channel_index)
            self.update_editor_view()
            self.refresh_program_view()
            # Focus on Channel List after 0.5s (only if requested, e.g. on init)
            if set_focus:
                self._delayed_focus(2000, delay=0.5)

    def update_editor_view(self):
        """Update grid 3000 based on selected channel."""
        list_control = self.getControl(2000)
        idx = list_control.getSelectedPosition()
        if idx < 0:
            return
            
        item = list_control.getListItem(idx)
        
        # If "Create New" is selected, clear editor and return
        if item.getProperty('is_create_button') == 'true':
            self.getControl(9100).reset()
            # self.getControl(9000).reset() # Nechame program list byt, at to neblika
            return
            
        channel_id = item.getProperty('channel_id')
        channel = self.channel_manager.get_channel_by_id(channel_id)
        
        grid_control = self.getControl(9100)
        grid_control.reset()
        
        if channel:
            # Add existing series
            for series in channel.series_list:
                s_item = xbmcgui.ListItem(label=series.get('name'))
                poster = series.get('poster_path') or series.get('poster')
                if poster and not poster.startswith('http'):
                     poster = f"https://image.tmdb.org/t/p/w500{poster}"
                elif not poster:
                     poster = 'special://home/addons/plugin.video.milionar/resources/media/placeholder_tv_card.png'
                
                s_item.setArt({'poster': poster})
                s_item.setProperty('series_id', str(series.get('id')))
                s_item.setProperty('channel_id', channel_id)
                grid_control.addItem(s_item)

            # Always add "Add Series" card at the end of the grid
            add_item = xbmcgui.ListItem(label="Pridat serial")
            add_item.setArt({'poster': 'special://home/addons/plugin.video.milionar/resources/media/plus_card.png'})
            add_item.setProperty('is_add_button', 'true')
            add_item.setProperty('channel_id', channel_id)
            grid_control.addItem(add_item)

    def refresh_program_view(self):
        """Debounced trigger - schedules async load after 300ms delay."""
        import threading
        
        # Initialize state if needed
        if not hasattr(self, '_program_load_token'):
            self._program_load_token = 0
        if not hasattr(self, '_program_load_timer'):
            self._program_load_timer = None
        
        # Increment token (cancels any pending/running loads)
        self._program_load_token += 1
        current_token = self._program_load_token
        
        # Get channel ID now (sync, fast)
        list_control = self.getControl(2000)
        idx = list_control.getSelectedPosition()
        if idx < 0:
            return
        
        item = list_control.getListItem(idx)
        channel_id = item.getProperty('channel_id')
        if not channel_id:
            return
        
        # Store for stale play prevention
        self._current_loaded_channel_id = channel_id
        
        # INSTANT LOAD: Try to load from program list cache immediately
        self._instant_load_from_cache(channel_id)
        
        # Cancel existing timer
        if self._program_load_timer:
            self._program_load_timer.cancel()
        
        # Schedule debounced async refresh (300ms delay)
        self._program_load_timer = threading.Timer(
            0.3,
            self._async_program_load,
            args=(current_token, channel_id)
        )
        self._program_load_timer.daemon = True
        self._program_load_timer.start()
    
    def _instant_load_from_cache(self, channel_id):
        """Instantly populate program list from cache (no API calls)."""
        cache_key = f"program_list_{channel_id}"
        
        cached_items = self._cache.get(cache_key)
        if not cached_items:
            return  # No cache, will load async
        
        try:
            program_list = self.getControl(9000)
            program_list.reset()
            
            for item_data in cached_items:
                item = xbmcgui.ListItem(label=item_data.get('label', ''))
                item.setLabel2(item_data.get('label2', ''))
                
                # Set all properties
                for key, value in item_data.get('properties', {}).items():
                    item.setProperty(key, str(value))
                
                # Set all art
                item.setArt(item_data.get('art', {}))
                
                program_list.addItem(item)
            
            xbmc.log(f"[MyTV] Instantly loaded {len(cached_items)} items from cache for channel {channel_id}", xbmc.LOGINFO)
        except Exception as e:
            xbmc.log(f"[MyTV] Error loading from cache: {e}", xbmc.LOGWARNING)
    
    def _async_program_load(self, token, channel_id):
        """Background thread - loads program data without blocking UI."""
        import random
        from wcs.metadata import TMDbClient
        
        # Check if still valid
        if token != self._program_load_token:
            return  # Cancelled - user navigated away
        
        channel = self.channel_manager.get_channel_by_id(channel_id)
        if not channel or not channel.series_list:
            # Update UI with empty message
            self._apply_empty_program()
            return
        
        # Settings
        try:
            upcoming_count_val = self.addon.getSetting('mytv_upcoming_count')
            upcoming_count = int(upcoming_count_val) if upcoming_count_val else 12
        except:
            upcoming_count = 12
        
        block_size_setting = self.addon.getSetting('mytv_block_size') or "2"
        
        # Parse block size
        min_block, max_block = 1, 1
        if '-' in block_size_setting:
            try:
                parts = block_size_setting.split('-')
                min_block, max_block = int(parts[0]), int(parts[1])
            except:
                pass
        else:
            try:
                val = int(block_size_setting)
                min_block = max_block = val
            except:
                pass
        
        # Load recently played cache once (module-level)
        global _recently_played_cache
        if _recently_played_cache is None:
            _recently_played_cache = user_data.load_recently_played(self.addon)
        
        series_candidates = list(channel.series_list)
        random.shuffle(series_candidates)
        
        # Local progress simulation
        simulated_progress = {}
        items_count = 0
        candidate_idx = 0
        max_loops = upcoming_count * 2
        loops = 0
        
        # Reset list at start
        try:
            program_list = self.getControl(9000)
            program_list.reset()
        except:
            return
        
        while items_count < upcoming_count and series_candidates and loops < max_loops:
            loops += 1
            
            # Token check - exit early if cancelled
            if token != self._program_load_token:
                return
            
            series = series_candidates[candidate_idx % len(series_candidates)]
            candidate_idx += 1
            
            series_id = str(series.get('id'))
            series_name = series.get('name')
            
            # Initialize progress
            if series_id not in simulated_progress:
                last_season, last_episode = 1, 0
                for rp in _recently_played_cache or []:
                    if str(rp.get('tmdb_id', '')) == series_id and rp.get('media_type') in ['tv', 'episode', 'series']:
                        if 'season' in rp and 'episode' in rp:
                            last_season = int(rp['season'])
                            last_episode = int(rp['episode'])
                        break
                simulated_progress[series_id] = (last_season, last_episode)
            
            block_size = random.randint(min_block, max_block)
            
            for _ in range(block_size):
                if items_count >= upcoming_count:
                    break
                
                # Token check
                if token != self._program_load_token:
                    return
                
                curr_s, curr_e = simulated_progress[series_id]
                
                # Check cache first (using CacheManager)
                cache_key = f"episode_{series_id}_{curr_s}_{curr_e + 1}"
                next_meta = self._cache.get(cache_key)
                if not next_meta:
                    # Fetch from TMDb (slow)
                    next_meta = TMDbClient.get_next_episode_from_tmdb(series_id, curr_s, curr_e, self.addon)
                    if next_meta:
                        # Cache with 7-day TTL
                        self._cache.set(cache_key, next_meta, ttl_seconds=7 * 24 * 3600)
                
                if next_meta:
                    # Token check before UI update
                    if token != self._program_load_token:
                        return
                    
                    # Add item to UI immediately (progressive loading)
                    self._add_single_program_item(next_meta, series_name, series_id)
                    items_count += 1
                    simulated_progress[series_id] = (next_meta['season'], next_meta['episode'])
                else:
                    break
        
        # After loading program, update background if collage mode
        if items_count > 0:
            self._update_background(channel_id)
        
        # Save program list cache for instant reload
        self._save_program_list_cache(channel_id)
        
        # Flush cache to disk after load completes
        self._cache.flush()
    
    def _save_program_list_cache(self, channel_id):
        """Save current program list to cache for instant reload."""
        try:
            program_list = self.getControl(9000)
            items_data = []
            
            for i in range(program_list.size()):
                item = program_list.getListItem(i)
                item_data = {
                    'label': item.getLabel(),
                    'label2': item.getLabel2(),
                    'properties': {
                        'tmdb_id': item.getProperty('tmdb_id'),
                        'series_name': item.getProperty('series_name'),
                        'season': item.getProperty('season'),
                        'episode': item.getProperty('episode'),
                        'ep_code': item.getProperty('ep_code'),
                        'ep_title': item.getProperty('ep_title'),
                        'plot': item.getProperty('plot'),
                        'plot_short': item.getProperty('plot_short'),
                        'runtime': item.getProperty('runtime'),
                        'air_date': item.getProperty('air_date')
                    },
                    'art': {
                        'thumb': item.getArt('thumb'),
                        'fanart': item.getArt('fanart'),
                        'clearlogo': item.getArt('clearlogo'),
                        'poster': item.getArt('poster')
                    }
                }
                items_data.append(item_data)
            
            cache_key = f"program_list_{channel_id}"
            # Save to CacheManager with 1-day TTL (program lists change frequently)
            self._cache.set(cache_key, items_data, ttl_seconds=24 * 3600)
            xbmc.log(f"[MyTV] Saved {len(items_data)} items to program list cache for channel {channel_id}", xbmc.LOGINFO)
        except Exception as e:
            xbmc.log(f"[MyTV] Error saving program list cache: {e}", xbmc.LOGWARNING)
    
    def _add_single_program_item(self, next_meta, series_name, series_id):
        """Add a single program item to the list (progressive loading)."""
        try:
            program_list = self.getControl(9000)
            
            ep_code = f"S{next_meta['season']:02d}E{next_meta['episode']:02d}"
            full_plot = next_meta.get('plot', '')
            plot_short = full_plot[:77] + '...' if len(full_plot) > 80 else full_plot
            
            item = xbmcgui.ListItem(label=series_name)
            item.setLabel2(str(next_meta.get('runtime', '')))
            
            # Set properties
            item.setProperty('tmdb_id', series_id)
            item.setProperty('series_name', series_name)
            item.setProperty('season', str(next_meta['season']))
            item.setProperty('episode', str(next_meta['episode']))
            item.setProperty('episode_title', next_meta.get('episode_title', ''))
            item.setProperty('plot', full_plot)
            item.setProperty('plot_short', plot_short)
            item.setProperty('rating', str(next_meta.get('rating', '')))
            item.setProperty('ep_code', ep_code)
            item.setProperty('series_logo', next_meta.get('series_logo', ''))
            item.setProperty('poster', next_meta.get('poster', ''))
            item.setProperty('fanart', next_meta.get('fanart', ''))
            item.setProperty('year', str(next_meta.get('year', '')))
            item.setProperty('genre', next_meta.get('genre', ''))
            
            # Art
            thumb = next_meta.get('episode_thumb') or next_meta.get('poster') or ''
            item.setArt({
                'thumb': thumb,
                'icon': next_meta.get('poster', ''),
                'poster': next_meta.get('poster', ''),
                'fanart': next_meta.get('fanart', '')
            })
            
            # InfoTag
            info_tag = {
                'title': next_meta.get('episode_title', ''),
                'tvshowtitle': series_name,
                'plot': full_plot,
                'season': next_meta['season'],
                'episode': next_meta['episode'],
                'mediatype': 'episode'
            }
            try:
                dur_min = int(str(next_meta.get('runtime', '0')).replace(' min', '').strip() or '0')
                info_tag['duration'] = dur_min * 60
            except:
                pass
            
            utils.set_video_info_tag(item, info_tag)
            program_list.addItem(item)
        except Exception as e:
            xbmc.log(f"[MyTV] Error adding program item: {e}", xbmc.LOGERROR)
    
    def _apply_empty_program(self):
        """Apply empty state to program list."""
        try:
            program_list = self.getControl(9000)
            program_list.reset()
            item = xbmcgui.ListItem(label="Zadny program. Pridejte serialy.")
            program_list.addItem(item)
        except:
            pass
    
    def _apply_program_items(self, items_data):
        """Apply loaded program items to UI. Called from background thread."""
        try:
            program_list = self.getControl(9000)
            program_list.reset()
            
            for data in items_data:
                item = xbmcgui.ListItem(label=data['label'])
                item.setLabel2(data['label2'])
                
                # Set all properties
                for key in ['tmdb_id', 'series_name', 'season', 'episode', 'episode_title',
                            'plot', 'plot_short', 'rating', 'ep_code', 'series_logo',
                            'poster', 'fanart', 'year', 'genre']:
                    item.setProperty(key, data.get(key, ''))
                
                # Art
                item.setArt({
                    'thumb': data.get('thumb', ''),
                    'icon': data.get('poster', ''),
                    'poster': data.get('poster', ''),
                    'fanart': data.get('fanart', '')
                })
                
                # InfoTag
                info_tag = {
                    'title': data.get('episode_title', ''),
                    'tvshowtitle': data.get('series_name', ''),
                    'plot': data.get('plot', ''),
                    'season': int(data.get('season', 1)),
                    'episode': int(data.get('episode', 1)),
                    'mediatype': 'episode'
                }
                try:
                    runtime = data.get('runtime', 0)
                    if isinstance(runtime, str):
                        runtime = int(runtime.replace(' min', '').strip() or '0')
                    info_tag['duration'] = runtime * 60
                except:
                    pass
                
                utils.set_video_info_tag(item, info_tag)
                program_list.addItem(item)
        except Exception as e:
            xbmc.log(f"[MyTV] Error applying program items: {e}", xbmc.LOGERROR)

    def onAction(self, action):
        action_id = action.getId()
        focused_id = self.getFocusId()
        
        # Handle Add Series Dialog ESC/Back
        if self.getProperty('WCS.MyTV.AddDialog.Visible'):
            if action_id in [10, 92]:  # ACTION_PREVIOUS_MENU, ACTION_NAV_BACK
                if self.getProperty('WCS.MyTV.AddDialog.ListingMode'):
                    # In listing mode - go back to action selection
                    self.clearProperty('WCS.MyTV.AddDialog.ListingMode')
                    self.clearProperty('WCS.MyTV.AddDialog.ListingTitle')
                    self.setFocusId(9201)
                else:
                    # In action selection - close dialog
                    self._close_add_series_dialog()
                return
        
        # Get last known focus (before this action)
        last_focus = getattr(self, '_last_known_focus', None)
        
        # Handle Channel List (2000) actions locally - DO NOT pass to base class!
        # Base class has its own sidebar logic for 2000 that conflicts with MyTV
        if focused_id == 2000:
            # Get current position for tracking
            try:
                list_control = self.getControl(2000)
                current_pos = list_control.getSelectedPosition()
            except:
                current_pos = 0
            
            if action_id in [1, 2]:  # LEFT/RIGHT
                # Check if LEFT on first position - should open nav sidebar
                # Use _last_list_position (like base class) to check PREVIOUS position
                last_pos = getattr(self, '_last_list_position', 0)
                if action_id == 1 and last_pos == 0:  # LEFT at position 0
                    # Open nav sidebar
                    self._show_nav_sidebar()
                    return
                
                # Update position tracking BEFORE processing
                self._last_list_position = current_pos
                
                # Allow UI to update selection then refresh
                xbmc.sleep(50)
                self.update_editor_view()
                self.refresh_program_view()
                # Update focus tracking
                self._last_known_focus = 2000
                # Let XML handle focus movement
                xbmcgui.WindowXMLDialog.onAction(self, action)
                return
            elif action_id == 3:  # UP - Let XML handle it
                self._last_known_focus = 2000
                xbmcgui.WindowXMLDialog.onAction(self, action)
                return
            elif action_id == 4:  # DOWN - Open AI Chat Sidebar
                # Only open sidebar if we were ALREADY on 2000 before this action
                # (This prevents opening when navigating from grid TO channel list)
                if last_focus != 2000:
                    # We just arrived at 2000 via this DOWN action
                    # Update tracking and don't open sidebar
                    self._last_known_focus = 2000
                    return
                # Don't open if blocked (during internal operations like add series)
                if getattr(self, '_block_sidebar', False):
                    return
                # Only open if sidebar is not already visible
                if not self.getProperty('WCS.AIChat.Visible'):
                    self.setProperty('WCS.AIChat.Visible', 'true')
                    self._populate_mytv_chat_buttons()
                    self.setFocusId(4000)
                return
            else:
                # Other actions on 2000 - handle with base Window, not our parent
                self._last_known_focus = 2000
                xbmcgui.WindowXMLDialog.onAction(self, action)
                return
        
        # Update last known focus for non-2000 controls
        self._last_known_focus = focused_id
        
        super(MyTVDialog, self).onAction(action)
    
    def onFocus(self, controlId):
        # Close chat sidebar when focusing channel list
        if controlId == 2000:
            self.clearProperty('WCS.AIChat.Visible')
        # Dynamic descriptions for Add Dialog action cards
        elif controlId == 9201:
            self.setProperty('WCS.MyTV.AddDialog.Description', 'Vyberte seriály z vaší uložené knihovny a přidejte je do kanálu.')
        elif controlId == 9202:
            self.setProperty('WCS.MyTV.AddDialog.Description', 'Vyhledejte nový seriál na TMDb podle názvu.')
        elif controlId == 9203:
            self.setProperty('WCS.MyTV.AddDialog.Description', 'Získejte doporučení podobných seriálů na základě seriálů v kanálu.')
        elif controlId == 9204:
            self.setProperty('WCS.MyTV.AddDialog.Description', 'Nechte AI doporučit seriály na základě vašich preferencí.')
        else:
            # For all other controls (including 4001 for sidebar close), call base class
            super(MyTVDialog, self).onFocus(controlId)

    def onClick(self, controlId):
        if controlId == 2000:
            # Check if "Create New"
            list_control = self.getControl(2000)
            item = list_control.getSelectedItem()
            if item.getProperty('is_create_button') == 'true':
                self._create_new_channel()
            else:
                # Move focus to "Spustit vysilani" button with delay
                self._delayed_focus(9004, delay=0.1)
        elif controlId == 9100:
            # Context Menu for Grid Item (Remove)
            self._handle_grid_click()
        elif controlId == 9001:
            # Add from My Series (legacy - button removed)
            self._add_from_my_series()
        elif controlId == 9002:
            # Add New (Search) (legacy - button removed)
            self._add_new_search()
        elif controlId == 9003:
            # Delete Channel
            self._delete_current_channel()
        elif controlId == 9004:
            # Play Broadcast (First item in upcoming)
            self._play_broadcast()
        elif controlId == 9005:
            # Refresh/Shuffle
            self.refresh_program_view()
            xbmcgui.Dialog().notification("Moje TV", "Program aktualizovan", xbmcgui.NOTIFICATION_INFO)
        elif controlId == 9000:
            # Play Program Item
            self._play_program_item()
        # Add Series Dialog controls
        elif controlId == 9200:
            # Close button (X)
            self._close_add_series_dialog()
        elif controlId == 9201:
            # Z moji knihovny
            self._show_series_listing('my_series')
        elif controlId == 9202:
            # Vyhledat na TMDb
            self._show_series_listing('search')
        elif controlId == 9203:
            # Doporucit z TMDb
            self._show_series_listing('tmdb_recommendations')
        elif controlId == 9204:
            # Doporucit od AI
            self._show_series_listing('ai_recommendations')
        elif controlId == 9211:
            # Series listing - add selected series to channel
            self._add_series_from_listing()
        
        super(MyTVDialog, self).onClick(controlId)

    def _create_new_channel(self):
        # Block sidebar during this operation
        self._block_sidebar = True
        try:
            name = xbmcgui.Dialog().input("Nazev kanalu", type=xbmcgui.INPUT_ALPHANUM)
            if not name: return
            
            self.channel_manager.create_channel(name)
            # Select the new one (first one - new channels are prepended)
            self.current_channel_index = 0
            self.refresh_channels_view(set_focus=False)
            
            # Auto-trigger "Add from My Series"
            self._add_from_my_series()
            
            # After adding series, refresh and focus on the new channel
            self.refresh_channels_view(set_focus=False)
            self.update_editor_view()
            self.refresh_program_view()
            
            # Focus on channel list (new channel card)
            self._delayed_focus(2000, delay=0.2)
        finally:
            self._block_sidebar = False

    def _delete_current_channel(self):
        list_control = self.getControl(2000)
        item = list_control.getSelectedItem()
        if not item or item.getProperty('is_create_button') == 'true': return
        
        channel_id = item.getProperty('channel_id')
        channel_name = item.getLabel()
        
        if xbmcgui.Dialog().yesno("Smazat kanál", f"Opravdu chccete smazat kanál '{channel_name}'?"):
            # We need method in manager to delete
            # self.channel_manager.delete_channel(channel_id) # Missing method
            # Let's implement ad-hoc or add to manager. Best to add to manager.
            # For quick fix, I will do it here via accessing list directly, but cleaner is to update manager file.
            # I will update manager file first? No, I can access internal list if I have to, but let's assume I can update manager.
            # Actually I already implemented the manager file in previous turns. I should check if I have delete method.
            # I don't think I added delete method.
            
            # Accessing internal list for now to save tool calls, or re-write manager.
            # Let's rewrite manager slightly or just filter list.
            
            self.channel_manager.channels = [ch for ch in self.channel_manager.channels if ch.id != channel_id]
            self.channel_manager._save_channels()
            
            self.current_channel_index = 0
            self.refresh_channels_view()
            self.current_channel_index = 0
            self.refresh_channels_view()
            self.refresh_channels_view()
            xbmcgui.Dialog().notification("Moje TV", "Kanál smazán.", xbmcgui.NOTIFICATION_INFO)

    def _play_broadcast(self):
        """Plays all items in the upcoming program list continuously."""
        list_control = self.getControl(9000)
        size = list_control.size()
        if size == 0:
            xbmcgui.Dialog().notification("Moje TV", "Žádný program k vysílání.", xbmcgui.NOTIFICATION_INFO)
            return
            
        self.close()
        
        # Get Playlist
        playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
        playlist.clear()
        
        # Add all items to playlist
        for i in range(size):
            item = list_control.getListItem(i)
            
            series_name = item.getProperty('series_name')
            season = item.getProperty('season')
            episode = item.getProperty('episode')
            tmdb_id = item.getProperty('tmdb_id')
            
            if not series_name or not season or not episode:
                continue

            # Metadata params
            episode_title = item.getProperty('episode_title')
            plot = item.getProperty('plot')
            rating = item.getProperty('rating')
            poster = item.getProperty('poster')
            fanart = item.getProperty('fanart')
            genre = item.getProperty('genre')
            year = item.getProperty('year')
            
            # Construct plugin URL for PLAYLIST action
            url = (f"plugin://{self.addon.getAddonInfo('id')}?action=play_episode_from_addon_playlist"
                   f"&series_name={quote_plus(series_name)}"
                   f"&season_number={season}"
                   f"&episode_number={episode}"
                   f"&tmdb_id={tmdb_id}"
                   f"&episode_title={quote_plus(episode_title)}"
                   f"&plot={quote_plus(plot)}"
                   f"&rating={quote_plus(rating)}"
                   f"&poster={quote_plus(poster)}"
                   f"&fanart={quote_plus(fanart)}"
                   f"&genre={quote_plus(genre)}"
                   f"&year={quote_plus(year)}")
            
            # Create list item for playlist
            li = xbmcgui.ListItem(label=item.getLabel())
            li.setInfo('video', {
                'title': item.getLabel(),
                'plot': plot, 
                'year': int(year) if year.isdigit() else 0,
                'premiered': year,
                'mediatype': 'episode',
                'season': int(season),
                'episode': int(episode),
                'tvshowtitle': series_name
            })
            li.setArt({
                'poster': poster,
                'fanart': fanart,
                'thumb': poster
            })
            
            playlist.add(url, li)
            
        # Start playback
        xbmc.Player().play(playlist)

    def _play_program_item(self):
        list_control = self.getControl(9000)
        item = list_control.getSelectedItem()
        if not item: return
        
        series_name = item.getProperty('series_name')
        season = item.getProperty('season')
        episode = item.getProperty('episode')
        tmdb_id = item.getProperty('tmdb_id')
        
        # Metadata pro utils.play_episode_from_addon
        episode_title = item.getProperty('episode_title')
        plot = item.getProperty('plot')
        rating = item.getProperty('rating')
        poster = item.getProperty('poster')
        fanart = item.getProperty('fanart')
        genre = item.getProperty('genre')
        year = item.getProperty('year')
        
        if not series_name or not season or not episode:
             return

        self.close()
        
        # Trigger play action via plugin URL - using standard utils function
        url = (f"plugin://{self.addon.getAddonInfo('id')}?action=play_episode_from_addon"
               f"&series_name={quote_plus(series_name)}"
               f"&season_number={season}"
               f"&episode_number={episode}"
               f"&tmdb_id={tmdb_id}"
               f"&episode_title={quote_plus(episode_title)}"
               f"&plot={quote_plus(plot)}"
               f"&rating={quote_plus(rating)}"
               f"&poster={quote_plus(poster)}"
               f"&fanart={quote_plus(fanart)}"
               f"&genre={quote_plus(genre)}"
               f"&year={quote_plus(year)}")
        
        xbmc.executebuiltin(f"PlayMedia({url})")

    def _play_current_channel(self):
        """Spustí přehrávání aktuálního kanálu (náhodná epizoda Next Up)."""
        list_control = self.getControl(2000)
        item = list_control.getSelectedItem()
        if not item: return
        
        channel_id = item.getProperty('channel_id')
        
        # Najít další epizodu pro přehrání
        next_episode = self.channel_manager.get_random_episode_for_channel(channel_id)
        
        if not next_episode:
            xbmcgui.Dialog().notification(
                self.addon.getAddonInfo('name'),
                "Kanál nemá žádné dostupné epizody",
                xbmcgui.NOTIFICATION_INFO
            )
            return
            
        self.close()
        
        # Přehrát epizodu pomocí standardní akce
        series_name = next_episode.get('series_name') or next_episode.get('series_title')
        season = next_episode.get('season_number')
        episode = next_episode.get('episode_number')
        
        url = (f"plugin://{self.addon.getAddonInfo('id')}?action=play_episode_from_addon"
               f"&series_name={quote_plus(series_name)}"
               f"&season_number={season}"
               f"&episode_number={episode}"
               f"&tmdb_id={next_episode.get('tmdb_id', '')}"
               f"&episode_title={quote_plus(next_episode.get('episode_title', ''))}"
               f"&plot={quote_plus(next_episode.get('episode_plot', ''))}"
               f"&rating={quote_plus(str(next_episode.get('episode_rating', '')))}"
               f"&poster={quote_plus(next_episode.get('series_poster', ''))}"
               f"&fanart={quote_plus(next_episode.get('series_fanart', ''))}"
               f"&year={quote_plus(str(next_episode.get('episode_year', '')))}")
        
        xbmc.executebuiltin(f"PlayMedia({url})")

    def _populate_mytv_chat_buttons(self):
        """Populate chat sidebar buttons with MyTV-specific options."""
        buttons = [
            {"label": "Vytvorit Comedy Channel", "value": "create_channel:comedy"},
            {"label": "Vytvorit Drama Channel", "value": "create_channel:drama"},
            {"label": "Vytvorit Scifi Channel", "value": "create_channel:scifi"},
        ]
        self.update_buttons(buttons)

    def _handle_grid_click(self):
        # Block sidebar during this operation
        self._block_sidebar = True
        try:
            grid = self.getControl(9100)
            item = grid.getSelectedItem()
            if not item: return
            
            # Check if it's the "Add Series" placeholder
            if item.getProperty('is_add_button') == 'true':
                # Show custom Add Series dialog overlay
                self._show_add_series_dialog()
                return

            series_label = item.getLabel()
            channel_id = item.getProperty('channel_id')
            series_id = item.getProperty('series_id')
            
            # Context menu
            menu = xbmcgui.Dialog().contextmenu([f"Smazat '{series_label}' z kanalu"])
            if menu == 0:
                # Remove
                self.channel_manager.toggle_series_in_channel(channel_id, {'id': series_id})
                # Refresh editor view only to keep focus position best as possible
                self.update_editor_view()
                self.refresh_program_view()
                self.setFocusId(9100)  # Keep focus in grid (fixed ID)
                xbmcgui.Dialog().notification("Moje TV", f"'{series_label}' odebrano.", xbmcgui.NOTIFICATION_INFO)
        finally:
            self._block_sidebar = False

    def _add_from_my_series(self):
        # Block sidebar from opening during this operation
        self._block_sidebar = True
        try:
            # Get My Series
            my_series = user_data.load_tmdb_series(self.addon)
            if not my_series:
                 xbmcgui.Dialog().notification("Moje TV", "Nenalezeny zadne ulozene serialy.", xbmcgui.NOTIFICATION_INFO)
                 return
                 
            # Filter out already added ones?
            list_control = self.getControl(2000)
            channel_id = list_control.getSelectedItem().getProperty('channel_id')
            channel = self.channel_manager.get_channel_by_id(channel_id)
            current_ids = [str(s.get('id')) for s in channel.series_list]
            
            candidates = [s for s in my_series if str(s.get('id')) not in current_ids]
            
            if not candidates:
                 xbmcgui.Dialog().notification("Moje TV", "Vsechny vase serialy uz jsou v kanalu.", xbmcgui.NOTIFICATION_INFO)
                 return
                 
            # Multi select
            labels = [f"{s.get('name')} ({s.get('year', '')})" for s in candidates]
            selected = xbmcgui.Dialog().multiselect("Pridat do kanalu", labels)
            
            if selected:
                for idx in selected:
                    self.channel_manager.toggle_series_in_channel(channel_id, candidates[idx])
                # Refresh editor view
                self.update_editor_view()
                self.refresh_program_view()
                self.setFocusId(9100)  # Changed from 3000 to correct ID
                xbmcgui.Dialog().notification("Moje TV", "Serialy pridany.", xbmcgui.NOTIFICATION_INFO)
        finally:
            # Unblock sidebar
            self._block_sidebar = False

    def _add_new_search(self):
        # Block sidebar during this operation
        self._block_sidebar = True
        try:
            import requests
            from wcs.metadata.TMDbClient import get_tmdb_api_key
            
            search_query = xbmcgui.Dialog().input("Hledat serial", type=xbmcgui.INPUT_ALPHANUM)
            if not search_query: return
            
            try:
                url = f'https://api.themoviedb.org/3/search/tv?api_key={get_tmdb_api_key()}&language=cs-CZ&query={quote_plus(search_query)}'
                response = requests.get(url, timeout=10)
                results = response.json().get('results', [])
            except Exception:
                return
                
            if not results:
                 xbmcgui.Dialog().notification("Moje TV", "Nic nenalezeno.", xbmcgui.NOTIFICATION_INFO)
                 return
                 
            choices = [f"{item['name']} ({item.get('first_air_date', '')[:4]})" for item in results]
            selected_idx = xbmcgui.Dialog().select("Vyberte serial", choices)
            
            if selected_idx >= 0:
                selected_item = results[selected_idx]
                list_control = self.getControl(2000)
                channel_id = list_control.getSelectedItem().getProperty('channel_id')
                
                self.channel_manager.toggle_series_in_channel(channel_id, selected_item)
                # Full refresh
                self.update_editor_view()
                self.refresh_program_view()
                self.setFocusId(9100)
                xbmcgui.Dialog().notification("Moje TV", f"'{selected_item['name']}' pridano.", xbmcgui.NOTIFICATION_INFO)
        finally:
            self._block_sidebar = False

    # ==================================================================================
    # ADD SERIES DIALOG METHODS
    # ==================================================================================
    
    def _show_add_series_dialog(self):
        """Show the custom Add Series overlay dialog."""
        self.setProperty('WCS.MyTV.AddDialog.Visible', 'true')
        self.setProperty('WCS.MyTV.AddDialog.Description', 'Vyberte serialy z vasi ulozene knihovny a pridejte je do kanalu.')
        self.clearProperty('WCS.MyTV.AddDialog.ListingMode')
        self.clearProperty('WCS.MyTV.AddDialog.ListingTitle')
        # Store current channel_id for later use
        list_control = self.getControl(2000)
        self._add_dialog_channel_id = list_control.getSelectedItem().getProperty('channel_id')
        self._add_dialog_channel = self.channel_manager.get_channel_by_id(self._add_dialog_channel_id)
        self.setFocusId(9201)
    
    def _close_add_series_dialog(self):
        """Close the Add Series overlay dialog."""
        self.clearProperty('WCS.MyTV.AddDialog.Visible')
        self.clearProperty('WCS.MyTV.AddDialog.ListingMode')
        self.clearProperty('WCS.MyTV.AddDialog.ListingTitle')
        self.clearProperty('WCS.MyTV.AddDialog.Description')
        # Refresh views to reflect any changes
        self.update_editor_view()
        self.refresh_program_view()
        self.setFocusId(9100)
    
    def _show_series_listing(self, source):
        """Show series listing for the given source."""
        import requests
        from wcs.metadata.TMDbClient import get_tmdb_api_key
        
        items = []
        title = ""
        
        if source == 'my_series':
            title = "Z mojí knihovny"
            my_series = user_data.load_tmdb_series(self.addon)
            if not my_series:
                xbmcgui.Dialog().notification("Moje TV", "Žádné uložené seriály.", xbmcgui.NOTIFICATION_INFO)
                return
            
            # Show dialog immediately with loading state, then async enrich
            self.setProperty('WCS.MyTV.AddDialog.ListingMode', source)
            self.setProperty('WCS.MyTV.AddDialog.ListingTitle', title)
            
            # Store basic items first
            self._add_dialog_items = my_series
            
            # Start async enrichment with cache
            import threading
            def enrich_worker():
                cache = get_cache_manager()
                list_control = self.getControl(9211)
                list_control.reset()
                
                # Get current channel series IDs for checkmark
                current_ids = {str(s.get('id')) for s in self._add_dialog_channel.series_list} if self._add_dialog_channel else set()
                
                enriched = []
                for series in my_series:
                    tmdb_id = series.get('id')
                    if not tmdb_id:
                        enriched.append(series)
                        self._add_item_to_list(list_control, series, current_ids)
                        continue
                    
                    cache_key = f"tv_detail_{tmdb_id}"
                    cached = cache.get(cache_key)
                    
                    if cached:
                        # Use cached data
                        enriched.append(cached)
                        self._add_item_to_list(list_control, cached, current_ids)
                    else:
                        # Fetch from TMDb
                        try:
                            url = f'https://api.themoviedb.org/3/tv/{tmdb_id}?api_key={get_tmdb_api_key()}&language=cs-CZ'
                            response = requests.get(url, timeout=5)
                            if response.status_code == 200:
                                full_data = response.json()
                                # Cache for 7 days
                                cache.set(cache_key, full_data, ttl_seconds=7*24*3600)
                                enriched.append(full_data)
                                self._add_item_to_list(list_control, full_data, current_ids)
                            else:
                                enriched.append(series)
                                self._add_item_to_list(list_control, series, current_ids)
                        except Exception as e:
                            xbmc.log(f"[MyTV] TMDb fetch error: {e}", xbmc.LOGWARNING)
                            enriched.append(series)
                            self._add_item_to_list(list_control, series, current_ids)
                
                # Update stored items with enriched data
                self._add_dialog_items = enriched
                cache.flush()
                
                # Focus first item
                if list_control.size() > 0:
                    self.setFocusId(9211)
            
            threading.Thread(target=enrich_worker, daemon=True).start()
            return  # Don't continue to normal flow
            
        elif source == 'search':
            title = "Vyhledávání na TMDb"
            search_query = xbmcgui.Dialog().input("Hledat seriál", type=xbmcgui.INPUT_ALPHANUM)
            if not search_query:
                return
            try:
                url = f'https://api.themoviedb.org/3/search/tv?api_key={get_tmdb_api_key()}&language=cs-CZ&query={quote_plus(search_query)}'
                response = requests.get(url, timeout=10)
                items = response.json().get('results', [])
            except Exception as e:
                xbmc.log(f"[MyTV] Search error: {e}", xbmc.LOGERROR)
                return
            if not items:
                xbmcgui.Dialog().notification("Moje TV", "Nic nenalezeno.", xbmcgui.NOTIFICATION_INFO)
                return
                
        elif source == 'tmdb_recommendations':
            title = "Doporučení z TMDb"
            # Get recommendations based on series in channel
            if not self._add_dialog_channel or not self._add_dialog_channel.series_list:
                xbmcgui.Dialog().notification("Moje TV", "Kanál nemá žádné seriály.", xbmcgui.NOTIFICATION_INFO)
                return
            
            all_recs = []
            seen_ids = set()
            current_ids = {str(s.get('id')) for s in self._add_dialog_channel.series_list}
            
            # Get recommendations for each series in channel (max 3 to avoid many API calls)
            for series in self._add_dialog_channel.series_list[:3]:
                series_id = series.get('id')
                try:
                    url = f'https://api.themoviedb.org/3/tv/{series_id}/recommendations?api_key={get_tmdb_api_key()}&language=cs-CZ'
                    response = requests.get(url, timeout=10)
                    recs = response.json().get('results', [])
                    for rec in recs:
                        rec_id = str(rec.get('id'))
                        if rec_id not in seen_ids and rec_id not in current_ids:
                            seen_ids.add(rec_id)
                            all_recs.append(rec)
                except Exception as e:
                    xbmc.log(f"[MyTV] TMDb recommendations error: {e}", xbmc.LOGERROR)
            
            if not all_recs:
                xbmcgui.Dialog().notification("Moje TV", "Zadna doporuceni.", xbmcgui.NOTIFICATION_INFO)
                return
            items = all_recs[:20]  # Limit to 20
            
        elif source == 'ai_recommendations':
            title = "Doporuceni od AI"
            # Get series from channel for context
            if not self._add_dialog_channel or not self._add_dialog_channel.series_list:
                xbmcgui.Dialog().notification("Moje TV", "Kanal nema zadne serialy pro kontext.", xbmcgui.NOTIFICATION_INFO)
                return
            
            # Keep dialog open - just switch to listing mode with loading state
            self.setProperty('WCS.MyTV.AddDialog.ListingMode', source)
            self.setProperty('WCS.MyTV.AddDialog.ListingTitle', "Načítám AI doporučení...")
            
            # Prepare series items for context
            series_items = list(self._add_dialog_channel.series_list)
            
            # Start AI in background thread
            import threading
            import json
            def ai_worker():
                try:
                    # === Build prompt using same pattern as Hub ===
                    self.media_type = 'series'
                    self.context = {'type': 'my_series', 'data': series_items}
                    self._prompt_builder = AIRecommendationPromptBuilder(self.media_type, self.context)
                    
                    # Get context instruction (generates proper prompt with series list)
                    context_instruction = self._get_context_instruction()
                    user_msg = context_instruction if context_instruction else "Doporuc mi zajimave serialy k sledovani."
                    
                    # Build full prompt using prompt builder
                    system_prompt = self._prompt_builder.build_system_prompt()
                    
                    # Call AI
                    from wcs.ai.AIProvider import AIProvider
                    ai_provider = AIProvider()
                    response, usage = ai_provider.send_prompt(f"{system_prompt}\n\nUser: {user_msg}")
                    
                    if not response:
                        xbmcgui.Dialog().notification("Moje TV", "AI nevratila odpoved.", xbmcgui.NOTIFICATION_INFO)
                        return
                    
                    # Parse AI response - extract recommendations array (same format as Hub)
                    try:
                        data = json.loads(response)
                        recommendations = data.get('recommendations', [])
                    except json.JSONDecodeError:
                        xbmcgui.Dialog().notification("Moje TV", "AI odpoved neni validni JSON.", xbmcgui.NOTIFICATION_INFO)
                        return
                    
                    if not recommendations:
                        xbmcgui.Dialog().notification("Moje TV", "AI nedoporucila zadne serialy.", xbmcgui.NOTIFICATION_INFO)
                        return
                    
                    # === Use Hub's _tmdb_worker pattern ===
                    # Get current channel series IDs to filter
                    current_ids = {str(s.get('id')) for s in self._add_dialog_channel.series_list} if self._add_dialog_channel else set()
                    
                    tmdb_items = []
                    for rec in recommendations:
                        rec_title = rec.get('title') or rec.get('original_title')
                        rec_year = rec.get('year')
                        # AI returns 'tv' for series
                        item_media_type = rec.get('type', 'tv')
                        if item_media_type not in ['movie', 'tv', 'collection']:
                            item_media_type = 'tv'  # Default to TV for series
                        
                        # Use parent's _search_tmdb method (inherited from AIChatRecommendationDialog)
                        tmdb_data = self._search_tmdb(rec_title, rec_year, item_media_type)
                        
                        if tmdb_data:
                            item_id = str(tmdb_data.get('id'))
                            # Skip if already in channel
                            if item_id in current_ids:
                                continue
                            tmdb_items.append(tmdb_data)
                    
                    if not tmdb_items:
                        xbmcgui.Dialog().notification("Moje TV", "Nenalezeny serialy na TMDb.", xbmcgui.NOTIFICATION_INFO)
                        return
                    
                    # Update dialog with results
                    self._add_dialog_items = tmdb_items
                    self.setProperty('WCS.MyTV.AddDialog.ListingTitle', title)
                    self._populate_add_dialog_list(tmdb_items)
                    self.setFocusId(9211)
                    
                except Exception as e:
                    xbmc.log(f"[MyTV] AI recommendations error: {e}", xbmc.LOGERROR)
                    xbmcgui.Dialog().notification("Moje TV", "Chyba AI doporuceni.", xbmcgui.NOTIFICATION_ERROR)
            
            threading.Thread(target=ai_worker, daemon=True).start()
            return  # Don't continue to normal listing flow
        
        if not items:
            return
        
        # Store items for later use
        self._add_dialog_items = items
        
        # Set listing mode
        self.setProperty('WCS.MyTV.AddDialog.ListingMode', source)
        self.setProperty('WCS.MyTV.AddDialog.ListingTitle', title)
        
        # Populate the list control (9211)
        self._populate_add_dialog_list(items)
        self.setFocusId(9211)
    
    def _populate_add_dialog_list(self, items):
        """Populate the series listing control with items."""
        try:
            list_control = self.getControl(9211)
            list_control.reset()
            
            # Get current channel series IDs for checkmark
            current_ids = {str(s.get('id')) for s in self._add_dialog_channel.series_list} if self._add_dialog_channel else set()
            
            for item_data in items:
                item = xbmcgui.ListItem(label=item_data.get('name', ''))
                
                # Poster
                poster = item_data.get('poster_path') or item_data.get('poster', '')
                if poster and not poster.startswith('http'):
                    poster = f"https://image.tmdb.org/t/p/w500{poster}"
                
                # Fanart
                fanart = item_data.get('backdrop_path') or item_data.get('fanart', '')
                if fanart and not fanart.startswith('http'):
                    fanart = f"https://image.tmdb.org/t/p/w1280{fanart}"
                
                item.setArt({
                    'poster': poster or 'special://home/addons/plugin.video.milionar/resources/media/placeholder_tv_card.png',
                    'fanart': fanart or 'special://home/addons/plugin.video.milionar/resources/media/fanart_fallback.jpg'
                })
                
                # Properties for metadata panel - support both TMDb and library formats
                item.setProperty('tmdb_id', str(item_data.get('id', '')))
                
                # Year: TMDb uses 'first_air_date', library may use 'year'
                year_val = item_data.get('first_air_date', '') or item_data.get('year', '')
                if year_val:
                    item.setProperty('year', str(year_val)[:4])
                else:
                    item.setProperty('year', '')
                
                # Rating: TMDb uses 'vote_average', library may use 'rating'
                rating_val = item_data.get('vote_average') or item_data.get('rating') or 0
                item.setProperty('rating', str(round(float(rating_val), 1)) if rating_val else '0')
                
                # Plot: TMDb uses 'overview', library may use 'plot'
                plot_val = item_data.get('overview', '') or item_data.get('plot', '')
                item.setProperty('plot', plot_val)
                
                # Genre: TMDb uses 'genre_ids', library may use 'genre' or 'genres'
                genres = item_data.get('genre_ids', [])
                if genres:
                    genre_names = self._map_genre_ids(genres)
                else:
                    # Try library formats
                    genre_names = item_data.get('genre', '') or item_data.get('genres', '')
                    if isinstance(genre_names, list):
                        # Handle list of genre dicts from TMDb details
                        genre_names = ', '.join([g.get('name', '') for g in genre_names if isinstance(g, dict)])
                item.setProperty('genre', genre_names)
                
                # Check if already in channel
                if str(item_data.get('id')) in current_ids:
                    item.setProperty('is_added', 'true')
                
                list_control.addItem(item)
                
        except Exception as e:
            xbmc.log(f"[MyTV] Error populating add dialog list: {e}", xbmc.LOGERROR)
    
    def _add_item_to_list(self, list_control, item_data, current_ids):
        """Add a single item to the list control with proper metadata."""
        try:
            item = xbmcgui.ListItem(label=item_data.get('name', ''))
            
            # Poster
            poster = item_data.get('poster_path') or item_data.get('poster', '')
            if poster and not poster.startswith('http'):
                poster = f"https://image.tmdb.org/t/p/w500{poster}"
            
            # Fanart
            fanart = item_data.get('backdrop_path') or item_data.get('fanart', '')
            if fanart and not fanart.startswith('http'):
                fanart = f"https://image.tmdb.org/t/p/w1280{fanart}"
            
            item.setArt({
                'poster': poster or 'special://home/addons/plugin.video.milionar/resources/media/placeholder_tv_card.png',
                'fanart': fanart or 'special://home/addons/plugin.video.milionar/resources/media/fanart_fallback.jpg'
            })
            
            # Properties - support both TMDb and library formats
            item.setProperty('tmdb_id', str(item_data.get('id', '')))
            
            # Year
            year_val = item_data.get('first_air_date', '') or item_data.get('year', '')
            item.setProperty('year', str(year_val)[:4] if year_val else '')
            
            # Rating
            rating_val = item_data.get('vote_average') or item_data.get('rating') or 0
            item.setProperty('rating', str(round(float(rating_val), 1)) if rating_val else '0')
            
            # Plot
            plot_val = item_data.get('overview', '') or item_data.get('plot', '')
            item.setProperty('plot', plot_val)
            
            # Genre
            genres = item_data.get('genre_ids', [])
            if genres:
                genre_names = self._map_genre_ids(genres)
            else:
                genre_names = item_data.get('genre', '') or item_data.get('genres', '')
                if isinstance(genre_names, list):
                    genre_names = ', '.join([g.get('name', '') for g in genre_names if isinstance(g, dict)])
            item.setProperty('genre', genre_names if genre_names else '')
            
            # Check if already in channel
            if str(item_data.get('id')) in current_ids:
                item.setProperty('is_added', 'true')
            
            list_control.addItem(item)
        except Exception as e:
            xbmc.log(f"[MyTV] Error adding item to list: {e}", xbmc.LOGERROR)
    
    def _map_genre_ids(self, genre_ids):
        """Map TMDb genre IDs to names."""
        genre_map = {
            10759: 'Akce & Dobrodruzstvi', 16: 'Animovany', 35: 'Komedie',
            80: 'Krimi', 99: 'Dokument', 18: 'Drama', 10751: 'Rodinny',
            10762: 'Detsky', 9648: 'Zahadny', 10763: 'Zpravy',
            10764: 'Reality', 10765: 'Sci-Fi & Fantasy', 10766: 'Telenovela',
            10767: 'Talk Show', 10768: 'Valecny & Politik', 37: 'Western'
        }
        names = [genre_map.get(gid, '') for gid in genre_ids[:2] if gid in genre_map]
        return ', '.join(names) if names else ''
    
    def _add_series_from_listing(self):
        """Add the currently selected series from the listing to the channel."""
        try:
            list_control = self.getControl(9211)
            selected_pos = list_control.getSelectedPosition()
            
            if selected_pos < 0 or not hasattr(self, '_add_dialog_items'):
                return
            
            selected_item = self._add_dialog_items[selected_pos]
            series_id = str(selected_item.get('id'))
            
            # Check if already added
            current_ids = {str(s.get('id')) for s in self._add_dialog_channel.series_list} if self._add_dialog_channel else set()
            if series_id in current_ids:
                xbmcgui.Dialog().notification("Moje TV", "Serial uz je v kanalu.", xbmcgui.NOTIFICATION_INFO)
                return
            
            # Add to channel
            self.channel_manager.toggle_series_in_channel(self._add_dialog_channel_id, selected_item)
            
            # Update the item to show checkmark
            list_item = list_control.getListItem(selected_pos)
            list_item.setProperty('is_added', 'true')
            
            # Update channel reference
            self._add_dialog_channel = self.channel_manager.get_channel_by_id(self._add_dialog_channel_id)
            
            xbmcgui.Dialog().notification("Moje TV", f"'{selected_item.get('name')}' pridano.", xbmcgui.NOTIFICATION_INFO)
            
        except Exception as e:
            xbmc.log(f"[MyTV] Error adding series from listing: {e}", xbmc.LOGERROR)

    def _fetch_ai_series_recommendations(self, series_names):
        """Fetch AI recommendations based on series names in channel."""
        try:
            from wcs.ai.AIProvider import AIProvider
            
            # Build prompt - combine system and user message
            series_list = ", ".join(series_names[:10])  # Limit to 10 for prompt
            prompt = f"""Jsi expert na serialy. Doporucujes podobne serialy na zaklade preferencii uzivatele.

Na zaklade techto serialu doporuc 10 podobnych serialu:
{series_list}

Odpovez POUZE seznamem nazvu serialu, jeden na radek, bez cislovani a bez dalsiho textu.
Priklad formatu:
Breaking Bad
The Wire
Better Call Saul"""

            # Call AI - AIProvider uses settings for provider, send_prompt returns (response, usage)
            ai_provider = AIProvider()
            response, usage = ai_provider.send_prompt(prompt)
            
            if not response:
                return []
            
            # Parse response - each line is a series name
            lines = response.strip().split('\n')
            results = []
            for line in lines:
                name = line.strip()
                # Remove numbering if present (e.g., "1. Breaking Bad" -> "Breaking Bad")
                if name and len(name) > 2:
                    if name[0].isdigit() and (name[1] == '.' or name[1] == ')'):
                        name = name[2:].strip()
                    elif name[0].isdigit() and name[1].isdigit() and (name[2] == '.' or name[2] == ')'):
                        name = name[3:].strip()
                if name and len(name) > 1:
                    results.append(name)
            
            return results[:15]  # Limit to 15
            
        except Exception as e:
            xbmc.log(f"[MyTV] AI recommendations error: {e}", xbmc.LOGERROR)
            return []
    
    def _parse_ai_series_response(self, response):
        """Parse AI response and extract series names."""
        try:
            import re
            import json
            
            results = []
            
            # Try to parse as JSON first (AI might return structured data)
            try:
                data = json.loads(response)
                if isinstance(data, list):
                    for item in data:
                        if isinstance(item, dict):
                            name = item.get('name') or item.get('title') or item.get('series')
                            if name:
                                results.append(name)
                        elif isinstance(item, str):
                            results.append(item)
                    if results:
                        return results[:15]
            except json.JSONDecodeError:
                pass
            
            # Parse as text - each line is a series name
            lines = response.strip().split('\n')
            for line in lines:
                name = line.strip()
                
                # Skip empty lines and common prefixes
                if not name or name.startswith('---') or name.startswith('==='):
                    continue
                
                # Remove markdown formatting
                name = re.sub(r'\*\*([^*]+)\*\*', r'\1', name)  # **bold**
                name = re.sub(r'\*([^*]+)\*', r'\1', name)      # *italic*
                
                # Remove numbering (e.g., "1. Breaking Bad" -> "Breaking Bad")
                name = re.sub(r'^\d+[\.\)]\s*', '', name)
                
                # Remove bullet points
                name = re.sub(r'^[-•]\s*', '', name)
                
                # Extract title before description (e.g., "Breaking Bad - Amazing series" -> "Breaking Bad")
                if ' - ' in name:
                    name = name.split(' - ')[0].strip()
                if ': ' in name and len(name.split(': ')[0]) > 3:
                    # Only split if first part looks like a title
                    first_part = name.split(': ')[0]
                    if not any(word in first_part.lower() for word in ['serial', 'doporucuji', 'take']):
                        name = first_part.strip()
                
                # Remove year in parentheses at end
                name = re.sub(r'\s*\(\d{4}\)\s*$', '', name)
                
                if name and len(name) > 2 and len(name) < 100:
                    results.append(name)
            
            return results[:15]  # Limit to 15
            
        except Exception as e:
            xbmc.log(f"[MyTV] Parse AI response error: {e}", xbmc.LOGERROR)
            return []
    
    def _lookup_series_on_tmdb(self, series_names):
        """Lookup series names on TMDb and return full metadata."""
        import requests
        from wcs.metadata.TMDbClient import get_tmdb_api_key
        
        results = []
        seen_ids = set()
        current_ids = {str(s.get('id')) for s in self._add_dialog_channel.series_list} if self._add_dialog_channel else set()
        
        for name in series_names:
            try:
                url = f'https://api.themoviedb.org/3/search/tv?api_key={get_tmdb_api_key()}&language=cs-CZ&query={quote_plus(name)}'
                response = requests.get(url, timeout=5)
                search_results = response.json().get('results', [])
                
                if search_results:
                    # Take the first result
                    item = search_results[0]
                    item_id = str(item.get('id'))
                    
                    # Skip duplicates and already in channel
                    if item_id not in seen_ids and item_id not in current_ids:
                        seen_ids.add(item_id)
                        results.append(item)
                        
            except Exception as e:
                xbmc.log(f"[MyTV] TMDb lookup error for '{name}': {e}", xbmc.LOGWARNING)
                continue
        
        return results

    # ==================================================================================
    # BACKGROUND GENERATION METHODS
    # ==================================================================================
    
    def _setup_background_properties(self):
        """Read background settings and set window properties for XML."""
        # Background mode - Kodi returns text value, need to map to index
        mode_value = self.addon.getSetting('mytv_background_mode') or 'Fanart seriálu'
        
        # Map text values to indices
        mode_map = {
            'Statické': 0,
            'Fanart seriálu': 1,
            'Still epizody': 2,
            'Koláž posterů': 3,
            'Koláž stillů': 4,
            'Mix koláž': 5
        }
        mode_idx = mode_map.get(mode_value, 1)  # Default to fanart
        
        xbmc.log(f"[MyTV] Background mode: '{mode_value}' -> {mode_idx}", xbmc.LOGINFO)
        self.setProperty('WCS.MyTV.BackgroundMode', str(mode_idx))
        
        # Dimming
        dim_pct = self.addon.getSetting('mytv_background_dim') or '50'
        try:
            dim_val = int(dim_pct)
        except ValueError:
            dim_val = 50
        # Convert percentage to hex alpha (0-90% -> hex value)
        dim_alpha = int(255 * (dim_val / 100))
        dim_hex = f"{dim_alpha:02X}000000"
        self.setProperty('WCS.MyTV.BackgroundDim', dim_hex)
        
        # Animation - Ken Burns effect with selectable style
        anim_enabled = self.addon.getSetting('mytv_background_animation') == 'true'
        self.setProperty('WCS.MyTV.Animation', 'true' if anim_enabled else '')
        
        if anim_enabled:
            # Read animation style and set property for XML conditions
            style = self.addon.getSetting('mytv_anim_style') or 'Jemny dech'
            style_map = {
                'Jemny dech': '0',        # Subtle zoom + slight drift, 45s
                'Pulzujici srdce': '1',   # Only zoom, no slide, 30s
                'Plynouci tok': '2',      # Only horizontal slide, 50s
                'Dynamicka vlna': '3',    # Big zoom + diagonal, fast 25s
                'Zrcadlovy odraz': '4'    # Medium zoom, opposite diagonal, 40s
            }
            style_idx = style_map.get(style, '0')
            self.setProperty('WCS.MyTV.AnimStyle', style_idx)
            xbmc.log(f"[MyTV] Animation: style={style} (idx={style_idx})", xbmc.LOGINFO)
        
        # Immediately load last used cached background (for instant display on dialog re-open)
        blur_enabled = self.addon.getSetting('mytv_background_blur') == 'true'
        glow_enabled = self.addon.getSetting('mytv_background_glow') == 'true'
        effects_enabled = blur_enabled or glow_enabled
        
        # Collage modes always need custom background
        needs_custom_bg = effects_enabled or mode_idx in (self.BG_COLLAGE_POSTERS, self.BG_COLLAGE_STILLS, self.BG_COLLAGE_MIX)
        
        if needs_custom_bg:
            # Try to load last used cache path for this mode from metadata
            last_cache_key = f"last_bg_mode_{mode_idx}"
            last_cache_path = self._cache.get(last_cache_key)
            if last_cache_path and xbmcvfs.exists(last_cache_path):
                self.setProperty('WCS.MyTV.Background.Custom', last_cache_path)
                xbmc.log(f"[MyTV] Loaded cached background immediately: {last_cache_path}", xbmc.LOGINFO)
    
    def _update_background(self, channel_id=None):
        """Update background image based on current mode and selected channel."""
        mode_str = self.getProperty('WCS.MyTV.BackgroundMode') or '1'
        try:
            mode = int(mode_str)
        except ValueError:
            mode = self.BG_FANART
        
        # Check if effects are enabled
        blur_enabled = self.addon.getSetting('mytv_background_blur') == 'true'
        glow_enabled = self.addon.getSetting('mytv_background_glow') == 'true'
        effects_enabled = blur_enabled or glow_enabled
        
        # If no effects and not collage mode, XML handles backgrounds directly - no action needed
        # Don't clear property - keep any cached background visible
        if not effects_enabled and mode in (self.BG_STATIC, self.BG_FANART, self.BG_STILL):
            return
        
        # All other cases need PIL processing
        if not PIL_AVAILABLE:
            xbmc.log("[MyTV] PIL not available, effects disabled", xbmc.LOGWARNING)
            return
        
        # Get channel info
        if not channel_id:
            try:
                list_control = self.getControl(2000)
                idx = list_control.getSelectedPosition()
                if idx >= 0:
                    item = list_control.getListItem(idx)
                    channel_id = item.getProperty('channel_id')
            except:
                return
        
        if not channel_id:
            return
        
        channel = self.channel_manager.get_channel_by_id(channel_id)
        if not channel:
            return
        
        # Generate background in background thread
        import threading
        threading.Thread(
            target=self._generate_background_async,
            args=(mode, channel_id, channel),
            daemon=True
        ).start()
    
    def _generate_background_async(self, mode, channel_id, channel):
        """Generate background with effects in background thread."""
        try:
            # Get effect settings for cache key
            blur_on = self.addon.getSetting('mytv_background_blur') == 'true'
            blur_r = self.addon.getSetting('mytv_background_blur_radius') or '5'
            glow_on = self.addon.getSetting('mytv_background_glow') == 'true'
            glow_i = self.addon.getSetting('mytv_background_glow_intensity') or '3'
            effects_key = f"b{blur_on}{blur_r}g{glow_on}{glow_i}"
            
            result_image = None
            source_url = None
            
            # Mode 0: Static background
            if mode == self.BG_STATIC:
                source_url = 'special://home/addons/plugin.video.milionar/resources/media/hub_background_v2.png'
                cache_key = f"bg_static_{effects_key}"
            
            # Mode 1: Fanart from first program item
            elif mode == self.BG_FANART:
                try:
                    program_list = self.getControl(9000)
                    if program_list.size() > 0:
                        source_url = program_list.getListItem(0).getArt('fanart')
                except:
                    pass
                if not source_url:
                    return
                cache_key = f"bg_fanart_{hash(source_url)}_{effects_key}"
            
            # Mode 2: Still from first program item
            elif mode == self.BG_STILL:
                try:
                    program_list = self.getControl(9000)
                    if program_list.size() > 0:
                        source_url = program_list.getListItem(0).getArt('thumb')
                except:
                    pass
                if not source_url:
                    return
                cache_key = f"bg_still_{hash(source_url)}_{effects_key}"
            
            # Mode 3-5: Collages
            else:
                images = []
                
                if mode == self.BG_COLLAGE_POSTERS:
                    if channel and channel.series_list:
                        for series in channel.series_list[:12]:
                            poster = series.get('poster_path') or series.get('poster')
                            if poster:
                                if not poster.startswith('http'):
                                    poster = f"https://image.tmdb.org/t/p/w342{poster}"
                                images.append(('poster', poster))
                
                elif mode == self.BG_COLLAGE_STILLS:
                    try:
                        program_list = self.getControl(9000)
                        for i in range(min(12, program_list.size())):
                            item = program_list.getListItem(i)
                            thumb = item.getArt('thumb')
                            if thumb:
                                images.append(('still', thumb))
                    except:
                        pass
                
                elif mode == self.BG_COLLAGE_MIX:
                    if channel and channel.series_list:
                        for series in channel.series_list[:6]:
                            poster = series.get('poster_path') or series.get('poster')
                            if poster:
                                if not poster.startswith('http'):
                                    poster = f"https://image.tmdb.org/t/p/w342{poster}"
                                images.append(('poster', poster))
                    try:
                        program_list = self.getControl(9000)
                        for i in range(min(6, program_list.size())):
                            item = program_list.getListItem(i)
                            thumb = item.getArt('thumb')
                            if thumb:
                                images.append(('still', thumb))
                    except:
                        pass
                
                if not images:
                    return
                
                cache_key = f"bg_collage_{mode}_{channel_id}_{len(images)}_{effects_key}"
            
            # Check cache using CacheManager
            cache_path = self._cache.get_asset_path(cache_key)
            if cache_path:
                self.setProperty('WCS.MyTV.Background.Custom', cache_path)
                self._save_last_cache_path(mode, cache_path)
                return
            
            # Generate/download base image
            if mode in (self.BG_STATIC, self.BG_FANART, self.BG_STILL):
                # Single image modes
                result_image = self._download_image(source_url)
                if result_image:
                    result_image = result_image.convert('RGB')
                    # Resize to 1920x1080
                    result_image = self._resize_to_cover(result_image, 1920, 1080)
            else:
                # Collage modes
                if mode == self.BG_COLLAGE_MIX:
                    result_image = self._generate_mix_collage(images)
                else:
                    result_image = self._generate_collage(images)
            
            if result_image:
                # Apply effects
                result_image = self._apply_effects(result_image)
                
                # Save to cache using CacheManager
                cache_path = self._cache.save_pil_image(cache_key, result_image)
                if cache_path:
                    self.setProperty('WCS.MyTV.Background.Custom', cache_path)
                    # Save reference for instant loading on next dialog open
                    self._save_last_cache_path(mode, cache_path)
        
        except Exception as e:
            xbmc.log(f"[MyTV] Error generating background: {e}", xbmc.LOGERROR)
    
    def _save_last_cache_path(self, mode, cache_path):
        """Save the last used cache path for a mode for instant loading."""
        try:
            last_cache_key = f"last_bg_mode_{mode}"
            self._cache.set(last_cache_key, cache_path)
        except Exception as e:
            xbmc.log(f"[MyTV] Error saving last cache path: {e}", xbmc.LOGWARNING)
    
    def _resize_to_cover(self, image, target_w, target_h):
        """Resize image to cover target dimensions (crop excess)."""
        img_ratio = image.width / image.height
        target_ratio = target_w / target_h
        
        if img_ratio > target_ratio:
            new_h = target_h
            new_w = int(new_h * img_ratio)
        else:
            new_w = target_w
            new_h = int(new_w / img_ratio)
        
        image = image.resize((new_w, new_h), Image.LANCZOS)
        
        left = (new_w - target_w) // 2
        top = (new_h - target_h) // 2
        return image.crop((left, top, left + target_w, top + target_h))
    
    def _generate_collage(self, images):
        """Generate a grid collage from images."""
        import requests
        from io import BytesIO
        
        # Determine grid size
        count = len(images)
        if count <= 4:
            cols, rows = 2, 2
        elif count <= 6:
            cols, rows = 3, 2
        elif count <= 9:
            cols, rows = 3, 3
        else:
            cols, rows = 4, 3
        
        # Canvas size (1920x1080)
        canvas_w, canvas_h = 1920, 1080
        cell_w = canvas_w // cols
        cell_h = canvas_h // rows
        
        canvas = Image.new('RGB', (canvas_w, canvas_h), (20, 20, 20))
        
        for i, (img_type, url) in enumerate(images[:cols*rows]):
            if i >= cols * rows:
                break
            
            try:
                img = self._download_image(url)
                if img:
                    # Resize to fill cell
                    img = img.convert('RGB')
                    img_ratio = img.width / img.height
                    cell_ratio = cell_w / cell_h
                    
                    if img_ratio > cell_ratio:
                        new_h = cell_h
                        new_w = int(new_h * img_ratio)
                    else:
                        new_w = cell_w
                        new_h = int(new_w / img_ratio)
                    
                    img = img.resize((new_w, new_h), Image.LANCZOS)
                    
                    # Crop to center
                    left = (new_w - cell_w) // 2
                    top = (new_h - cell_h) // 2
                    img = img.crop((left, top, left + cell_w, top + cell_h))
                    
                    # Paste to canvas
                    col = i % cols
                    row = i // cols
                    canvas.paste(img, (col * cell_w, row * cell_h))
            except Exception as e:
                xbmc.log(f"[MyTV] Collage image error: {e}", xbmc.LOGWARNING)
        
        return canvas
    
    def _generate_mix_collage(self, images):
        """Generate creative mixed collage with varying sizes."""
        import random
        from io import BytesIO
        
        canvas_w, canvas_h = 1920, 1080
        canvas = Image.new('RGB', (canvas_w, canvas_h), (15, 15, 20))
        
        random.shuffle(images)
        
        # Define varying cell sizes for creative layout
        positions = [
            (0, 0, 640, 540),      # Large left top
            (0, 540, 640, 540),    # Large left bottom
            (640, 0, 480, 360),    # Medium center top
            (640, 360, 480, 360),  # Medium center middle
            (640, 720, 480, 360),  # Medium center bottom
            (1120, 0, 400, 270),   # Small right top 1
            (1520, 0, 400, 270),   # Small right top 2
            (1120, 270, 400, 270), # Small right mid 1
            (1520, 270, 400, 270), # Small right mid 2
            (1120, 540, 800, 540), # Large right bottom
        ]
        
        for i, (img_type, url) in enumerate(images[:len(positions)]):
            if i >= len(positions):
                break
            
            x, y, w, h = positions[i]
            
            try:
                img = self._download_image(url)
                if img:
                    img = img.convert('RGB')
                    
                    # Resize maintaining aspect ratio then crop
                    img_ratio = img.width / img.height
                    cell_ratio = w / h
                    
                    if img_ratio > cell_ratio:
                        new_h = h
                        new_w = int(new_h * img_ratio)
                    else:
                        new_w = w
                        new_h = int(new_w / img_ratio)
                    
                    img = img.resize((new_w, new_h), Image.LANCZOS)
                    
                    left = (new_w - w) // 2
                    top = (new_h - h) // 2
                    img = img.crop((left, top, left + w, top + h))
                    
                    canvas.paste(img, (x, y))
            except Exception as e:
                xbmc.log(f"[MyTV] Mix collage image error: {e}", xbmc.LOGWARNING)
        
        return canvas
    
    def _apply_effects(self, image):
        """Apply blur/glow effects based on settings."""
        # Blur
        if self.addon.getSetting('mytv_background_blur') == 'true':
            try:
                radius = int(self.addon.getSetting('mytv_background_blur_radius') or '5')
                image = image.filter(ImageFilter.GaussianBlur(radius=radius))
            except:
                pass
        
        # Glow (brightness + blur for glow effect)
        if self.addon.getSetting('mytv_background_glow') == 'true':
            try:
                from PIL import ImageEnhance
                intensity = int(self.addon.getSetting('mytv_background_glow_intensity') or '3')
                # Increase brightness
                enhancer = ImageEnhance.Brightness(image)
                image = enhancer.enhance(1 + (intensity * 0.05))
                # Subtle blur for glow
                image = image.filter(ImageFilter.GaussianBlur(radius=intensity))
            except:
                pass
        
        return image
    
    def _download_image(self, url):
        """Download image from URL and return PIL Image."""
        import requests
        from io import BytesIO
        
        try:
            # Handle special:// paths
            if url.startswith('special://'):
                local_path = xbmcvfs.translatePath(url)
                if xbmcvfs.exists(local_path):
                    return Image.open(local_path)
                return None
            
            # Download from URL
            response = requests.get(url, timeout=10)
            if response.status_code == 200:
                return Image.open(BytesIO(response.content))
        except Exception as e:
            xbmc.log(f"[MyTV] Download image error: {url} - {e}", xbmc.LOGWARNING)
        
        return None


def show_my_tv_dialog(addon, show_nav_sidebar=False, nav_position=0):
    dialog = MyTVDialog(
        "ai/DialogMyTV.xml",
        addon.getAddonInfo('path'),
        "Default",
        "1080i",
        show_nav_sidebar=show_nav_sidebar,
        nav_position=nav_position
    )
    dialog.doModal()
    del dialog
