import xbmcgui
import xbmcaddon
import xbmc
import time
import os
import sys
import urllib
import xbmcplugin
import xbmcgui
import xbmcaddon
import xbmc
import os
import json
import re
import hashlib
import gzip

pyv3 = False
if sys.version_info[0] == 2:
    import urllib2
    from urllib import urlopen
    from urllib2 import urlopen as urlopen2
    from urllib import quote, quote_plus, unquote, unquote_plus
    import cookielib
    from StringIO import StringIO
else:
    import xbmcvfs
    import urllib.request
    from urllib.request import urlopen
    from urllib.request import urlopen as urlopen2
    from urllib.parse import quote, quote_plus, unquote, unquote_plus
    import http.cookiejar as cookielib
    from io import BytesIO as StringIO
    pyv3 = True

class NoRedirectHandler(urllib.request.HTTPRedirectHandler if sys.version_info[0] >= 3 else urllib2.HTTPRedirectHandler):
    def http_error_302(self, req, fp, code, msg, headers):
        return headers
    http_error_301 = http_error_302
    http_error_303 = http_error_302
    http_error_307 = http_error_302

    http_error_307 = http_error_302

def unquote_c(string):
    if not string: return ""
    # Standard unquote plus fix for + being spaces
    try:
        return unquote_plus(string)
    except:
        return string

def translatePath(path):
    # Wrapper for xbmcvfs.translatePath / xbmc.translatePath
    return xbmcvfs.translatePath(path)

def PlayUrl(name, url, iconimage=None):
    # Standard User-Agent for consistency
    user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    
    # ZERO-TOUCH STRATEGY FOR LIVE STREAMS
    # Connecting to the redirector (load balancer) in Python counts as a "connection" on the server side.
    # This prevents Kodi from playing (Error 555 - Max Connections).
    # so we MUST NOT touch the network in Python for Live streams.
    # We pass the original URL to Kodi with the User-Agent, letting Kodi handle the full chain.
    if '/live/' in url or url.endswith('.ts'):
        xbmc.log('--- Live Stream detected: Bypassing Python resolution to avoid Session Lock.', 2)
        if '|' not in url:
            url += '|User-Agent=' + quote(user_agent)
        xbmc.log('--- Playing (Zero-Touch) "{0}". {1}'.format(name, url), 2)
        listitem = xbmcgui.ListItem(path=url)
        listitem.setArt({'icon': iconimage, 'thumb': iconimage})
        listitem.setInfo(type="Video", infoLabels={"mediatype": "movie", "Title": name})
        
        # DISABLING PROBING:
        # Prevent Kodi from opening a preliminary connection to check headers/codecs.
        # This ensures strictly ONE connection is made (for playback), preventing 555 errors.
        listitem.setContentLookup(False)
        listitem.setMimeType('video/mp2t')
        
        if iconimage is not None:
            try:
                listitem.setArt({'thumb': iconimage})
            except:
                listitem.setThumbnailImage(iconimage)
        xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
        return

    try:
        # Resolve redirects RECURSIVELY with COOKIES and UA (For Movies/VOD)
        
        cj = cookielib.CookieJar()
        max_redirects = 5
        current_redirects = 0
        resolved_url = url
        
        while current_redirects < max_redirects:
            try:
                if pyv3:
                    opener = urllib.request.build_opener(NoRedirectHandler, urllib.request.HTTPCookieProcessor(cj))
                    opener.addheaders = [('User-Agent', user_agent)]
                else:
                    opener = urllib2.build_opener(NoRedirectHandler, urllib2.HTTPCookieProcessor(cj))
                    opener.addheaders = [('User-Agent', user_agent)]
                
                response = opener.open(resolved_url)
                
                # Check for redirect in headers (since we intercepted it)
                if hasattr(response, 'get'):
                    new_url = response.get('Location') or response.get('location')
                    if new_url:
                        if new_url == resolved_url:
                            break
                        resolved_url = new_url
                        current_redirects += 1
                        xbmc.log('--- Redirect {0}: {1}'.format(current_redirects, resolved_url), 2)
                    else:
                        break # No redirect header
                elif hasattr(response, 'info'): # Fallback for some urllib versions
                     info = response.info()
                     if hasattr(info, 'get'):
                         new_url = info.get('Location') or info.get('location')
                         if new_url:
                             resolved_url = new_url
                             current_redirects += 1
                             continue

                # Close the connection if it's a real response object to free the slot
                if hasattr(response, 'close'):
                    response.close()
                
                # If we got a 200 OK (no redirect), we are done.
                if not new_url:
                    break

            except Exception as e:
                # If 555 or other error, break and try current resolved_url
                xbmc.log('--- HTTP Error / End of chain: {0}'.format(str(e)), 2)
                break

        # Format cookies for Kodi
        cookie_string = ""
        try:
            cookie_string = "; ".join(["{}={}".format(cookie.name, cookie.value) for cookie in cj])
        except: pass

        # Append headers to final URL for Kodi
        if resolved_url:
            url = resolved_url
            if '|' not in url:
                url += '|User-Agent=' + quote(user_agent)
                if cookie_string:
                    url += '&Cookie=' + quote(cookie_string)
            xbmc.log('--- Final Resolved URL: {0}'.format(url), 2)
            
    except Exception as general_e:
        xbmc.log('--- Error Resolving URL: {0} - {1}'.format(url, str(general_e)), 2)
        pass

    xbmc.log('--- Playing "{0}". {1}'.format(name, url), 2)
    listitem = xbmcgui.ListItem(path=url)
    listitem.setArt({'icon': iconimage, 'thumb': iconimage})
    listitem.setInfo(type="Video", infoLabels={
                     "mediatype": "movie", "Title": name})
    if iconimage is not None:
        try:
            listitem.setArt({'thumb': iconimage})
        except:
            listitem.setThumbnailImage(iconimage)
    xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)

def traslatePath(path):
    if pyv3:
        return xbmcvfs.translatePath(path)
    else:
        return xbmc.translatePath(path)

def installOPENaddon(IDdoADDON, news, sure):    
    pathTOaddon = os.path.join(traslatePath('special://home/addons'), IDdoADDON)

    if not os.path.exists(pathTOaddon)==True:
        xbmc.executebuiltin('InstallAddon(%s)' % (IDdoADDON))
        xbmc.executebuiltin('SendClick(11)'), time.sleep(0), xbmcgui.Dialog().ok("Add-on Install", news)
    else:
        pass
    if os.path.exists(pathTOaddon)==True:
        res = xbmcgui.Dialog().yesno("Tutto on Demand", sure)
        if res: xbmc.executebuiltin('RunAddon(%s)' % (IDdoADDON))

# --- FIX CODIFICA CARATTERI ---
# --- FIX CODIFICA CARATTERI ---
def FixString(text):
    if not text: return ""
    
    # Unify text to unicode (Py2) or str (Py3) for processing
    current = text
    if not pyv3: # Python 2
        if isinstance(current, str):
            try: current = current.decode('utf-8')
            except: 
                try: current = current.decode('latin-1')
                except: pass
    
    # Loop per gestire mojibake multipli (es. utf8 -> cp1252 -> utf8 -> cp1252)
    # Tenta di correggere doppio/triplo encoding (es. Ã¨ -> è, ÃƒÂ -> à)
    for _ in range(3): # Prova fino a 3 livelli
        try:
            # Usa CP1252 invece di latin-1 per coprire char come 'ƒ' (0x83)
            # In Py3: str -> bytes(cp1252) -> str(utf8)
            # In Py2: unicode -> str(cp1252) -> unicode(utf8)
            decoded = current.encode('cp1252').decode('utf-8')
            if decoded == current: break
            current = decoded
        except:
            try:
                # Fallback a latin-1
                decoded = current.encode('latin-1').decode('utf-8')
                if decoded == current: break
                current = decoded
            except:
                break # Stop se non decodificabile

    # Return correct type
    if not pyv3: # Python 2: return utf-8 encoded str
        if isinstance(current, unicode):
            return current.encode('utf-8')
    return current

def unquote_c(value):
    if not value: return ""
    try:
        if pyv3:
            # Unquote e poi fix encoding
            res = unquote(value)
            return FixString(res)
        else:
            return unquote(value.encode('utf-8')).decode('utf-8')
    except:
        return value

def unquote_plus_c(value):
    if not value: return ""
    try:
        if pyv3:
            res = unquote_plus(value)
            return FixString(res)
        else:
            return unquote_plus(value.encode('utf-8')).decode('utf-8')
    except:
        return value

def GetUrl(url):
    # Funzione helper per scaricare URL con supporto GZIP automatico
    req_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
                   'Accept-Encoding': 'gzip'}
    
    try:
        if pyv3:
            req = urllib.request.Request(url, headers=req_headers)
            response = urlopen2(req)
        else:
            req = urllib2.Request(url, headers=req_headers)
            response = urlopen2(req)
            
        content = response.read()
        
        # Check for GZIP encoding
        is_gzipped = False
        if response.info().get('Content-Encoding') == 'gzip':
            is_gzipped = True
        
        # Fallback check on magic bytes (1f 8b) if header is missing but content is gzipped
        if not is_gzipped and len(content) > 2:
            # Py3 bytes index returns int, Py2 returns str
            b1 = content[0]
            b2 = content[1]
            if pyv3:
                if b1 == 0x1f and b2 == 0x8b: is_gzipped = True
            else:
                if ord(b1) == 0x1f and ord(b2) == 0x8b: is_gzipped = True

        if is_gzipped:
            try:
                buf = StringIO(content)
                f = gzip.GzipFile(fileobj=buf)
                content = f.read()
            except Exception as e:
                xbmc.log("IPTVAddons: GZIP Decompression Failed: " + str(e), xbmc.LOGERROR)

        return content
        
    except Exception as e:
        xbmc.log("IPTVAddons: Error downloading " + url + " - " + str(e), xbmc.LOGERROR)
        return None

# --- CONTROLLO VALIDITA' CACHE (24 Ore) ---
def IsCacheValid(file_path, seconds=86400):
    try:
        if os.path.exists(file_path):
            # Controlla se il file è più giovane di "seconds" (Def: 24 ore)
            if (time.time() - os.path.getmtime(file_path)) < seconds:
                return True
    except:
        pass
    return False
# ------------------------------------------

AddonID = 'plugin.video.iptvaddons'
Addon = xbmcaddon.Addon(AddonID)
AddonName = Addon.getAddonInfo("name")
icon = Addon.getAddonInfo('icon')

addonDir = Addon.getAddonInfo('path')

libDir = os.path.join(addonDir, 'resources', 'lib')
sys.path.insert(0, libDir)
if pyv3:
    import common3 as common
else:
    import common

addon_data_dir = os.path.join(traslatePath(
    "special://userdata/addon_data"), AddonID)
addon_extra_data_dir = os.path.join(traslatePath(
    "special://userdata/addon_data"), 'pvr.iptvsimple')
addon_oscam_config_dir = os.path.join(traslatePath(
    "special://userdata/addon_data"), 'service.softcam.oscam', 'config')
if not os.path.exists(addon_data_dir):
    os.makedirs(addon_data_dir)

playlistsFile = os.path.join(addon_data_dir, "playLists.txt")
codelistsFile = os.path.join(addon_extra_data_dir, "settings.xml")
oscamlistFile = os.path.join(addon_oscam_config_dir, 'oscam.server')
tmpListFile = os.path.join(addon_data_dir, 'tempList.txt')
favoritesFile = os.path.join(addon_data_dir, 'favorites.txt')
code = 'none'
secure = 'no so'
sufx = 'none'
#sitesource = 'http://ipset.in/u/'
#siteimg = 'http://ipset.in/img/'
sitesource = Addon.getSetting('base_url')
if not sitesource: sitesource = 'https://md1.qp1.in/u/'
if not sitesource.endswith('/'): sitesource += '/'

siteimg = Addon.getSetting('image_url')
if not siteimg: siteimg = 'https://md1.qp1.in/img/'
if not siteimg.endswith('/'): siteimg += '/'

if not (os.path.isfile(favoritesFile)):
    f = open(favoritesFile, 'w')
    f.write('[]')
    f.close()


def read_from_file(path, silent=False):
    try:
        f = open(path, 'r')
        r = f.read()
        f.close()
        return str(r)
    except:
        if not silent:
            print("Could not read from " + path)
        return None


def getLocaleString(id):
    return Addon.getLocalizedString(id).encode('utf-8')


def Menu():
    msg = u''
    if pyv3: msg = ''
    try:
        for line in urlopen(sitesource + code + "/i.txt"):
            if pyv3:
                msg += line.decode("utf-8");
            else:
                msg += line
    except:
        xbmc.executebuiltin('Notification(IPTVAddons, Errore Connessione Menu, 3000, ' + icon + ')')
        pass
    
    # Imposta vista Thumbnails o List
    xbmcplugin.setContent(int(sys.argv[1]), 'files')
        
    icon = siteimg + 'primafila.jpg'
    back = siteimg + 'primafilaback.jpg'
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Novita! Stream4Me"),
           sitesource, 60, siteimg + 'lente.png')
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Cerca Film e Serie"),
           sitesource, 59, siteimg + 'lente.png', background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Primafila per Anno"), sitesource +
           code + "/pri.json", 61, siteimg + 'skyprimafila.jpg', background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Primafila Elenco"), sitesource +
           code + "/prir.json", 1, siteimg + 'popcorn.png', background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Film per Genere"),
           sitesource, 57, siteimg + 'tickets.png', background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Serie TV"), sitesource,
           58, siteimg + 'tv_series.png', background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Serie per Lettera"), sitesource,
           63, siteimg + 'folder_tv.png', background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Oggi Al Cinema"), sitesource +
           code + "/ogg.json", 1, siteimg + 'dvdpirata.png', background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Gruppi"),
           sitesource, 54, icon, background=back, descrizione=msg)
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Indice"), sitesource,
           55, siteimg + 'indicef.png', background=back, descrizione=msg)
    icon = siteimg + 'live.jpg'
    back = siteimg + 'liveback.jpg'
    AddDir("[COLOR white][B]{0}[/B][/COLOR]".format("Live"),
           sitesource, 56, icon, background=back, descrizione=msg)


def CercaFilm():
    kcode = GetKeyboardText("Inserisci almeno 3 lettere", "")
    if len(kcode) < 3: return
    
    # Imposta vista mista
    xbmcplugin.setContent(int(sys.argv[1]), 'movies')
    
    icon = siteimg + 'primafila.jpg'
    back = siteimg + 'primafilaback.jpg'
    
    # URL di ricerca pulito
    clean_query = quote(kcode.replace(" ", "_"))
    cerca_film = sitesource + code + "/jcercafilm/" + clean_query
    
    # Prima mostra i film (gestito da JsonCategory con paginazione)
    JsonCategory(cerca_film)
    
    # Poi le serie, CON PAGINAZIONE LOCALE
    try:
        page_s = int(params.get("page_s", "1")) 
        use_cache_s = params.get("use_cache_s", "false") == "true"
    except:
        page_s = 1
        use_cache_s = False

    cerca_serie_url = sitesource + code + "/listacercaserie/" + quote(kcode.replace(" ", "_") + '.txt')
    # Hash della query per cache univoca serie
    query_hash = hashlib.md5(kcode.encode('utf-8')).hexdigest()
    cache_file = os.path.join(addon_data_dir, "search_series_" + query_hash + ".json")
    items_s = []

    # Carica cache serie se esiste ed è valida (24h)
    if IsCacheValid(cache_file):
        try:
            with open(cache_file, 'r') as f:
                items_s = json.load(f)
        except: pass
    
    # Scarica se non in cache o scaduta
    if not items_s:
        try:
            for line in urlopen(cerca_serie_url):
                line_dec = line.decode('utf-8').strip()
                if not line_dec: continue
                parts = line_dec.split(';')
                if len(parts) >= 4:
                    msgin = parts[4] if len(parts) > 4 else ""
                    items_s.append({
                        'urln': parts[0],
                        'nomem': FixString(parts[1]),
                        'icon': parts[2],
                        'back': parts[3].strip(),
                        'msg': FixString(msgin)
                    })
            if items_s: # Salva solo se abbiamo trovato qualcosa
                with open(cache_file, 'w') as f: json.dump(items_s, f)
        except: pass

    # Paginazione Serie
    limit = 200
    start = (page_s - 1) * limit
    end = start + limit
    
    # Separatore visivo se ci sono serie
    if items_s:
        AddDir("[COLOR blue]--- SERIE TV (Pagina %d) ---[/COLOR]" % page_s, "", 99, "", isFolder=False)

    for item in items_s[start:end]:
        AddDir("[COLOR white][B]{0}[/B][/COLOR]".format(item['nomem']), sitesource + code + "/jcatserie/" +
               item['urln'], 1, item['icon'], background=item['back'], descrizione=item['msg'])


def FilmGeneri():
    xbmcplugin.setContent(int(sys.argv[1]), 'files')
    ProcessTextList(sitesource + code + "/jcatfilm/listin.txt", "generi_cache.json", 57)

def SerieTv():
    # Imposta vista Serie TV per abilitare locandine, trame, ecc.
    xbmcplugin.setContent(int(sys.argv[1]), 'tvshows')

    try:
        page = int(params.get("page", "1"))
    except:
        page = 1

    cache_file = os.path.join(addon_data_dir, "series_json_cache.json")
    data = []

    # 1. CONTROLLA CACHE VALIDA (1 SETTIMANA = 604800s)
    if IsCacheValid(cache_file, 604800):
        try:
            with open(cache_file, 'r') as f:
                data = json.load(f)
        except:
            pass
    
    # 2. Se cache vuota, scaduta o file non esiste, scarica
    if not data:
        try:
            xbmc.executebuiltin('Notification(IPTVAddons, Aggiornamento Lista Serie..., 2000, ' + icon + ')')
            content = GetUrl(sitesource + code + "/jcatserie/listjson.gz")
            if content:
                if pyv3 and isinstance(content, bytes): content = content.decode('utf-8')
                data = json.loads(content)
            
            if data:
                with open(cache_file, 'w') as f:
                    json.dump(data, f)
        except:
            xbmc.executebuiltin('Notification(IPTVAddons, Errore Download Serie, 3000, ' + icon + ')')
            return

    # 3. Paginazione Locale
    limit = 200
    start = (page - 1) * limit
    end = start + limit
    
    # --- NAVIGAZIONE: INDIETRO + MENU ---
    # Tasto Home (sempre o solo pag > 1? Mettiamolo pulsante rosso)
    # --- NAVIGAZIONE: INDIETRO + MENU ---
    # Sostituito "Torna al menu principale" con "Torna Indietro" su richiesta utente
    u_back = sys.argv[0] + "?mode=99"
    liz = xbmcgui.ListItem("[COLOR red][B]*** TORNA AL MENU PRECEDENTE ***[/B][/COLOR]")
    liz.setArt({'icon': siteimg + 'indicef.png'})
    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_back, listitem=liz, isFolder=False)

    if page > 1:
        prev_page = page - 1
        u_prev = sys.argv[0] + "?url=&mode=58&name=Prev&page=" + str(prev_page) + "&use_cache=true"
        liz = xbmcgui.ListItem("[COLOR yellow][B]<< PAGINA PRECEDENTE ({0})[/B][/COLOR]".format(prev_page))
        liz.setArt({'icon': siteimg + 'prev.png'}) 
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_prev, listitem=liz, isFolder=True)
    # --------------------------------------

    current_slice = data[start:end]

    for item in current_slice:
        msg = FixString(item.get("msgin", ""))
        nomem = FixString(item.get("nomem", "Sconosciuto"))
        
        AddDir("[COLOR white][B]{0}[/B][/COLOR]".format(nomem), sitesource + code + "/jcatserie/" +
               item["urln"], 1, item["icon"], background=item["back"], descrizione=msg)
    
    # --- NAVIGAZIONE: AVANTI ---
    if end < len(data):
        next_page = page + 1
        u_next = sys.argv[0] + "?url=&mode=58&name=Next&page=" + str(next_page) + "&use_cache=true"
        liz = xbmcgui.ListItem("[COLOR yellow][B]PAGINA SUCCESSIVA ({0}) >>[/B][/COLOR]".format(next_page))
        liz.setArt({'icon': siteimg + 'next.png'})
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_next, listitem=liz, isFolder=True)


def Categorie():
    xbmcplugin.setContent(int(sys.argv[1]), 'files')
    ProcessTextList(sitesource + code + "/jgruppic/listin.txt", "cats_cache.json", 54, is_cat=True)

def SubCategorie(url):
    xbmcplugin.setContent(int(sys.argv[1]), 'files')
    url_hash = hashlib.md5(url.encode('utf-8')).hexdigest()
    cache_name = "subcat_%s.json" % url_hash
    ProcessTextList(url, cache_name, 53, is_cat=True, pass_url=True)

def Alfabetico():
    xbmcplugin.setContent(int(sys.argv[1]), 'files')
    ProcessTextList(sitesource + code + "/jlisteex/listin.txt", "alfa_cache.json", 55)

def Live():
    xbmcplugin.setContent(int(sys.argv[1]), 'files') # o 'tvchannels' se supportato
    ProcessTextList(sitesource + code + "/jgruppilive/listin.txt", "live_cache.json", 56, is_live=True)

# --- NUOVA FUNZIONE GENERICA PER PAGINARE LISTE .TXT ---
def ProcessTextList(url, cache_filename, mode_id, is_cat=False, is_live=False, pass_url=False):
    try:
        page = int(params.get("page", "1"))
    except:
        page = 1

    cache_file = os.path.join(addon_data_dir, cache_filename)
    items = []

    # 1. Carica Cache (Se valida < 24h)
    if IsCacheValid(cache_file):
        try:
            with open(cache_file, 'r') as f:
                items = json.load(f)
        except: pass

    # 2. Scarica e Parsa se cache mancante o scaduta
    if not items:
        try:
            content = GetUrl(url)
            if content:
                if pyv3 and isinstance(content, bytes): content = content.decode('utf-8')
                
                for line in content.splitlines():
                    # line is already valid str in Py3 after decode, or str in Py2
                    line_dec = line.strip()
                    if not line_dec: continue
                    parts = line_dec.split(';')
                    
                    item = {}
                    if len(parts) >= 2:
                        item['urln'] = parts[0]
                        item['nomem'] = FixString(parts[1].strip() if not is_cat else parts[2])
                        if is_cat and len(parts) >= 2: item['urln'] = parts[1] 
                        
                        item['icon'] = ""
                        item['back'] = ""
                        
                        if is_live: 
                            pass 
                        elif is_cat:
                            if len(parts) > 3: item['icon'] = parts[3]
                            if len(parts) > 4: item['back'] = parts[4].strip()
                        else:
                            if len(parts) > 2: item['icon'] = parts[2]
                            if len(parts) > 3: item['back'] = parts[3].strip()
                        
                        items.append(item)
            if items:
                with open(cache_file, 'w') as f: json.dump(items, f)
        except: 
            xbmc.executebuiltin('Notification(IPTVAddons, Errore Download Lista Testo, 3000, ' + icon + ')')
            return

    limit = 200
    start = (page - 1) * limit
    end = start + limit
    
    # --- NAVIGAZIONE: INDIETRO + MENU ---
    u_back = sys.argv[0] + "?mode=99"
    liz = xbmcgui.ListItem("[COLOR red][B]*** TORNA AL MENU PRECEDENTE ***[/B][/COLOR]")
    liz.setArt({'icon': siteimg + 'indicef.png'})
    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_back, listitem=liz, isFolder=False)

    if page > 1:
        prev_page = page - 1
        url_param = quote_plus(url) if pass_url else ""
        u_prev = sys.argv[0] + "?mode=" + str(mode_id) + "&page=" + str(prev_page) + "&use_cache=true" + "&url=" + url_param
        liz = xbmcgui.ListItem("[COLOR yellow][B]<< PAGINA PRECEDENTE ({0})[/B][/COLOR]".format(prev_page))
        liz.setArt({'icon': siteimg + 'prev.png'})
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_prev, listitem=liz, isFolder=True)
    # --------------------------------------
    
    for i in items[start:end]:
        u_val = i['urln']
        target_mode = 1
        if is_cat:
            urlz, sufx = u_val.split('.')
            if urlz == 'listin': target_mode = 53
            else: target_mode = 1
        elif is_live:
            target_mode = 1
        
        final_url = sitesource + code
        if is_live: final_url += "/jgruppilive/" + u_val
        elif is_cat: final_url += "/jgruppic/" + u_val
        elif mode_id == 57: final_url += "/jcatfilm/" + u_val
        elif mode_id == 55: final_url += "/jlisteex/" + u_val
        
        AddDir("[COLOR white][B]{0}[/B][/COLOR]".format(i['nomem']), final_url, target_mode, i['icon'], background=i['back'])

    if end < len(items):
        next_page = page + 1
        url_param = quote_plus(url) if pass_url else ""
        u = sys.argv[0] + "?mode=" + str(mode_id) + "&page=" + str(next_page) + "&use_cache=true" + "&url=" + url_param
        liz = xbmcgui.ListItem("[COLOR yellow][B]PAGINA SUCCESSIVA ({0}) >>[/B][/COLOR]".format(next_page))
        liz.setArt({'icon': siteimg + 'next.png'})
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)


def GetChoice(choiceTitle, fileTitle, urlTitle, choiceFile, choiceUrl, choiceNone=None, fileType=1, fileMask=None, defaultText=""):
    choice = ''
    choiceList = [getLocaleString(choiceFile), getLocaleString(choiceUrl)]
    if choiceNone != None:
        choiceList.append(getLocaleString(choiceNone))
    method = GetSourceLocation(getLocaleString(choiceTitle), choiceList)
    if method == 0:
        if not defaultText.startswith('http'):
            defaultText = ""
        choice = GetKeyboardText(getLocaleString(
            fileTitle), defaultText).strip()
    elif method == 1:
        if defaultText.startswith('http'):
            defaultText = ""
        choice = xbmcgui.Dialog().browse(fileType, getLocaleString(urlTitle),
                                         'myprograms', fileMask, False, False, defaultText)
    return choice


def JsonCategory(url):
    # Imposta vista Movies per mostrare trame e info
    xbmcplugin.setContent(int(sys.argv[1]), 'movies')
    
    tmpList = []
    try:
        page = int(params.get("page", "1"))
    except:
        page = 1

    # Cache univoca per URL
    url_hash = hashlib.md5(url.encode('utf-8')).hexdigest()
    cache_file = os.path.join(addon_data_dir, "json_" + url_hash + ".json")
    list_items = []

    # 1. Carica Cache (Se valida < 24h)
    if IsCacheValid(cache_file):
        try:
            with open(cache_file, 'r') as f:
                list_items = json.load(f)
        except: pass
    
    # 2. Scarica se vuoto o scaduto
    if not list_items:
        try:
            content = GetUrl(url)
            if content:
                if pyv3 and isinstance(content, bytes): content = content.decode('utf-8')
                list_items = json.loads(content)
                with open(cache_file, 'w') as f: json.dump(list_items, f)
        except:
            xbmc.log("IPTVAddons: Errore caricamento o JSON vuoto", xbmc.LOGERROR)
            xbmc.executebuiltin('Notification(IPTVAddons, Errore Caricamento Canali, 3000, ' + icon + ')')
            return

    # --- PAGINAZIONE CLIENT ---
    limit = 200
    start = (page - 1) * limit
    end = start + limit
    
    # --- NAVIGAZIONE: INDIETRO + MENU ---
    u_back = sys.argv[0] + "?mode=99"
    liz = xbmcgui.ListItem("[COLOR red][B]*** TORNA AL MENU PRECEDENTE ***[/B][/COLOR]")
    liz.setArt({'icon': siteimg + 'indicef.png'})
    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_back, listitem=liz, isFolder=False)

    if page > 1:
        prev_page = page - 1
        u_prev = sys.argv[0] + "?url=" + quote_plus(url) + "&mode=1&name=Prev&page=" + str(prev_page) + "&use_cache=true"
        liz = xbmcgui.ListItem("[COLOR yellow][B]<< PAGINA PRECEDENTE ({0})[/B][/COLOR]".format(prev_page))
        liz.setArt({'icon': siteimg + 'prev.png'})
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_prev, listitem=liz, isFolder=True)
    # ------------------------------

    current_slice = list_items[start:end]
    
    for channel in current_slice:
        iconimage = unquote(channel.get("stream_icon", ""))
        name = unquote_c(FixString(channel.get("name", "Unknown")))
        stream_url = unquote(channel.get("url", ""))
        bkg = unquote(channel.get("background", ""))
        desc = unquote_c(FixString(channel.get("descrizione", "")))
        
        AddDir(name, stream_url, 3, iconimage, isFolder=False, background=bkg, descrizione=desc)
        tmpList.append({"url": stream_url, "image": iconimage, "name": name})
        
    if end < len(list_items):
        next_page = page + 1
        u = sys.argv[0] + "?url=" + quote_plus(url) + "&mode=1&name=Next&page=" + str(next_page) + "&use_cache=true"
        liz = xbmcgui.ListItem("[COLOR yellow][B]PAGINA SUCCESSIVA ({0}) >>[/B][/COLOR]".format(next_page))
        liz.setArt({'icon': siteimg + 'next.png'})
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)

    common.SaveList(tmpListFile, tmpList)





def AddDir(name, url, mode, iconimage, logos="", index=-1, move=0, isFolder=True, background=None, descrizione=None):
    u = sys.argv[0]+"?url="+quote_plus(url)+"&mode="+str(mode)+"&name="+quote_plus(
        name)+"&iconimage="+quote_plus(iconimage)+"&logos="+quote_plus(logos)+"&index="+str(index)+"&move="+str(move)
    liz = xbmcgui.ListItem(name)
    liz.setArt({'icon': iconimage, 'thumb': iconimage})
    
    # --- MIGLIORAMENTO GRAFICO: INFO LABELS ---
    if descrizione == None:
        descrizione = name
    
    # Determina tipo contenuto per InfoLabels (Movie vs TVShow vs Episode)
    # Questo aiuta le skin a scegliere la vista giusta
    info_labels = {'title': name, 'plot': descrizione}
    
    # Se siamo in una lista di Film/Serie, proviamo a dare il media type corretto
    if 'jcatserie' in url: # Probabile episodio
        info_labels['mediatype'] = 'episode'
    elif 'movies' in str(sys.argv[0]) or mode == 3: # Film o Live
        if '/live/' in url or url.endswith('.ts'):
             info_labels['mediatype'] = 'video'
        else:
             info_labels['mediatype'] = 'movie'
    
    liz.setInfo(type="Video", infoLabels=info_labels)
    # -------------------------------------------

    listMode = 21  # Lists
    if background != None:
        liz.setProperty('fanart_image', background)
    # Patch per Live Strams: Forzare Thumbnail view o Info Wall se possibile
    if '/live/' in url:
        liz.setProperty('IsPlayable', 'true')
    if mode == 1 or mode == 2 or mode == 53:
        items = [		(getLocaleString(10026), 'XBMC.RunPlugin({0}?index={1}&mode=23)'.format(sys.argv[0], index)),
                   (getLocaleString(10028), 'XBMC.RunPlugin({0}?index={1}&mode=25)'.format(sys.argv[0], index))]
        if mode == 2:
            items.append((getLocaleString(
                10029), 'XBMC.RunPlugin({0}?index={1}&mode=26)'.format(sys.argv[0], index)))
    elif mode == 3:
        liz.setProperty('IsPlayable', 'true')
        liz.addContextMenuItems(items=[('{0}'.format(getLocaleString(
            10009)), 'XBMC.RunPlugin({0}?url={1}&mode=31&iconimage={2}&name={3})'.format(sys.argv[0], quote_plus(url), iconimage, name))])
    elif mode == 32:
        liz.setProperty('IsPlayable', 'true')
        items = [(getLocaleString(10010), 'XBMC.RunPlugin({0}?index={1}&mode=33)'.format(sys.argv[0], index)),
                 (getLocaleString(10026), 'XBMC.RunPlugin({0}?index={1}&mode=35)'.format(
                     sys.argv[0], index)),
                 (getLocaleString(10028), 'XBMC.RunPlugin({0}?index={1}&mode=37)'.format(sys.argv[0], index))]
        listMode = 38  # Favourits
    if mode == 1 or mode == 2 or mode == 32 or mode == 53:
        items += [(getLocaleString(10030), 'XBMC.RunPlugin({0}?index={1}&mode={2}&move=-1)'.format(sys.argv[0], index, listMode)),
                  (getLocaleString(10031), 'XBMC.RunPlugin({0}?index={1}&mode={2}&move=1)'.format(
                      sys.argv[0], index, listMode)),
                  (getLocaleString(10032), 'XBMC.RunPlugin({0}?index={1}&mode={2}&move=0)'.format(sys.argv[0], index, listMode))]
        
        # --- ADD TO LIBRARY CONTEXT MENU ---
        # --- ADD TO LIBRARY CONTEXT MENU ---
        # Relaxed check: allows any Mode 1 (Folder/Series) item. 
        # Ideally checks for .json but some URLs might be different.
        items.append(("[COLOR yellow]Aggiungi alla Libreria Kodi[/COLOR]", 
                      'XBMC.RunPlugin({0}?mode=65&name={1}&url={2}&iconimage={3})'.format(
                          sys.argv[0], quote_plus(name), quote_plus(url), quote_plus(iconimage))))
        xbmc.log("[IPTVAddons] Added 'Add to Library' context menu for: {0}".format(name), xbmc.LOGINFO)
        # -----------------------------------

        liz.addContextMenuItems(items)

    xbmcplugin.addDirectoryItem(handle=int(
        sys.argv[1]), url=u, listitem=liz, isFolder=isFolder)


def GetKeyboardText(title="", defaultText=""):
    keyboard = xbmc.Keyboard(defaultText, title)
    keyboard.doModal()
    text = "" if not keyboard.isConfirmed() else keyboard.getText()
    return text


def GetSourceLocation(title, list):
    dialog = xbmcgui.Dialog()
    answer = dialog.select(title, list)
    return answer


def AddFavorites(url, iconimage, name):
    favList = common.ReadList(favoritesFile)
    for item in favList:
        if item["url"].lower() == url.lower():
            xbmc.executebuiltin("Notification({0}, '{1}' {2}, 5000, {3})".format(
                AddonName, name, getLocaleString(10011), icon))
            return
    list = common.ReadList(tmpListFile)
    for channel in list:
        if channel["name"].lower() == name.lower():
            url = channel["url"].encode("utf-8")
            iconimage = channel["image"].encode("utf-8")
            break
    if not iconimage:
        iconimage = ""
    data = {"url": url.decode(
        "utf-8"), "image": iconimage, "name": name}
    favList.append(data)
    common.SaveList(favoritesFile, favList)
    xbmc.executebuiltin("Notification({0}, '{1}' {2}, 5000, {3})".format(
        AddonName, name, getLocaleString(10012), icon))


def ListFavorites():
    AddDir("[COLOR yellow][B]{0}[/B][/COLOR]".format(getLocaleString(10013)), "favorites", 34,
           os.path.join(addonDir, "resources", "images", "bright_yellow_star.png"), isFolder=False)
    list = common.ReadList(favoritesFile)
    i = 0
    for channel in list:
        AddDir(channel["name"].encode("utf-8"), channel["url"].encode("utf-8"),
               32, channel["image"].encode("utf-8"), index=i, isFolder=False)
        i += 1


def AddNewFavorite():
    chName = GetKeyboardText(getLocaleString(10014))
    if len(chName) < 1:
        return
    chUrl = GetKeyboardText(getLocaleString(10015))
    if len(chUrl) < 1:
        return
    image = GetChoice(10023, 10023, 10023, 10024, 10025, 10021, fileType=2)

    favList = common.ReadList(favoritesFile)
    for item in favList:
        if item["url"].lower() == chUrl.lower():
            xbmc.executebuiltin("Notification({0}, '{1}' {2}, 5000, {3})".format(
                AddonName, chName, getLocaleString(10011), icon))
            return

    data = {"url": chUrl, "image": image,
            "name": chName}

    favList.append(data)
    if common.SaveList(favoritesFile, favList):
        xbmc.executebuiltin("XBMC.Container.Refresh()")


def ChangeKey(index, listFile, key, title):
    list = common.ReadList(listFile)
    str = GetKeyboardText(getLocaleString(
        title), list[index][key].encode("utf-8"))
    if len(str) < 1:
        return
    list[index][key] = str
    if common.SaveList(listFile, list):
        xbmc.executebuiltin("XBMC.Container.Refresh()")


def ChangeChoice(index, listFile, key, choiceTitle, fileTitle, urlTitle, choiceFile, choiceUrl, choiceNone=None, fileType=1, fileMask=None):
    list = common.ReadList(listFile)
    if key == "logos":
        listUrl = list[index].get("url", "")
        if listUrl.endswith('.json'):
            return
    defaultText = list[index].get(key, "")
    str = GetChoice(choiceTitle, fileTitle, urlTitle, choiceFile, choiceUrl,
                    choiceNone, fileType, fileMask, defaultText.encode("utf-8"))
    if key == "url" and len(str) < 1:
        return
    elif key == "logos" and str.startswith('http') and not str.endswith('/'):
        str += '/'
    list[index][key] = str
    if common.SaveList(listFile, list):
        xbmc.executebuiltin("XBMC.Container.Refresh()")


def MoveInList(index, step, listFile):
    theList = common.ReadList(listFile)
    if index + step >= len(theList) or index + step < 0:
        return
    if step == 0:
        step = GetIndexFromUser(len(theList), index)
    if step < 0:
        tempList = theList[0:index + step] + [theList[index]] + \
            theList[index + step:index] + theList[index + 1:]
    elif step > 0:
        tempList = theList[0:index] + theList[index + 1:index +
                                              1 + step] + [theList[index]] + theList[index + 1 + step:]
    else:
        return
    common.SaveList(listFile, tempList)
    xbmc.executebuiltin("XBMC.Container.Refresh()")


def GetIndexFromUser(listLen, index):
    dialog = xbmcgui.Dialog()
    location = dialog.input(
        '{0} (1-{1})'.format(getLocaleString(10033), listLen), type=xbmcgui.INPUT_NUMERIC)
    if location is None or location == "":
        return 0
    try:
        location = int(location) - 1
        if location >= listLen or location < 0:
            return 0
    except:
        return 0
    return location - index


def PrimafilaPerAnno(url):
    xbmcplugin.setContent(int(sys.argv[1]), 'files')
    try:
        content = GetUrl(url)
        if content:
            if pyv3 and isinstance(content, bytes): content = content.decode('utf-8')
            items = json.loads(content)
        
        years = {}
        for item in items:
            name = unquote_c(FixString(item.get("name", "")))
            # Extract year from: (YYYY), - YYYY, or just YYYY at end
            # Regex Explanation:
            # 1. \((\d{4})\) -> Parentheses
            # 2. \s-\s?(\d{4}) -> Dash separator
            # 3. \b(\d{4})$ -> At end of string
            # We iterate matches to find the best candidate (usually the last one is the year)
            match = re.findall(r'\((\d{4})\)|\s-\s?(\d{4})|\b(\d{4})$', name)
            
            year = "Vari"
            if match:
                # match is a list of tuples due to groups. flatten it.
                # Example: [('', '2024', '')]
                for groups in match:
                    for g in groups:
                        if g: year = g # Take the last valid year found
            
            if not year or year == "Vari": 
                year = "Vari"
            
            if year not in years: years[year] = 0
            years[year] += 1
            
        # Sort years descending
        sorted_years = sorted(years.keys(), reverse=True)
        if "Vari" in sorted_years: # Move Vari to end
            sorted_years.remove("Vari")
            sorted_years.append("Vari")

        for year in sorted_years:
            count = years[year]
            name_label = "[COLOR white][B]Film {0}[/B][/COLOR] [COLOR yellow]({1})[/COLOR]".format(year, count)
            # Passa URL e Anno come parametri
            u = sys.argv[0] + "?url=" + quote_plus(url) + "&mode=62&name=" + str(year)
            
            liz = xbmcgui.ListItem(name_label)
            liz.setArt({'icon': siteimg + 'skyprimafila.jpg', 'thumb': siteimg + 'skyprimafila.jpg'})
            # Fix 404: Usiamo skyprimafila.jpg anche come back se manca primafilaback.jpg
            liz.setProperty('fanart_image', siteimg + 'skyprimafila.jpg')
            
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)

    except Exception as e:
         xbmc.executebuiltin('Notification(IPTVAddons, Errore Anni: ' + str(e) + ', 3000, ' + icon + ')')

def PrimafilaByYear(url, year):
    # Usa JsonCategory logic ma filtra per anno
    xbmcplugin.setContent(int(sys.argv[1]), 'movies')
    try: page = int(params.get("page", "1"))
    except: page = 1
    
    try:
        content = GetUrl(url)
        if content:
            if pyv3 and isinstance(content, bytes): content = content.decode('utf-8')
            all_items = json.loads(content)
        
        filtered_items = []
        for item in all_items:
            name = unquote_c(FixString(item.get("name", "")))
            # Regex improvement: match (YYYY), - YYYY, YYYY$
            match = re.findall(r'\((\d{4})\)|\s-\s?(\d{4})|\b(\d{4})$', name)
            item_year = "Vari"
            if match:
                for groups in match:
                    for g in groups:
                        if g: item_year = g # Take last valid year
            
            if str(item_year) == str(year):
                filtered_items.append(item)
                
        # --- LOGICA PAGINAZIONE ---
        limit = 200
        start = (page - 1) * limit
        end = start + limit
        
        u_back = sys.argv[0] + "?mode=99"
        liz = xbmcgui.ListItem("[COLOR red][B]*** TORNA AL MENU PRECEDENTE ***[/B][/COLOR]")
        liz.setArt({'icon': siteimg + 'indicef.png'})
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_back, listitem=liz, isFolder=False)

        if page > 1:
            prev_page = page - 1
            u_prev = sys.argv[0] + "?url=" + quote_plus(url) + "&mode=62&name=" + str(year) + "&page=" + str(prev_page)
            liz = xbmcgui.ListItem("[COLOR yellow][B]<< PAGINA PRECEDENTE ({0})[/B][/COLOR]".format(prev_page))
            liz.setArt({'icon': siteimg + 'prev.png'})
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_prev, listitem=liz, isFolder=True)
            
        current_slice = filtered_items[start:end]
        
        for channel in current_slice:
            iconimage = unquote(channel.get("stream_icon", ""))
            name = unquote_c(FixString(channel.get("name", "Unknown")))
            stream_url = unquote(channel.get("url", ""))
            bkg = unquote(channel.get("background", ""))
            desc = unquote_c(FixString(channel.get("descrizione", "")))
            
            AddDir(name, stream_url, 3, iconimage, isFolder=False, background=bkg, descrizione=desc)
            
        if end < len(filtered_items):
            next_page = page + 1
            u_next = sys.argv[0] + "?url=" + quote_plus(url) + "&mode=62&name=" + str(year) + "&page=" + str(next_page)
            liz = xbmcgui.ListItem("[COLOR yellow][B]PAGINA SUCCESSIVA ({0}) >>[/B][/COLOR]".format(next_page))
            liz.setArt({'icon': siteimg + 'next.png'})
            xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_next, listitem=liz, isFolder=True)

    except Exception as e:
         xbmc.executebuiltin('Notification(IPTVAddons, Errore Filtro Anno: ' + str(e) + ', 3000, ' + icon + ')')


def SerieTvMenuAZ():
    xbmcplugin.setContent(int(sys.argv[1]), 'files')
    
    # Load Cache
    cache_file = os.path.join(addon_data_dir, "series_json_cache.json")
    series_list = []
    if os.path.exists(cache_file):
        try:
            with open(cache_file, 'r') as f:
                series_list = json.load(f)
        except: pass
            
    # Group by First Letter
    letters = {}
    for item in series_list:
        # Use 'nomem' as key for name
        name = unquote_c(FixString(item.get("nomem", ""))).strip()
        if not name: continue
        
        # Gestione caratteri speciali o unicode
        try:
            first_char = name[0].upper()
            if not first_char.isalpha():
                first_char = '#'
        except:
            first_char = '#'
            
        if first_char not in letters:
            letters[first_char] = 0
        letters[first_char] += 1
        
    sorted_letters = sorted(letters.keys())

    # --- NAVIGAZIONE: INDIETRO ---
    u_back = sys.argv[0] + "?mode=99"
    liz = xbmcgui.ListItem("[COLOR red][B]*** TORNA AL MENU PRINCIPALE ***[/B][/COLOR]")
    liz.setArt({'icon': siteimg + 'indicef.png'})
    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_back, listitem=liz, isFolder=False)

    for letter in sorted_letters:
        count = letters[letter]
        name_label = "[COLOR white][B]Lettera {0}[/B][/COLOR] [COLOR yellow]({1})[/COLOR]".format(letter, count)
        
        # Mode 64 = SerieTvByLetter
        u = sys.argv[0] + "?mode=64&name=" + quote_plus(letter)
        
        liz = xbmcgui.ListItem(name_label)
        liz.setArt({'icon': siteimg + 'folder_tv.png', 'thumb': siteimg + 'folder_tv.png'})
        
        xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u, listitem=liz, isFolder=True)
        
    xbmcplugin.endOfDirectory(int(sys.argv[1]))

def SerieTvByLetter(letter):
    xbmcplugin.setContent(int(sys.argv[1]), 'tvshows')
    
    # Load Cache
    cache_file = os.path.join(addon_data_dir, "series_json_cache.json")
    series_list = []
    if os.path.exists(cache_file):
        try:
            with open(cache_file, 'r') as f:
                series_list = json.load(f)
        except: pass

    filtered_items = []
    for item in series_list:
        name = unquote_c(FixString(item.get("nomem", ""))).strip()
        if not name: continue
        
        try:
            first_char = name[0].upper()
            if not first_char.isalpha():
                first_char = '#'
        except:
            first_char = '#'
            
        if first_char == letter:
            filtered_items.append(item)
            
    # Sort alphabetically
    filtered_items.sort(key=lambda x: unquote_c(FixString(x.get("nomem", ""))).lower())

    # Back button
    u_back = sys.argv[0] + "?mode=63"
    liz = xbmcgui.ListItem("[COLOR red][B]*** TORNA AL MENU LETTERE ***[/B][/COLOR]")
    liz.setArt({'icon': siteimg + 'indicef.png'})
    xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=u_back, listitem=liz, isFolder=False)

    for tvshow in filtered_items:
        # Map correct keys from series_json_cache.json
        iconimage = unquote(tvshow.get("icon", ""))
        name = unquote_c(FixString(tvshow.get("nomem", "Unknown")))
        url = unquote(tvshow.get("urln", ""))
        bkg = unquote(tvshow.get("back", ""))
        desc = unquote_c(FixString(tvshow.get("msgin", "")))
        
        # Mode 1 is JsonCategory which handles the episodes/seasons json
        AddDir(name, sitesource + code + "/jcatserie/" + url, 1, iconimage, isFolder=True, background=bkg, descrizione=desc)
        
    xbmcplugin.endOfDirectory(int(sys.argv[1]))


def get_params():
    param = {}
    if len(sys.argv) >= 3:
        paramstring = sys.argv[2]
        if len(paramstring) >= 2:
            cleanedparams = paramstring.replace('?', '')
            if (paramstring[len(paramstring)-1] == '/'):
                paramstring = paramstring[0:len(paramstring)-2]
            pairsofparams = cleanedparams.split('&')
            for i in range(len(pairsofparams)):
                splitparams = {}
                splitparams = pairsofparams[i].split('=')
                if (len(splitparams)) == 2:
                    param[splitparams[0].lower()] = splitparams[1]
    return param


if os.path.exists(addon_oscam_config_dir):
    u = 'none'
    p = 'none'
    allsettings = read_from_file(oscamlistFile)
    match = re.compile('(.*)').findall(allsettings)
    for line in match:
        if line[0:1] != "#":
            if line.find("user") >= 0:
                line = re.sub(" ", "", line)
                line = re.sub("\n", "", line)
                e, u = line.split('=')
            if line.find("password") >= 0:
                line = re.sub(" ", "", line)
                line = re.sub("\n", "", line)
                e, p = line.split('=')
    code = u + p
elif os.path.exists(addon_extra_data_dir):
    allsettings = read_from_file(codelistsFile)
    match = re.compile('setting id="(.+?)" value="(.+?)"').findall(allsettings)
    for id, value in match:
        if id == "m3uUrl":
            if value != '':
                h, n, v, u, o, i = value.split('/')
                code = o

if code == "none" and os.path.exists(addon_extra_data_dir):
    allsettings = read_from_file(codelistsFile)
    match = re.compile('>(.+?)<').findall(allsettings)
    for value in match:
        if value[-3:] == 'm3u':
            h, n, v, u, o, i = value.split('/')
            code = o

            code = o

            code = o

def AddToLibrary(name, url, iconimage):
    xbmc.log("[IPTVAddons] AddToLibrary Triggered. Name: {0}, URL: {1}".format(name, url), xbmc.LOGINFO)
    
    # 1. Sanitize Series Name for Folder
    # Rimuove tags Kodi e caratteri invalidi
    clean_name = re.sub(r'\[.*?\]', '', name)
    valid_name = "".join([c for c in clean_name if c.isalpha() or c.isdigit() or c in ' ._-']).strip()
    if not valid_name: valid_name = "Serie_Sconosciuta"
    
    # 2. Definisce Path Libreria
    # library_path = special://userdata/addon_data/plugin.video.iptvaddons/Library/TVShows/
    library_path = os.path.join(addon_data_dir, "Library", "TVShows")
    series_path = os.path.join(library_path, valid_name)
    
    xbmc.log("[IPTVAddons] Library Path: {0}".format(library_path), xbmc.LOGINFO)
    
    if not xbmcvfs.exists(library_path):
        xbmcvfs.mkdirs(library_path)
    
    # Crea cartella serie se non esiste
    if not xbmcvfs.exists(series_path):
        xbmcvfs.mkdirs(series_path)
        xbmc.log("[IPTVAddons] Created series path: {0}".format(series_path), xbmc.LOGINFO)
        
    xbmcgui.Dialog().notification("IPTV Addons", "Recupero episodi in corso...", xbmcgui.NOTIFICATION_INFO, 3000)

    # 3. Scarica JSON Episodi
    # Usa url passato (es: 312312.json) ma deve ricostruire URL completo se relativo
    if url.startswith('http'):
        target_url = url
    else:
        # Se relativo, assume sia jcatserie
        target_url = sitesource + code + "/jcatserie/" + url
        
    xbmc.log("[IPTVAddons] Downloading content from: {0}".format(target_url), xbmc.LOGINFO)

    try:
        content = GetUrl(target_url)
        if content:
             if pyv3 and isinstance(content, bytes): content = content.decode('utf-8')
             episodes = json.loads(content)
             xbmc.log("[IPTVAddons] JSON loaded. Episodes count: {0}".format(len(episodes)), xbmc.LOGINFO)
             
             count = 0
             for ep in episodes:
                 ep_name = unquote_c(FixString(ep.get("name", "Episodio")))
                 ep_url = unquote(ep.get("url", "")) # Stream URL
                 
                 # 4. Parse Stagione/Episodio (SxxExx)
                 # Cerca pattern: S01E01, 1x01, ecc
                 match = re.search(r'[S|s](\d+)[E|e](\d+)|(\d+)x(\d+)', ep_name)
                 
                 strm_filename = ""
                 if match:
                     # Estrai gruppi. match.groups() avrà 4 elementi, alcuni None
                     # Grp 1,2 per SxxExx. Grp 3,4 per NxN
                     groups = match.groups()
                     if groups[0] and groups[1]: 
                         s, e = int(groups[0]), int(groups[1])
                     elif groups[2] and groups[3]:
                         s, e = int(groups[2]), int(groups[3])
                     else:
                         s, e = 0, 0
                     
                     strm_filename = "S{:02d}E{:02d}.strm".format(s, e)
                 else:
                     # Fallback: Usa nome file pulito
                     clean_ep_name = "".join([c for c in ep_name if c.isalpha() or c.isdigit() or c in ' ._-']).strip()
                     # Accorcia filename se troppo lungo
                     if len(clean_ep_name) > 50: clean_ep_name = clean_ep_name[:50]
                     strm_filename = clean_ep_name + ".strm"
                 
                 # 5. Crea file .strm
                 # Contenuto: URL Plugin (Mode 3)
                 # plugin://plugin.video.iptvaddons/?mode=3&url=...
                 plugin_url = "plugin://plugin.video.iptvaddons/?mode=3&url={0}&name={1}&iconimage={2}".format(
                     quote_plus(ep_url), quote_plus(ep_name), quote_plus(iconimage))
                 
                 file_path = os.path.join(series_path, strm_filename)
                 with open(file_path, 'w') as f:
                     f.write(plugin_url)
                 count += 1
                 
             xbmc.log("[IPTVAddons] Successfully added {0} episodes.".format(count), xbmc.LOGINFO)
             xbmcgui.Dialog().notification("IPTV Addons", "Aggiunti " + str(count) + " episodi alla libreria!", xbmcgui.NOTIFICATION_INFO, 5000)
        else:
            xbmc.log("[IPTVAddons] Error: Content empty or None.", xbmc.LOGERROR)
            xbmcgui.Dialog().notification("IPTV Addons", "Errore: Lista episodi vuota o non trovata.", xbmcgui.NOTIFICATION_ERROR, 5000)
             
    except Exception as e:
        import traceback
        xbmc.log("IPTVAddons: Errore Aggiunta Libreria: " + str(e) + "\n" + traceback.format_exc(), xbmc.LOGERROR)
        xbmcgui.Dialog().notification("IPTV Addons", "Errore durante l'aggiunta", xbmcgui.NOTIFICATION_ERROR, 5000)

params = get_params()
url = None
logos = None
name = None
mode = None
iconimage = None
description = None

try:
    url = unquote_plus(params["url"])
except:
    pass
try:
    logos = unquote_plus(params.get("logos", ''))
except:
    pass
try:
    name = unquote_plus(params["name"])
except:
    pass
try:
    iconimage = unquote_plus(params["iconimage"])
except:
    pass
try:
    mode = int(params["mode"])
except:
    pass
try:
    index = int(params["index"])
except:
    pass
try:
    move = int(params["move"])
except:
    pass


if mode == None:
    Menu()
elif mode == 55:
    Alfabetico()
elif mode == 54:
    Categorie()
elif mode == 53:
    SubCategorie(url)
elif mode == 56:
    Live()
elif mode == 57:
    FilmGeneri()
elif mode == 58:
    SerieTv()
elif mode == 59:
    CercaFilm()
elif mode == 1:
    JsonCategory(url)
elif mode == 3 or mode == 32:
    PlayUrl(name, url, iconimage)
elif mode == 21:
    MoveInList(index, move, playlistsFile)
elif mode == 23:
    ChangeKey(index, playlistsFile, "name", 10004)
elif mode == 24:
    ChangeChoice(index, playlistsFile, "url", 10002, 10005,
                 10006, 10016, 10017, None, 1, '.json|.m3u|.m3u8')
elif mode == 25:
    ChangeChoice(index, playlistsFile, "image", 10022,
                 10022, 10022, 10024, 10025, 10021, 2)
elif mode == 30:
    ListFavorites()
elif mode == 31:
    AddFavorites(url, iconimage, name)
elif mode == 34:
    AddNewFavorite()
elif mode == 35:
    ChangeKey(index, favoritesFile, "name", 10014)
elif mode == 36:
    ChangeKey(index, favoritesFile, "url", 10015)
elif mode == 37:
    ChangeChoice(index, favoritesFile, "image", 10023,
                 10023, 10023, 10024, 10025, 10021, 2)
elif mode == 38:
    MoveInList(index, move, favoritesFile)
elif mode == 60:
    installOPENaddon("plugin.video.s4me", "Verra' installato Stream4Me! Vedrai cosi' tanti contenuti tramite Internet, configuralo in Italiano per ottenere il massimo! Sconsigliamo l'installazione della beta", "Avvio Stream4Me?")
elif mode == 61:
    PrimafilaPerAnno(url)
elif mode == 62:
    PrimafilaByYear(url, name)
elif mode == 63:
    SerieTvMenuAZ()
elif mode == 64:
    SerieTvByLetter(name)

elif mode == 65:
    AddToLibrary(name, url, iconimage)
        
elif mode == 99:
    # Azione "Torna Indietro" per pulsante rosso
    xbmc.executebuiltin('Action(Back)') # name qui contiene l'anno
    sys.exit()

elif mode == 200:
    # BACKGROUND UPDATE SERVICE (Startup)
    xbmc.log("IPTVAddons: Avvio Background Service (Serie TV)...", xbmc.LOGINFO)
    
    # Code is populated by main script header logic
    if code and code != 'none':
        cache_file = os.path.join(addon_data_dir, "series_json_cache.json")
        
        # Check integrity of cache (1 Week = 604800s)
        needs_update = True
        if IsCacheValid(cache_file, 604800):
            needs_update = False
            # Optional: Check if file allows JSON load
            try:
                with open(cache_file, 'r') as f: json.load(f)
            except:
                needs_update = True
        
        if needs_update:
            xbmc.log("IPTVAddons: Cache scaduta o assente. Download in corso...", xbmc.LOGINFO)
            xbmcgui.Dialog().notification("IPTV Addons", "Aggiornamento lista serie TV in corso...", xbmcgui.NOTIFICATION_INFO, 5000)
            try:
                url = sitesource + code + "/jcatserie/listjson.gz"
                content = GetUrl(url)
                if content:
                    # Validate JSON
                    items = json.loads(content) 
                    if items:
                         with open(cache_file, 'w') as f: json.dump(items, f)
                         xbmc.log("IPTVAddons: Cache Serie TV aggiornata con successo! (" + str(len(items)) + " elementi)", xbmc.LOGINFO)
                         xbmcgui.Dialog().notification("IPTV Addons", "Lista serie TV aggiornata!", xbmcgui.NOTIFICATION_INFO, 5000)
            except Exception as e:
                xbmc.log("IPTVAddons: Errore aggiornamento background: " + str(e), xbmc.LOGERROR)
                xbmcgui.Dialog().notification("IPTV Addons", "Errore aggiornamento serie TV", xbmcgui.NOTIFICATION_ERROR, 5000)
        else:
            xbmc.log("IPTVAddons: Cache Serie TV valida. Nessun aggiornamento necessario.", xbmc.LOGINFO)
    else:
        xbmc.log("IPTVAddons: Impossibile aggiornare serie, credenziali non trovate (code=none).", xbmc.LOGWARNING)

    sys.exit()

xbmcplugin.endOfDirectory(int(sys.argv[1]))