# -*- coding: utf-8 -*-
from __future__ import unicode_literals  # ← precisa vir logo no início no Python 2

import sys
from six import text_type, PY3

if not PY3:
    try:
        reload(sys)
        sys.setdefaultencoding('utf-8')
    except:
        pass

from six.moves.urllib.parse import urlparse, parse_qs, urlencode, parse_qsl, unquote_plus, quote, urljoin
import requests
import re
import os
import base64
import unicodedata
import logging
import time
import atexit
import io

try:
    import inputstreamhelper
except ImportError:
    inputstreamhelper = None

try:
    from kodi_six import xbmc, xbmcplugin, xbmcgui, xbmcaddon, xbmcvfs
except ImportError:
    import xbmc
    import xbmcplugin
    import xbmcgui
    import xbmcaddon
    import xbmcvfs

from resources.lib import tmdb, m3u, proxy, pluto, subtitles
from resources.lib.dns import customdns
from resources.lib.common import (
    make_session, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT,
    get_setting_bool as common_setting_bool,
    set_setting_bool as common_set_setting_bool,
    get_setting_text as common_setting_text,
    get_setting_enum_value as common_setting_enum_value,
    log_debug as common_log_debug,
    sync_legacy_setting_mirrors
)
PORT = proxy.PORT
URL_HLSRETRY = "http://127.0.0.1:{}/hlsretry?url=".format(PORT)
URL_TS_DOWNLOADER = "http://127.0.0.1:{}/tsdownloader?url=".format(PORT)
ADDON_HANDLE = int(sys.argv[1])
BASE_URL = sys.argv[0]
ADDON = xbmcaddon.Addon()
TRANSLATE = xbmcvfs.translatePath if PY3 else xbmc.translatePath
homeDir = ADDON.getAddonInfo('path')
addonIcon = TRANSLATE(os.path.join(homeDir, 'icon.png'))
addonFanart = TRANSLATE(os.path.join(homeDir, 'fanart.jpg'))
profile = TRANSLATE(ADDON.getAddonInfo('profile'))
if not os.path.exists(profile):
    os.makedirs(profile)
TV_ADULT_PIN_FILE = os.path.join(profile, 'tv_adult_pin.txt')
HTTP_SESSION = make_session()
sync_legacy_setting_mirrors(addon=ADDON)

ONEPLAY_DESC = '''
Para trabalhar em equipe precisamos ter empatia, transparência, solidariedade e muita lealdade, esses ingredientes são necessários para o sucesso em grupo.

[B][COLOR aquamarine]Repositório Oficial ONEPLAY[/COLOR][/B]
[COLOR blue]https://oneplayhd.github.io[/COLOR]

[B][COLOR aquamarine]Grupo Oficial FACEBOOK[/COLOR][/B]
[COLOR blue]https://tinyurl.com/oneplay2019[/COLOR]

[B][COLOR aquamarine]Grupo Oficial TELEGRAM[/COLOR][/B]
[COLOR blue]http://t.me/oneplay2019[/COLOR]
    '''
_DNS_READY = False


def _cleanup_runtime_refs():
    global ADDON
    try:
        ADDON = None
    except Exception:
        pass

atexit.register(_cleanup_runtime_refs)

def ensure_customdns():
    global _DNS_READY
    if _DNS_READY:
        return
    try:
        customdns()
    except:
        pass
    _DNS_READY = True

ensure_customdns()

def _configure_python_logging():
    try:
        debug_enabled = ADDON.getSetting('mododebug').lower() == 'true'
    except Exception:
        debug_enabled = False
    try:
        root_logger = logging.getLogger()
        if not debug_enabled:
            root_logger.setLevel(logging.WARNING)
        else:
            root_logger.setLevel(logging.INFO)
    except Exception:
        pass
    for logger_name in ('urllib3', 'urllib3.connectionpool', 'requests', 'requests.packages.urllib3', 'chardet'):
        try:
            logger = logging.getLogger(logger_name)
            logger.propagate = True
            logger.setLevel(logging.WARNING if debug_enabled else logging.ERROR)
        except Exception:
            pass


def _set_listitem_info(item, *args, **kwargs):
    info_type = 'video'
    info = {}
    if args:
        if len(args) >= 1 and args[0]:
            info_type = args[0]
        if len(args) >= 2 and isinstance(args[1], dict):
            info.update(args[1])
    info_type = kwargs.get('type', info_type) or 'video'
    extra_info = kwargs.get('infoLabels')
    if isinstance(extra_info, dict):
        info.update(extra_info)

    normalized = {}
    for key, value in info.items():
        try:
            normalized[str(key).strip().lower()] = value
        except Exception:
            normalized[key] = value

    try:
        lowered_type = str(info_type).strip().lower()
    except Exception:
        lowered_type = 'video'

    if lowered_type == 'video':
        try:
            tag = item.getVideoInfoTag()
        except Exception:
            tag = None
        if tag:
            try:
                title = normalized.get('title')
                if title not in (None, '') and hasattr(tag, 'setTitle'):
                    tag.setTitle(str(title))
                plot = normalized.get('plot')
                if plot not in (None, '') and hasattr(tag, 'setPlot'):
                    tag.setPlot(str(plot))
                year = normalized.get('year')
                if year not in (None, '', 0) and hasattr(tag, 'setYear'):
                    try:
                        tag.setYear(int(year))
                    except Exception:
                        pass
                genre = normalized.get('genre')
                if genre not in (None, '') and hasattr(tag, 'setGenres'):
                    if isinstance(genre, (list, tuple)):
                        tag.setGenres([str(x) for x in genre if x not in (None, '')])
                    else:
                        tag.setGenres([str(genre)])
                tvshowtitle = normalized.get('tvshowtitle')
                if tvshowtitle not in (None, '') and hasattr(tag, 'setTvShowTitle'):
                    tag.setTvShowTitle(str(tvshowtitle))
                season = normalized.get('season')
                if season not in (None, '') and hasattr(tag, 'setSeason'):
                    try:
                        tag.setSeason(int(season))
                    except Exception:
                        pass
                episode = normalized.get('episode')
                if episode not in (None, '') and hasattr(tag, 'setEpisode'):
                    try:
                        tag.setEpisode(int(episode))
                    except Exception:
                        pass
                tagline = normalized.get('tagline')
                if tagline not in (None, '') and hasattr(tag, 'setTagLine'):
                    tag.setTagLine(str(tagline))
            except Exception:
                pass
        try:
            mediatype = normalized.get('mediatype')
            if mediatype not in (None, ''):
                item.setProperty('mediatype', str(mediatype))
        except Exception:
            pass
        return

    try:
        _set_listitem_info(item, info_type, info)
    except Exception:
        pass


_configure_python_logging()

# Wrappers locais mantidos para leitura simples no roteador principal.
def _setting_bool(key, default=False):
    return common_setting_bool(key, default, addon=ADDON)

def _set_setting_bool(key, value):
    common_set_setting_bool(key, value, addon=ADDON)

def _setting_text(key, default=''):
    return common_setting_text(key, default, addon=ADDON)


def _setting_enum_value(key, values, default=None):
    return common_setting_enum_value(key, values, default, addon=ADDON)


def _normalize_text(value):
    try:
        value = value or ''
        if not isinstance(value, text_type):
            value = text_type(value)
        value = unicodedata.normalize('NFKD', value)
        value = ''.join(ch for ch in value if not unicodedata.combining(ch))
        return value.strip().lower()
    except Exception:
        try:
            return text_type(value).strip().lower()
        except Exception:
            return ''


def _is_tv_adult_group(group_name):
    normalized = _normalize_text(group_name)
    if not normalized:
        return False
    keywords = ('18+', '+18', '18 +', '+ 18', 'xxx', 'porn', 'porno', 'sex', 'adulttime', 'adulto', 'onlyfans', 'only fans', 'privacy', 'novinha', 'novinho', 'interracialsexx', 'mypervyfamily', 'brazzersexxtra', 'blacked raw', 'brad montana', 'mia khalifa', 'brasileirinhas')
    return any(keyword in normalized for keyword in keywords)


def _tv_adult_protection_enabled():
    return _setting_bool('tv_adult_protect', True)


def _sanitize_tv_adult_pin(value):
    value = ''.join(ch for ch in (value or '') if ch.isdigit())
    return value if len(value) == 4 else ''


def _read_tv_adult_pin_file():
    try:
        if not os.path.exists(TV_ADULT_PIN_FILE):
            return ''
        with io.open(TV_ADULT_PIN_FILE, 'r', encoding='utf-8') as fh:
            return _sanitize_tv_adult_pin(fh.read().strip())
    except Exception:
        return ''


def _write_tv_adult_pin_file(pin):
    pin = _sanitize_tv_adult_pin(pin)
    if not pin:
        return False
    try:
        with io.open(TV_ADULT_PIN_FILE, 'w', encoding='utf-8') as fh:
            fh.write(pin)
        return True
    except Exception:
        return False


def _save_tv_adult_pin(pin):
    pin = _sanitize_tv_adult_pin(pin)
    if not pin:
        return False
    saved = False
    try:
        ADDON.setSetting('tv_adult_pin', pin)
        saved = True
    except Exception:
        pass
    if _write_tv_adult_pin_file(pin):
        saved = True
    return saved


def _get_tv_adult_pin():
    pin_from_file = _read_tv_adult_pin_file()
    pin_from_setting = _sanitize_tv_adult_pin(_setting_text('tv_adult_pin', ''))

    current = pin_from_file or pin_from_setting or '0000'

    if pin_from_file:
        if pin_from_setting != pin_from_file:
            try:
                ADDON.setSetting('tv_adult_pin', pin_from_file)
            except Exception:
                pass
    elif pin_from_setting:
        _write_tv_adult_pin_file(pin_from_setting)
    else:
        _save_tv_adult_pin(current)

    return current


def _prompt_tv_adult_pin():
    expected = _get_tv_adult_pin()
    try:
        entered = xbmcgui.Dialog().input('Digite o PIN das categorias adultas', type=xbmcgui.INPUT_NUMERIC)
    except Exception:
        return False
    entered = ''.join(ch for ch in (entered or '') if ch.isdigit())
    if entered == expected:
        return True
    _show_error('PIN incorreto.', title='OnePlay', dialog=True)
    return False


def change_tv_adult_pin():
    current = _get_tv_adult_pin()
    prompt_current = 'Digite o PIN atual'
    if current == '0000':
        prompt_current += ' (padrão: 0000)'
    try:
        entered_current = xbmcgui.Dialog().input(prompt_current, type=xbmcgui.INPUT_NUMERIC)
    except Exception:
        entered_current = ''
    entered_current = ''.join(ch for ch in (entered_current or '') if ch.isdigit())
    if entered_current != current:
        _show_error('PIN atual incorreto.', title='OnePlay', dialog=True)
        return
    try:
        new_pin = xbmcgui.Dialog().input('Digite o novo PIN (4 dígitos)', type=xbmcgui.INPUT_NUMERIC)
    except Exception:
        new_pin = ''
    new_pin = ''.join(ch for ch in (new_pin or '') if ch.isdigit())
    if len(new_pin) != 4:
        _show_error('O novo PIN deve ter 4 dígitos.', title='OnePlay', dialog=True)
        return
    try:
        confirm_pin = xbmcgui.Dialog().input('Confirme o novo PIN', type=xbmcgui.INPUT_NUMERIC)
    except Exception:
        confirm_pin = ''
    confirm_pin = ''.join(ch for ch in (confirm_pin or '') if ch.isdigit())
    if confirm_pin != new_pin:
        _show_error('A confirmação do PIN não confere.', title='OnePlay', dialog=True)
        return
    if not _save_tv_adult_pin(new_pin):
        _show_error('Não foi possível salvar o novo PIN.', title='OnePlay', dialog=True)
        return
    _notify('OnePlay', 'PIN das categorias adultas atualizado.', xbmcgui.NOTIFICATION_INFO, 2500)


def open_tv_adult_groups(params):
    url = params.get('url')
    if not url:
        _show_error('URL da lista M3U inválida.', title='Kodi', dialog=True)
        return
    if not _tv_adult_protection_enabled():
        _show_error('Categorias adultas desativadas nesta configuração.', title='OnePlay', dialog=True)
        return
    if not _prompt_tv_adult_pin():
        return
    try:
        channels, groups = m3u.parse_m3u(url)
        adult_groups = []
        counts = {}
        for channel in channels or []:
            group_name = channel.get('group') or 'Outros'
            if not _is_tv_adult_group(group_name):
                continue
            counts[group_name] = counts.get(group_name, 0) + 1
            if group_name not in adult_groups:
                adult_groups.append(group_name)
        if not adult_groups:
            _show_error('Nenhuma categoria adulta foi encontrada nesta lista.', title='OnePlay', dialog=True)
            return
        icon = TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'tv.png'))
        for group in adult_groups:
            label = '{} ({})'.format(group, counts.get(group, 0))
            url_ = build_url({'action': 'opengroup', 'url': url, 'group': group})
            li = xbmcgui.ListItem(label)
            _set_listitem_info(li, 'video', {'title': label, 'mediatype': 'video'})
            _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": 'Abrir a categoria {} de canais.'.format(group)})
            li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
            xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url_, listitem=li, isFolder=True)
        _end_directory_with_view(content='movies', view='List', delay_ms=40, repeat=2, default_once_key='default_view_tv_list_applied')
    except Exception as exc:
        _log_debug('Erro ao abrir categorias adultas: {}'.format(exc))
        _show_error('Erro ao processar as categorias adultas.', title='Kodi', dialog=True)


def _log_debug(message):
    common_log_debug(message, addon=ADDON)

def _notify(title, message, icon=None, milliseconds=2800):
    try:
        if icon is None:
            icon = xbmcgui.NOTIFICATION_INFO
        xbmcgui.Dialog().notification(title, message, icon, milliseconds)
    except:
        pass

def _show_error(message, title='OnePlay', dialog=False):
    if dialog:
        try:
            xbmcgui.Dialog().ok(title, message)
            return
        except:
            pass
    if _setting_bool('notificarerros', True):
        _notify(title, message, xbmcgui.NOTIFICATION_ERROR, 3500)



def _movie_navigation_mode():
    return _setting_enum_value('fluxofilmes', ['direct', 'details'], 'direct')











def _ensure_pluto_session(params=None):
    if isinstance(params, dict):
        incoming = (params.get('psid') or '').strip()
    else:
        incoming = (params or '').strip()
    return pluto.ensure_session(incoming)

def _pluto_entry_mode():
    return _setting_enum_value('pluto_entrada', ['channels', 'categories'], 'channels')


def _stream_priority_mode():
    return _setting_text('qualidadestream', '0')

def _apply_default_view_once(flag_key, view, delay_ms=40, repeat=2):
    if _setting_bool(flag_key, False):
        return
    try:
        setview(view)
    except Exception:
        return
    extra_retries = max(0, int(repeat) - 1)
    for _ in range(extra_retries):
        try:
            xbmc.sleep(delay_ms)
        except Exception:
            pass
        try:
            setview(view)
        except Exception:
            break
    _set_setting_bool(flag_key, True)

def _complete_plugin_action(succeeded=True):
    for builtin in ('Dialog.Close(busydialog)', 'Dialog.Close(busydialognocancel)'):
        try:
            xbmc.executebuiltin(builtin)
        except:
            pass
    try:
        xbmcplugin.endOfDirectory(ADDON_HANDLE, succeeded=succeeded, updateListing=False, cacheToDisc=False)
    except TypeError:
        try:
            xbmcplugin.endOfDirectory(ADDON_HANDLE, succeeded=succeeded)
        except:
            pass
    except:
        pass

def _add_settings_item(plot='Abrir as configurações do addon OnePlay.'):
    label = 'Configurações'
    icon = TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'configuracoes.png'))
    li = xbmcgui.ListItem(label)
    li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
    _set_listitem_info(li, 'video', {'mediatype': 'video'})
    _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": plot})
    li.setProperty('IsPlayable', 'false')
    xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=build_url({'action': 'open_settings'}), listitem=li, isFolder=False)


def _add_pluto_item(plot=None):
    label = 'Pluto TV'
    icon = TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'pluto.png'))
    if not os.path.exists(icon):
        icon = TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'tv.png'))
    if plot is None:
        mode_label = 'categorias' if _pluto_entry_mode() == 'categories' else 'canais'
        plot = 'Abrir Pluto TV em {} com player dedicado separado do fluxo oficial.'.format(mode_label)
    li = xbmcgui.ListItem(label)
    li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
    _set_listitem_info(li, 'video', {'mediatype': 'video'})
    _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": plot})
    li.setProperty('IsPlayable', 'false')
    pluto_session_id = pluto.new_session_id()
    xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=build_url({'action': 'pluto_entry', 'psid': pluto_session_id}), listitem=li, isFolder=True)

def clear_subtitles_cache(notify=True):
    removed = 0
    try:
        removed = subtitles.clear_subtitles_cache(addon=ADDON)
    except Exception as exc:
        _log_debug('Erro limpando cache de legendas: {}'.format(exc))
    if notify:
        if removed:
            _notify('OnePlay', 'Cache de legendas limpo.', xbmcgui.NOTIFICATION_INFO, 2500)
        else:
            _notify('OnePlay', 'Nenhuma legenda em cache para limpar.', xbmcgui.NOTIFICATION_INFO, 2500)
    return removed

def clear_epg_cache(notify=True):
    removed = 0
    try:
        removed = m3u.clear_epg_cache()
    except Exception as exc:
        _log_debug('Erro limpando cache de EPG: {}'.format(exc))
    if notify:
        if removed:
            _notify('OnePlay', 'Cache de EPG limpo.', xbmcgui.NOTIFICATION_INFO, 2500)
        else:
            _notify('OnePlay', 'Nenhum arquivo de EPG em cache para limpar.', xbmcgui.NOTIFICATION_INFO, 2500)
    return removed


def clear_tv_history(notify=True):
    keys = ('last_tv_list_url', 'last_tv_list_name', 'last_tv_group', 'last_tv_group_url')
    for key in keys:
        try:
            ADDON.setSetting(key, '')
        except:
            pass
    if notify:
        _notify('OnePlay', 'Atalhos recentes da TV ao vivo foram limpos.', xbmcgui.NOTIFICATION_INFO, 2500)


def _show_text_dialog(title, text_value):
    body = text_value or 'Nenhuma informação disponível.'
    try:
        xbmcgui.Dialog().textviewer(title, body)
    except Exception:
        try:
            xbmcgui.Dialog().ok(title, body)
        except Exception:
            pass


def open_settings():
    opened = False
    try:
        ADDON.openSettings()
        opened = True
    except Exception:
        try:
            xbmc.executebuiltin('Addon.OpenSettings({})'.format(ADDON.getAddonInfo('id')))
            opened = True
        except Exception:
            try:
                xbmcgui.Dialog().ok('OnePlay', 'Não foi possível abrir as configurações do addon.')
            except:
                pass
    _complete_plugin_action(succeeded=opened)


def _resolve_tv_mode():
    try:
        mode = ADDON.getSetting('modocanais')
    except:
        mode = ''
    # Valores válidos na configuração: 0=Perguntar sempre, 1=M3U8, 2=MPEGTS.
    if mode == '1':
        return 0
    if mode == '2':
        return 1
    # Compat legado: se algum perfil antigo ainda trouxer "3", trata como MPEGTS.
    if mode == '3':
        return 1
    try:
        selected = xbmcgui.Dialog().select('Escolha uma opção', ['MODO M3U8', 'MODO MPEGTS'])
    except:
        selected = -1
    if selected == 0:
        return 0
    if selected == 1:
        return 1
    return -1

def _tv_epg_enabled():
    return _setting_bool('tv_epg_ativo', False)


def _pluto_epg_enabled():
    return _setting_bool('pluto_epg_ativo', True)


def _apply_tv_epg(channel, epg_map):
    name = channel.get('name', 'Canal')
    plot = 'Abrir o canal para reprodução.'
    if not _tv_epg_enabled():
        return name, plot
    label_suffix, epg_plot = m3u.format_epg_entry(epg_map.get(channel.get('id')))
    if label_suffix and _setting_bool('tv_epg_nomelista', True):
        name = '{} - [COLOR aquamarine]{}[/COLOR]'.format(name, label_suffix)
    if epg_plot:
        plot = epg_plot
    return name, plot


def _get_tv_epg_metadata(m3u_url, channel):
    metadata = {'plot': 'Abrir o canal para reprodução.', 'current_title': '', 'next_title': ''}
    if not _tv_epg_enabled() or not m3u_url or not channel:
        return metadata
    try:
        epg_map = m3u.get_epg_for_channels(m3u_url, [channel])
        entry = epg_map.get(channel.get('id'))
        if not entry:
            return metadata
        meta = m3u.describe_epg_entry(entry)
        metadata.update({
            'plot': meta.get('plot') or metadata['plot'],
            'current_title': meta.get('current_title', ''),
            'next_title': meta.get('next_title', ''),
        })
    except Exception as exc:
        _log_debug('Falha ao buscar metadados EPG do canal em reprodução: {}'.format(exc))
    return metadata

def desc_vip():
    text = ''
    try:
        response = HTTP_SESSION.get(
            'https://oneplayhd.com/download/vip/info_oneplay.txt',
            headers={'User-Agent': DEFAULT_USER_AGENT},
            timeout=DEFAULT_TIMEOUT
        )
        response.raise_for_status()
        response.encoding = 'utf-8'
        text = response.text or ''
    except Exception as exc:
        _log_debug('Falha ao buscar descrição VIP: {}'.format(exc))
    return text


try:
    class QRCodeDialog(xbmcgui.WindowXMLDialog):
        def __init__(self, *args, **kwargs):
            self.image_path = kwargs.pop('image_path', '')
            xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs)

        def onInit(self):
            try:
                self.getControl(1001).setImage(self.image_path)
            except Exception:
                pass

        def onAction(self, action):
            try:
                action_id = action.getId()
            except Exception:
                action_id = action
            close_actions = (
                getattr(xbmcgui, 'ACTION_PREVIOUS_MENU', 10),
                getattr(xbmcgui, 'ACTION_NAV_BACK', 92),
                getattr(xbmcgui, 'ACTION_BACKSPACE', 110),
            )
            if action_id in close_actions:
                self.close()

        def onClick(self, control_id):
            if control_id == 1002:
                self.close()
except Exception:
    QRCodeDialog = None


def show_qr_code():
    qr_image_path = TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'qrcode.png'))
    if not xbmcvfs.exists(qr_image_path):
        xbmcgui.Dialog().ok('QR Code', 'Arquivo do QR Code não encontrado.')
        return
    if QRCodeDialog is not None:
        try:
            dialog = QRCodeDialog('QRCodeDialog.xml', ADDON.getAddonInfo('path'), 'Default', '1080i', image_path=qr_image_path)
            dialog.doModal()
            del dialog
            return
        except Exception as exc:
            _log_debug('Falha ao abrir QR Code em janela customizada: {}'.format(exc))
    try:
        dialog = Donate_()
        dialog.doModal()
    except Exception:
        xbmcgui.Dialog().ok('QR Code', 'Não foi possível abrir o QR Code.')

try:
    class Donate_(xbmcgui.WindowDialog):
        def __init__(self):
            try:
                self.image = xbmcgui.ControlImage(440, 128, 400, 400, TRANSLATE(os.path.join(homeDir, 'resources', 'images','qrcode.png')))
                self.text = xbmcgui.ControlLabel(x=495,y=600,width=1000,height=25,label='[B][COLOR yellow]PRESSIONE VOLTAR PARA SAIR[/COLOR][/B]',textColor='yellow')
                self.addControl(self.image)
                self.addControl(self.text)
            except:
                pass
except:
    pass

def setview(name):
    mode = {'Wall': '500',
            'List': '50',
            'Poster': '51',
            'Shift': '53',
            'InfoWall': '54',
            'WideList': '55',
            'Fanart': '502'
            }.get(name, '50')
    view = 'Container.SetViewMode(%s)'%mode
    xbmc.executebuiltin(view)

def _end_directory_with_view(content=None, view=None, succeeded=True, cache=True, delay_ms=40, repeat=1, default_once_key=None):
    if content:
        try:
            xbmcplugin.setContent(ADDON_HANDLE, content)
        except Exception:
            pass
    try:
        xbmcplugin.endOfDirectory(ADDON_HANDLE, succeeded=succeeded, updateListing=False, cacheToDisc=cache)
    except TypeError:
        try:
            xbmcplugin.endOfDirectory(ADDON_HANDLE, succeeded=succeeded)
        except Exception:
            pass
    except Exception:
        pass
    if view:
        if default_once_key:
            _apply_default_view_once(default_once_key, view, delay_ms=delay_ms, repeat=repeat)
        else:
            try:
                setview(view)
            except Exception:
                return
            extra_retries = max(0, int(repeat) - 1)
            for _ in range(extra_retries):
                try:
                    xbmc.sleep(delay_ms)
                except Exception:
                    pass
                try:
                    setview(view)
                except Exception:
                    break

def build_url(query):
    if PY3:
        return BASE_URL + '?' + urlencode(query, encoding='utf-8')
    else:
        try:
            query = {k: (v.encode('utf-8') if isinstance(v, unicode) else v) for k, v in query.items()}
        except NameError:
            pass
        return BASE_URL + '?' + urlencode(query)

def home():
    items = [('Pesquisar', 'Buscar filmes e séries pelo nome do conteúdo.', TRANSLATE(os.path.join(homeDir, 'resources','images','pesquisar.png')), {'action': 'search'}),
             ('TV ao vivo', 'Entrar nas listas e categorias de canais ao vivo.', TRANSLATE(os.path.join(homeDir, 'resources','images','tv.png')), {'action': 'tv'}),
             ('Filmes', 'Explorar filmes em alta, top avaliados e lançamentos.', TRANSLATE(os.path.join(homeDir, 'resources','images','filmes.png')), {'action': 'menu_movies'}),
             ('Séries', 'Explorar séries em alta e top avaliadas.', TRANSLATE(os.path.join(homeDir, 'resources','images','series.png')), {'action': 'menu_series'}),
             ('Configurações', 'Abrir as configurações do addon OnePlay.', TRANSLATE(os.path.join(homeDir, 'resources','images','configuracoes.png')), {'action': 'open_settings'})]

    if _setting_bool('mostrarboasvindas', True):
        items.insert(0, ('[COLOR aquamarine]:::[/COLOR]BEM-VINDOS AO ONEPLAY[COLOR aquamarine]:::[/COLOR]', ONEPLAY_DESC, addonIcon, {'action': 'donate'}))

    if _setting_bool('exibirvip', True):
        items.insert(1, ('VIP', desc_vip(), TRANSLATE(os.path.join(homeDir, 'resources', 'images','vip.png')), {'action': ''}))

    for label, description, icon, query in items:
        if label == 'VIP':
            url = 'plugin://plugin.video.oneplayvip/'
        else:
            url = build_url(query)
        li = xbmcgui.ListItem(label)
        li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": description})
        if label in ('Configurações', '[COLOR aquamarine]:::[/COLOR]BEM-VINDOS AO ONEPLAY[COLOR aquamarine]:::[/COLOR]'):
            li.setProperty('IsPlayable', 'false')
        is_folder = label not in ('Configurações', '[COLOR aquamarine]:::[/COLOR]BEM-VINDOS AO ONEPLAY[COLOR aquamarine]:::[/COLOR]')
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=is_folder)

    _end_directory_with_view(content='movies', view='List', delay_ms=40, repeat=2, default_once_key='default_view_home_list_applied')

# --- As funções em TV (m3u, proxy, etc) não foram alteradas ---
def menu_tv():
    options = m3u.get_lists()
    icon = TRANSLATE(os.path.join(homeDir, 'resources','images','tv.png'))

    if _setting_bool('tv_lembrarultimalista', True):
        last_url = _setting_text('last_tv_list_url', '')
        last_name = _setting_text('last_tv_list_name', 'Última lista')
        if last_url and last_url in options:
            recent_label = '[COLOR mediumaquamarine]Continuar[/COLOR] - {}'.format(last_name)
            recent_url = build_url({'action': 'openm3u', 'url': last_url, 'source_name': last_name})
            li = xbmcgui.ListItem(recent_label)
            _set_listitem_info(li, 'video', {'mediatype': 'video'})
            _set_listitem_info(li, type="Video", infoLabels={"Title": recent_label, "Plot": 'Abrir rapidamente a última lista usada.'})
            li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
            xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=recent_url, listitem=li, isFolder=True)

    for index, option in enumerate(options):
        name = 'LISTA {}'.format(index + 1)
        url = build_url({'action': 'openm3u', 'url': option, 'source_name': name})
        li = xbmcgui.ListItem(name)
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        _set_listitem_info(li, type="Video", infoLabels={"Title": name, "Plot": 'Abrir a {} de canais ao vivo.'.format(name.lower())})
        li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)

    if _setting_bool('pluto_exibir_menu', True):
        _add_pluto_item()
    _add_settings_item()
    _end_directory_with_view(content='movies', view='List', delay_ms=40, repeat=2, default_once_key='default_view_tv_list_applied')

def openm3u(params):
    url = params.get('url')
    source_name = params.get('source_name', 'Lista')
    if not url:
        xbmcgui.Dialog().ok('Kodi', 'URL da lista M3U inválida.')
        return
    try:
        if _setting_bool('tv_lembrarultimalista', True):
            try:
                ADDON.setSetting('last_tv_list_url', url)
                ADDON.setSetting('last_tv_list_name', source_name)
            except:
                pass

        ordered_groups, group_counts = m3u.get_group_index(url)
        if not ordered_groups:
            xbmcgui.Dialog().ok('Kodi', 'Nenhum grupo encontrado na lista.')
            return

        last_group = _setting_text('last_tv_group', '')
        last_group_url = _setting_text('last_tv_group_url', '')
        icon = TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'tv.png'))
        protect_adult = _tv_adult_protection_enabled()

        if _setting_bool('tv_lembrarultimogrupo', True) and last_group and last_group_url == url and group_counts.get(last_group, 0) > 0:
            if not _is_tv_adult_group(last_group):
                recent_group_label = '[COLOR mediumaquamarine]Último grupo[/COLOR] - {}'.format(last_group)
                recent_group_url = build_url({'action': 'opengroup', 'url': url, 'group': last_group})
                li = xbmcgui.ListItem(recent_group_label)
                _set_listitem_info(li, 'video', {'title': recent_group_label, 'mediatype': 'video'})
                _set_listitem_info(li, type="Video", infoLabels={"Title": recent_group_label, "Plot": 'Abrir rapidamente o último grupo acessado nesta lista.'})
                li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
                xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=recent_group_url, listitem=li, isFolder=True)

        hide_empty = _setting_bool('tv_ocultargruposvazios', True)
        adult_group_names = []
        adult_total = 0
        for group in ordered_groups:
            count = group_counts.get(group, 0)
            if hide_empty and count <= 0:
                continue
            if _is_tv_adult_group(group):
                if protect_adult:
                    adult_group_names.append(group)
                    adult_total += count
                continue
            url_ = build_url({'action': 'opengroup', 'url': url, 'group': group})
            label = '{} ({})'.format(group, count)
            li = xbmcgui.ListItem(label)
            _set_listitem_info(li, 'video', {'title': label, 'mediatype': 'video'})
            _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": 'Abrir a categoria {} de canais.'.format(group)})
            li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
            xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url_, listitem=li, isFolder=True)
        if protect_adult and adult_group_names:
            label = '[COLOR red]Conteúdos Adulto[/COLOR] ({})'.format(adult_total)
            url_ = build_url({'action': 'open_tv_adult_groups', 'url': url})
            li = xbmcgui.ListItem(label)
            _set_listitem_info(li, 'video', {'title': label, 'mediatype': 'video'})
            _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": 'Abrir categorias adultas em TV ao vivo mediante PIN.'})
            li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
            xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url_, listitem=li, isFolder=True)
        _end_directory_with_view(content='movies', view='List', delay_ms=40, repeat=2, default_once_key='default_view_tv_list_applied')
    except Exception:
        xbmcgui.Dialog().ok('Kodi', 'Erro ao processar a lista M3U.')

def opengroup(params):   
    url = params.get('url')
    group = params.get('group')
    if group:
        try:
            group = group.decode('utf-8')
        except:
            pass
    if not url:
        xbmcgui.Dialog().ok('Kodi', 'URL da lista M3U inválida.')
        return
    if _is_tv_adult_group(group):
        if not _tv_adult_protection_enabled():
            _show_error('Categorias adultas desativadas nesta configuração.', title='OnePlay', dialog=True)
            return
        if not _prompt_tv_adult_pin():
            return
    try:
        if _setting_bool('tv_lembrarultimogrupo', True) and group:
            try:
                ADDON.setSetting('last_tv_group', group)
                ADDON.setSetting('last_tv_group_url', url)
            except:
                pass
        channel_filter = m3u.get_group_channels(url, group)
        if not channel_filter:
            xbmcgui.Dialog().ok('Kodi', 'Nenhum canal encontrado na lista.')
            return
        epg_map = {}
        if _tv_epg_enabled():
            try:
                epg_map = m3u.get_epg_for_channels(url, channel_filter)
            except Exception as exc:
                _log_debug('Falha ao carregar EPG da TV ao vivo: {}'.format(exc))
                epg_map = {}
        for channel in channel_filter:
            display_name, display_plot = _apply_tv_epg(channel, epg_map)
            url_ = build_url({'action': 'play_proxy', 'name': channel['name'], 'url': channel['url'], 'icon': channel['logo'], 'm3u_url': url, 'tvg_id': channel.get('tvg_id', ''), 'tvg_name': channel.get('tvg_name', ''), 'group': channel.get('group', '')})
            li = xbmcgui.ListItem(display_name)
            _set_listitem_info(li, 'video', {'title': display_name, 'mediatype': 'video'})
            _set_listitem_info(li, type="Video", infoLabels={"Title": display_name, "Plot": display_plot})
            li.setArt({'icon': channel['logo'], 'thumb': channel['logo'], 'poster': channel['logo'], 'fanart': addonFanart})
            li.setProperty('IsPlayable', 'false')
            xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url_, listitem=li, isFolder=False)
        _end_directory_with_view(content='movies', view='List', delay_ms=40, repeat=2, default_once_key='default_view_tv_list_applied')
    except Exception as e:
        xbmcgui.Dialog().ok('Kodi', 'Erro ao processar a lista M3U.')

def play_proxy(params):
    url = params.get('url')
    if not url:
        _show_error('URL inválida.', title='Kodi', dialog=True)
        return
    if _setting_bool('confirmartv', False):
        try:
            channel_name = params.get('name', 'Canal')
            message = 'Deseja reproduzir este canal?'
            if channel_name:
                message += '\n' + channel_name
            confirmed = xbmcgui.Dialog().yesno('OnePlay', message)
        except Exception:
            confirmed = True
        if not confirmed:
            return
    original_name = params.get('name', 'Canal')
    op = _resolve_tv_mode()
    if op == 0:
        name = original_name + ' (M3U8)'
        url = unquote_plus(url)
        url = url.split('|')[0] if '|' in url else url
        url = m3u.convert_to_m3u8(url)
        url = URL_HLSRETRY + quote(url)
    elif op == 1:
        name = original_name + ' (TS)'
        url = unquote_plus(url)
        url = url.replace('live/', '').replace('.m3u8', '')
        url = URL_TS_DOWNLOADER + quote(url)
    else:
        return
    proxy.kodiproxy()
    channel_meta = {
        'name': original_name,
        'tvg_id': params.get('tvg_id', ''),
        'tvg_name': params.get('tvg_name', '') or original_name,
        'group': params.get('group', ''),
        'id': base64.urlsafe_b64encode(original_name.encode('utf-8')).decode('ascii')
    }
    epg_meta = _get_tv_epg_metadata(params.get('m3u_url', ''), channel_meta)
    plot = epg_meta.get('plot') or 'Abrir o canal para reprodução.'
    li = xbmcgui.ListItem(name)
    _set_listitem_info(li, 'video', {
        'title': name,
        'plot': plot,
        'mediatype': 'video'
    })
    _set_listitem_info(li, type="Video", infoLabels={
        "Title": name,
        "Plot": plot
    })
    li.setArt({'icon': params.get('icon', ''), 'thumb': params.get('icon', ''), 'poster': params.get('icon', ''), 'fanart': addonFanart})
    xbmc.Player().play(item=url, listitem=li)

# --- Funções de Filmes e Séries (navegação) não foram alteradas ---

def _get_pluto_channels():
    return pluto.get_channels(
        hide_no_logo=_setting_bool('pluto_ocultarsemlogo', False),
        include_epg=_pluto_epg_enabled()
    )


def pluto_entry(params=None):
    psid = _ensure_pluto_session(params or {})
    if _pluto_entry_mode() == 'categories':
        channels_pluto_categories(psid=psid)
        return
    channels_pluto(psid=psid)


def channels_pluto(psid=''):
    try:
        channels = _get_pluto_channels()
    except Exception as exc:
        _log_debug('Falha ao carregar Pluto TV: {}'.format(exc))
        _show_error('Não foi possível carregar os canais do Pluto TV.', dialog=True)
        return
    if not channels:
        _show_error('Nenhum canal do Pluto TV foi encontrado.', dialog=True)
        return
    for channel in channels:
        name = channel.get('display_name') or channel.get('name') or 'Pluto TV'
        desc = channel.get('description', '')
        thumb = channel.get('thumb', '')
        slug = channel.get('slug', '')
        channel_id = channel.get('channel_id', '')
        if not (slug or channel_id):
            continue
        icon = thumb or TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'pluto.png'))
        url_ = build_url({'action': 'play_pluto', 'name': name, 'description': desc, 'icon': icon, 'slug': slug, 'channel_id': channel_id, 'psid': psid})
        li = xbmcgui.ListItem(name)
        _set_listitem_info(li, 'video', {'title': name, 'plot': desc, 'mediatype': 'video'})
        _set_listitem_info(li, type="Video", infoLabels={"Title": name, "Plot": desc})
        li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        li.setProperty('IsPlayable', 'true')
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url_, listitem=li, isFolder=False)
    _end_directory_with_view(content='videos', view='List', delay_ms=40, repeat=2, default_once_key='default_view_tv_list_applied')


def channels_pluto_categories(psid=''):
    try:
        grouped = pluto.get_channels_grouped_by_category(
            hide_no_logo=_setting_bool('pluto_ocultarsemlogo', False),
            include_epg=_pluto_epg_enabled()
        )
    except Exception as exc:
        _log_debug('Falha ao carregar categorias do Pluto TV: {}'.format(exc))
        _show_error('Não foi possível carregar as categorias do Pluto TV.', dialog=True)
        return
    if not grouped:
        _show_error('Nenhuma categoria do Pluto TV foi encontrada.', dialog=True)
        return
    icon = TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'pluto.png'))
    for category, channels in grouped:
        label = '{} ({})'.format(category, len(channels))
        li = xbmcgui.ListItem(label)
        _set_listitem_info(li, 'video', {'title': label, 'plot': 'Abrir canais da categoria {} no Pluto TV.'.format(category), 'mediatype': 'video'})
        _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": 'Abrir canais da categoria {} no Pluto TV.'.format(category)})
        li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=build_url({'action': 'channels_pluto_category', 'category': category, 'psid': psid}), listitem=li, isFolder=True)
    _end_directory_with_view(content='videos', view='List', delay_ms=40, repeat=2, default_once_key='default_view_tv_list_applied')


def channels_pluto_category(params, psid=''):
    category = params.get('category', '')
    try:
        channels = pluto.get_channels_for_category(
            category,
            hide_no_logo=_setting_bool('pluto_ocultarsemlogo', False),
            include_epg=_pluto_epg_enabled()
        )
    except Exception as exc:
        _log_debug('Falha ao carregar categoria Pluto {}: {}'.format(category, exc))
        _show_error('Não foi possível carregar os canais dessa categoria do Pluto TV.', dialog=True)
        return
    if not channels:
        _show_error('Nenhum canal foi encontrado nessa categoria do Pluto TV.', dialog=True)
        return
    for channel in channels:
        name = channel.get('display_name') or channel.get('name') or 'Pluto TV'
        desc = channel.get('description', '')
        thumb = channel.get('thumb', '')
        slug = channel.get('slug', '')
        channel_id = channel.get('channel_id', '')
        if not (slug or channel_id):
            continue
        icon = thumb or TRANSLATE(os.path.join(homeDir, 'resources', 'images', 'pluto.png'))
        url_ = build_url({'action': 'play_pluto', 'name': name, 'description': desc, 'icon': icon, 'slug': slug, 'channel_id': channel_id, 'psid': psid})
        li = xbmcgui.ListItem(name)
        _set_listitem_info(li, 'video', {'title': name, 'plot': desc, 'mediatype': 'video'})
        _set_listitem_info(li, type="Video", infoLabels={"Title": name, "Plot": desc})
        li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        li.setProperty('IsPlayable', 'true')
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url_, listitem=li, isFolder=False)
    _end_directory_with_view(content='videos', view='List', delay_ms=40, repeat=2, default_once_key='default_view_tv_list_applied')


def play_pluto(params):
    slug = params.get('slug', '')
    channel_id = params.get('channel_id', '')
    name = params.get('name', 'Pluto TV')
    icon = params.get('icon', '')
    description = params.get('description', '')
    _ensure_pluto_session(params.get('psid', ''))
    url = pluto.resolve_stream_url(slug=slug, channel_id=channel_id)
    if not url:
        _show_error('URL do canal Pluto inválida.', dialog=True)
        return
    if inputstreamhelper is None:
        _show_error('O módulo InputStream Helper não está disponível para reproduzir o Pluto TV.', dialog=True)
        return
    try:
        helper = inputstreamhelper.Helper('hls')
        if not helper.check_inputstream():
            _show_error('O InputStream Adaptive não está disponível para reproduzir o Pluto TV.', dialog=True)
            return
    except Exception as exc:
        _log_debug('Falha no inputstream do Pluto: {}'.format(exc))
        _show_error('Falha ao inicializar o player dedicado do Pluto TV.', dialog=True)
        return

    headers = ''
    if '|' in url:
        url, headers = url.split('|', 1)

    li = xbmcgui.ListItem(path=url)
    li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
    _set_listitem_info(li, 'video', {'title': name, 'plot': description, 'mediatype': 'video'})
    _set_listitem_info(li, type="Video", infoLabels={"Title": name, "Plot": description})
    li.setProperty('inputstream', helper.inputstream_addon)
    li.setProperty('inputstream.adaptive.manifest_type', 'hls')
    li.setProperty('inputstream.adaptive.live_delay', '0')
    li.setProperty('inputstream.adaptive.manifest_update_parameter', 'full')
    li.setMimeType('application/x-mpegURL')
    if headers:
        li.setProperty('inputstream.adaptive.stream_headers', headers)
        li.setProperty('inputstream.adaptive.manifest_headers', headers)
    li.setContentLookup(False)
    xbmcplugin.setResolvedUrl(handle=ADDON_HANDLE, succeeded=True, listitem=li)


def menu_movies():  
    items = [
        ('Filmes - Em Alta', 'Ver os filmes que estão bombando no momento.', {'action': 'list', 'type': 'movie', 'category': 'trending'}),
        ('Filmes - Top Avaliados', 'Ver os filmes com melhor avaliação nas fontes.', {'action': 'list', 'type': 'movie', 'category': 'top'}),
        ('Filmes - Lançamentos', 'Ver os filmes lançados mais recentemente.', {'action': 'list', 'type': 'movie', 'category': 'latest'}),
    ]
    for label, description, query in items:
        url = build_url(query)
        icon = TRANSLATE(os.path.join(homeDir, 'resources','images','filmes.png'))
        li = xbmcgui.ListItem(label)
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": description})
        li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)
    _add_settings_item('Abrir as configurações do addon OnePlay a partir da área de filmes.')
    xbmcplugin.endOfDirectory(ADDON_HANDLE)
    xbmcplugin.setContent(ADDON_HANDLE, 'movies')
    setview('List')      

def menu_series():  
    items = [
        ('Séries - Em Alta', 'Ver as séries que estão em destaque agora.', {'action': 'list', 'type': 'series', 'category': 'trending'}),
        ('Séries - Top Avaliados', 'Ver as séries com melhor avaliação nas fontes.', {'action': 'list', 'type': 'series', 'category': 'top'}),
    ]    
    for label, description, query in items:
        url = build_url(query)
        icon = TRANSLATE(os.path.join(homeDir, 'resources','images','series.png'))
        li = xbmcgui.ListItem(label)
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        _set_listitem_info(li, type="Video", infoLabels={"Title": label, "Plot": description})
        li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)
    _add_settings_item('Abrir as configurações do addon OnePlay a partir da área de séries.')
    xbmcplugin.endOfDirectory(ADDON_HANDLE) 
    xbmcplugin.setContent(ADDON_HANDLE, 'movies') 
    setview('List')            

def list_items(params):  
    # ... (sem alterações aqui)
    next_button = False
    page = int(params.get('page', '1'))
    items = tmdb.get_items(params.get('type'), params.get('category'), page)
    if items:
        next_button = True

    for item in items:
        title = '{} ({})'.format(item['title'], item['year']) if item['year'] else item['title']
        fanart = item['background'] if item.get('background') else ''
        url = build_url({'action': 'details', 'type': params.get('type'), 'id': item['id'], 'name': item['title'], 'year': item['year'], 'fanart': fanart})
        li = xbmcgui.ListItem(title)
        if item.get('poster') or item.get('background'):
            li.setArt({
                'thumb': item['poster'] if item.get('poster') else None,
                'icon': item['poster'] if item.get('poster') else None,
                'poster': item['poster'] if item.get('poster') else None,
                'fanart': item['background'] if item.get('background') else None
            })
        _set_listitem_info(li, 'video', {'title': title, 'plot': item.get('description', ''), 'year': int(item['year']) if item['year'] else 0})
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)

    next_page_url = build_url({
        'action': 'list',
        'type': params.get('type'),
        'category': params.get('category'),
        'page': str(page + 1)
    })
    if next_button:
        next_li = xbmcgui.ListItem('[Próxima Página]')
        icon = TRANSLATE(os.path.join(homeDir, 'resources','images','proximo.png'))
        next_li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        _set_listitem_info(next_li, 'video', {'mediatype': 'video'})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=next_page_url, listitem=next_li, isFolder=True)

    xbmcplugin.endOfDirectory(ADDON_HANDLE)
    xbmcplugin.setContent(ADDON_HANDLE, 'movies')
    setview('List')      

def list_seasons(params):
    # ... (sem alterações aqui)
    tmdb_id = params['id']
    seasons = tmdb.get_seasons(params['type'], tmdb_id)

    for season in seasons:
        name = season['name']
        url = build_url({
            'action': 'list_episodes',
            'type': params['type'],
            'id': tmdb_id,
            'season_number': str(season['season_number']),
            'poster': params['poster'],
            'name': params['name'],
            'year': params['year']
        })
        li = xbmcgui.ListItem(name)
        if season.get('poster'):
            li.setArt({'thumb': season['poster'], 'icon': season['poster'], 'poster': season['poster']})
        _set_listitem_info(li, 'video', {'title': name, 'plot': season.get('description', ''), 'year': int(season['year']) if season['year'] else 0})
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)

    xbmcplugin.endOfDirectory(ADDON_HANDLE)
    xbmcplugin.setContent(ADDON_HANDLE, 'movies')
    setview('List')      

def list_episodes(params):
    # ... (sem alterações aqui, exceto a action no build_url)
    tmdb_id = params['id']
    season_number = params['season_number']
    episodes = tmdb.get_episodes(params['type'], tmdb_id, season_number)

    for episode in episodes:
        name = '{} - S{}E{}'.format(params['name'], season_number.zfill(2), str(episode['episode_number']).zfill(2))
        url = build_url({
            'action': 'list_streams', ## MODIFICADO: de 'audio' para 'list_streams'
            'type': params['type'],
            'id': tmdb_id,
            'season_number': season_number,
            'episode_number': str(episode['episode_number']),
            'name': params['name'],
            'poster': params['poster'],
            'year': params['year'],
            'fanart': params.get('fanart', '')
        })
        li = xbmcgui.ListItem(name)
        if episode.get('poster'):
            li.setArt({'thumb': episode['poster'], 'icon': episode['poster'], 'poster': episode['poster']})
        _set_listitem_info(li, 'video', {'title': name, 'plot': episode.get('description', ''), 'year': int(episode['year']) if episode['year'] else 0})
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)

    xbmcplugin.endOfDirectory(ADDON_HANDLE)
    xbmcplugin.setContent(ADDON_HANDLE, 'movies')
    setview('List')    

def _stream_title_text(stream):
    return stream.get('title', stream.get('description', '')).replace('\n', ' ').replace('\t', '').replace('SKYFLIX API', 'OnePlay API').strip()

def _stream_bad(stream_title, stream_url, raw_name):
    lowered_title = stream_title.lower()
    lowered_url = (stream_url or '').lower()
    lowered_name = (raw_name or '').lower()
    if 'showbox' in lowered_name:
        return True
    if 'pixeldrain.dev' in lowered_url or 'hindi' in lowered_url:
        return True
    if 'beta' in lowered_url and '.top' in lowered_url:
        return True
    if 'hindi' in lowered_title:
        return True
    return False

def _stream_name_type(stream):
    lowered_name = (stream.get('name', '') or '').lower()
    lowered_desc = (stream.get('description', '') or '').lower()
    if '- 4k' in lowered_name or '2160' in lowered_name:
        return '[B][COLOR orange](4K)[/COLOR][/B]'
    if '1080p' in lowered_name or '1080' in lowered_name:
        return '[B][COLOR gold](1080p)[/COLOR][/B]'
    if '720p' in lowered_name or '720' in lowered_name:
        return '[B][COLOR deepskyblue](720p)[/COLOR][/B]'
    if 'dublado' in lowered_desc:
        return '[B][COLOR yellow](Dublado)[/COLOR][/B]'
    return '[B][COLOR lime](Auto)[/COLOR][/B]'

def _stream_sort_key(stream):
    combined = '{} {} {}'.format(stream.get('name', ''), stream.get('description', ''), stream.get('title', '')).lower()
    is_auto = '- auto' in combined or '(auto)' in combined or combined.endswith(' auto')
    is_dublado = 'dublado' in combined
    is_4k = '4k' in combined or '2160' in combined
    is_1080 = '1080' in combined or 'fhd' in combined
    is_720 = '720' in combined
    is_light = '480' in combined or '360' in combined or 'sd' in combined
    mode = _stream_priority_mode()

    if mode == '1':
        bucket = 0 if is_4k else 1 if is_1080 else 2 if is_720 else 3 if is_dublado else 4 if is_auto else 5
    elif mode == '2':
        bucket = 0 if is_720 else 1 if is_light else 2 if is_auto else 3 if is_dublado else 4 if is_1080 else 5 if is_4k else 6
    else:
        bucket = 0 if is_auto else 1 if is_dublado else 2 if is_1080 else 3 if is_720 else 4 if is_4k else 5
    return (bucket, combined)

def _build_stream_item_name(params, type_, stream_title, name_type):
    if type_ == 'series':
        if name_type:
            return '{} - S{}E{} {} ({})'.format(params['name'], params['season_number'].zfill(2), params['episode_number'].zfill(2), name_type, stream_title)
        return '{} - S{}E{} ({})'.format(params['name'], params['season_number'].zfill(2), params['episode_number'].zfill(2), stream_title)
    if name_type:
        return '{} ({}) {} - {}'.format(params['name'], params['year'], name_type, stream_title)
    return '{} ({}) - {}'.format(params['name'], params['year'], stream_title)

def _build_play_params(params, imdb_id, type_, stream_url, item_name):
    play_params = {
        'action': 'play_item_with_subtitles',
        'video_url': stream_url,
        'imdb_id': imdb_id,
        'type': type_,
        'name': item_name,
        'poster': params['poster'],
        'fanart': params.get('fanart', '')
    }
    if type_ == 'series':
        play_params['season'] = params['season_number']
        play_params['episode'] = params['episode_number']
    return play_params

## MODIFICADO: A função 'audio_options' foi renomeada e completamente reescrita
def list_streams(params):    
    type_ = params['type']
    id_ = params['id']
    imdb_id = tmdb.get_imdb_id_tmdb(id_, type_)

    if not imdb_id:
        _show_error('Não foi possível encontrar o ID do IMDB para este item.', title='Erro', dialog=True)
        return

    if type_ == 'series':
        url_nuvio = 'https://hdhub.thevolecitor.qzz.io/eyJ0b3Jib3giOiJ1bnNldCIsInF1YWxpdGllcyI6IjIxNjBwLDEwODBwLDcyMHAiLCJzb3J0IjoiZGVzYyJ9/stream/series/{}:{}:{}.json'.format(imdb_id, params['season_number'], params['episode_number'])
    else: # movie
        url_nuvio = 'https://hdhub.thevolecitor.qzz.io/eyJ0b3Jib3giOiJ1bnNldCIsInF1YWxpdGllcyI6IjIxNjBwLDEwODBwLDcyMHAiLCJzb3J0IjoiZGVzYyJ9/stream/movie/{}.json'.format(imdb_id)

    try:
        r = HTTP_SESSION.get(url_nuvio, headers={'User-Agent': DEFAULT_USER_AGENT}, timeout=DEFAULT_TIMEOUT)
        r.raise_for_status()
        data = r.json()
    except requests.exceptions.RequestException as e:
        _log_debug('Erro ao buscar streams: {}'.format(e))
        _show_error('Não foi possível obter a lista de streams. A fonte pode estar offline.', title='Erro', dialog=True)
        return

    streams = data.get('streams', [])
    if not streams:
        _show_error('Nenhum stream foi encontrado para este conteúdo.', title='Sem Fontes', dialog=True)
        return

    valid_streams = []
    for stream in streams:
        stream_url = stream.get('url')
        if not stream_url:
            continue
        stream_title = _stream_title_text(stream)
        #if _stream_bad(stream_title, stream_url, stream.get('name', '')):
        #    continue
        valid_streams.append(stream)

    if not valid_streams:
        _show_error('Nenhum stream válido foi encontrado após os filtros.', title='Sem Fontes', dialog=True)
        return

    valid_streams.sort(key=_stream_sort_key)
    _log_debug('Streams válidos após filtros: {}'.format(len(valid_streams)))

    if _setting_bool('autoplaystream', False):
        best_stream = valid_streams[0]
        stream_title = _stream_title_text(best_stream)
        item_name = _build_stream_item_name(params, type_, stream_title, _stream_name_type(best_stream))
        _notify('OnePlay', 'Autoplay da melhor fonte ativado.', xbmcgui.NOTIFICATION_INFO, 2200)
        autoplay_params = _build_play_params(params, imdb_id, type_, best_stream.get('url'), item_name)
        autoplay_params['direct_play'] = '1'
        play_item_with_subtitles(autoplay_params)
        return

    for stream in valid_streams:
        stream_title = _stream_title_text(stream)
        stream_url = stream.get('url')
        item_name = _build_stream_item_name(params, type_, stream_title, _stream_name_type(stream))
        play_params = _build_play_params(params, imdb_id, type_, stream_url, item_name)
        url_to_play = build_url(play_params)

        li = xbmcgui.ListItem(item_name)
        li.setArt({'thumb': params['poster'], 'icon': params['poster'], 'fanart': params.get('fanart', '')})
        _set_listitem_info(li, 'video', {'title': item_name, 'plot': stream_title})
        li.setProperty('IsPlayable', 'true')
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url_to_play, listitem=li, isFolder=False)

    xbmcplugin.endOfDirectory(ADDON_HANDLE)
    xbmcplugin.setContent(ADDON_HANDLE, 'videos')
    setview('WideList')

def get_and_download_subtitles(imdb_id, type_, season=None, episode=None):
    try:
        subtitle_paths = subtitles.get_and_download_subtitles(
            imdb_id,
            type_,
            season=season,
            episode=episode,
            addon=ADDON,
            clear_before=True
        )
    except Exception as exc:
        _log_debug('Erro ao buscar legendas: {}'.format(exc))
        subtitle_paths = []

    if not subtitle_paths and _setting_bool('legendasauto', True) and _setting_bool('notificarsemlegenda', False):
        _notify('OnePlay', 'Nenhuma legenda encontrada.', xbmcgui.NOTIFICATION_INFO, 2500)

    return subtitle_paths


def play_item_with_subtitles(params):
    video_url = params.get('video_url')
    name = params.get('name', 'Reproduzindo')
    imdb_id = params.get('imdb_id')
    type_ = params.get('type')

    subtitle_paths = []
    dialog = None
    if _setting_bool('legendasauto', True):
        dialog = xbmcgui.DialogProgress()
        dialog.create('OnePlay', 'Buscando legendas...')
        dialog.update(50)

        subtitle_paths = get_and_download_subtitles(
            imdb_id,
            type_,
            params.get('season'),
            params.get('episode')
        )

        try:
            dialog.close()
        except Exception:
            pass

    li = xbmcgui.ListItem(name)
    li.setArt({'thumb': params.get('poster'), 'icon': params.get('poster'), 'fanart': params.get('fanart')})
    _set_listitem_info(li, 'video', {'title': name})
    li.setPath(video_url) # Define o caminho do vídeo

    if subtitle_paths:
        try:
            li.setSubtitles(subtitle_paths)
        except Exception:
            pass

    if params.get('direct_play') == '1':
        player = xbmc.Player()
        player.play(item=video_url, listitem=li)
        if subtitle_paths:
            for _ in range(40):
                xbmc.sleep(150)
                try:
                    if player.isPlaying():
                        player.setSubtitles(subtitle_paths[0])
                        break
                except Exception:
                    break
        return

    xbmcplugin.setResolvedUrl(handle=ADDON_HANDLE, succeeded=True, listitem=li) # Forma correta para itens "IsPlayable=true"

def _movie_streams_params(params, meta):
    return {
        'action': 'list_streams',
        'type': params['type'],
        'id': params['id'],
        'name': meta['name'],
        'poster': meta['poster'],
        'year': meta['year'],
        'fanart': params.get('fanart', meta['background'])
    }


def _render_movie_details_menu(params, meta):
    content_label = 'Ver fontes'
    content_plot = 'Abrir a lista de fontes disponíveis para este filme.'
    stream_params = _movie_streams_params(params, meta)
    item_name = '{} ({})'.format(meta['name'], meta['year']) if meta['year'] else meta['name']

    li_streams = xbmcgui.ListItem(content_label)
    _set_listitem_info(li_streams, 'video', {'title': content_label, 'plot': content_plot, 'mediatype': 'video'})
    _set_listitem_info(li_streams, type="Video", infoLabels={"Title": content_label, "Plot": content_plot})
    li_streams.setArt({'thumb': meta.get('poster'), 'icon': meta.get('poster'), 'poster': meta.get('poster'), 'fanart': meta.get('background')})
    xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=build_url(stream_params), listitem=li_streams, isFolder=True)

    info_url = build_url({'action': 'show_text_dialog', 'title': item_name, 'text': meta.get('description', '')})
    li_info = xbmcgui.ListItem('Sinopse')
    _set_listitem_info(li_info, 'video', {'title': item_name, 'plot': meta.get('description', ''), 'mediatype': 'video'})
    _set_listitem_info(li_info, type="Video", infoLabels={"Title": item_name, "Plot": meta.get('description', '')})
    li_info.setArt({'thumb': meta.get('poster'), 'icon': meta.get('poster'), 'poster': meta.get('poster'), 'fanart': meta.get('background')})
    xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=info_url, listitem=li_info, isFolder=False)

    xbmcplugin.endOfDirectory(ADDON_HANDLE)
    xbmcplugin.setContent(ADDON_HANDLE, 'movies')
    setview('List')


def show_details(params):
    meta = tmdb.get_meta(params['type'], params['id'])
    name = '{} ({})'.format(meta['name'], meta['year']) if meta['year'] else meta['name']
    if params['type'] == 'series':
        url = build_url({'action': 'list_seasons', 'type': params['type'], 'id': params['id'], 'name': meta['name'], 'poster': meta['poster'], 'year': meta['year'], 'fanart': params.get('fanart', meta['background'])})
        li = xbmcgui.ListItem(name)
        _set_listitem_info(li, 'video', {'title': name, 'plot': meta['description'], 'year': int(meta['year']) if meta['year'] else 0})
        li.setArt({'thumb': meta['poster'], 'poster': meta['poster'], 'fanart': meta['background']})
        _set_listitem_info(li, 'video', {'mediatype': 'video'})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)
        xbmcplugin.endOfDirectory(ADDON_HANDLE)
        xbmcplugin.setContent(ADDON_HANDLE, 'movies')
        setview('List')
    else: # movie
        if _movie_navigation_mode() == 'details':
            _render_movie_details_menu(params, meta)
            return
        play_params = _movie_streams_params(params, meta)
        list_streams(play_params)

def search(params):
    # ... (sem alterações aqui)
    if not params.get('query'):
        keyboard = xbmc.Keyboard('', 'Digite o nome')
        keyboard.doModal()
        if not keyboard.isConfirmed():
            return
        query = keyboard.getText()
        if not query:
            dialog = xbmcgui.Dialog()
            dialog.ok("Erro", "Por favor, insira um termo de busca válido.")
            return
        if isinstance(query, text_type):
            query = query.encode('utf-8') if sys.version_info[0] == 2 else query
        params = {'action': 'search', 'query': query, 'page': '1'}
        search(params)
        return

    query = params.get('query')
    if not query:
        dialog = xbmcgui.Dialog()
        dialog.ok("Erro", "Termo de busca inválido.")
        return     
    page = int(params.get('page', '1'))
    next_button = False
    for type_ in ['movie', 'series']:
        items = tmdb.search(type_, query, page)
        if items:
            next_button = True
        for item in items:
            fanart = item['background'] if item.get('background') else ''
            title = '{} ({})'.format(item['title'], item['year']) if item['year'] else item['title']
            url = build_url({'action': 'details', 'type': type_, 'id': item['id'], 'name': item['title'], 'year': item['year'], 'fanart': fanart})
            li = xbmcgui.ListItem(title)
            if item.get('poster') or item.get('background'):
                li.setArt({
                    'thumb': item['poster'] if item.get('poster') else None,
                    'poster': item['poster'] if item.get('poster') else None,
                    'fanart': item['background'] if item.get('background') else None
                })
            _set_listitem_info(li, 'video', {'title': title, 'plot': item.get('description', ''), 'year': int(item['year']) if item['year'] else 0})
            _set_listitem_info(li, 'video', {'mediatype': 'video'})
            xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=url, listitem=li, isFolder=True)

    next_page_url = build_url({
        'action': 'search',
        'query': query,
        'page': str(page + 1)
    })
    if next_button:
        next_li = xbmcgui.ListItem('[Próxima Página]')
        icon = TRANSLATE(os.path.join(homeDir, 'resources','images','proximo.png'))
        next_li.setArt({'icon': icon, 'thumb': icon, 'poster': icon, 'fanart': addonFanart})
        _set_listitem_info(next_li, 'video', {'mediatype': 'video'})
        xbmcplugin.addDirectoryItem(handle=ADDON_HANDLE, url=next_page_url, listitem=next_li, isFolder=True)

    xbmcplugin.endOfDirectory(ADDON_HANDLE)
    xbmcplugin.setContent(ADDON_HANDLE, 'movies')
    setview('List')     

def router(paramstring):
    params = dict(parse_qsl(paramstring, keep_blank_values=True))
    action = params.get('action')

    ## MODIFICADO: Atualização do roteador para as novas ações
    if action is None:
        start_mode = _setting_text('paginainicial', '0')
        if start_mode == '1':
            menu_tv()
        elif start_mode == '2':
            menu_movies()
        elif start_mode == '3':
            menu_series()
        else:
            home()
    elif action == 'menu_movies':
        menu_movies()
    elif action == 'tv':
        menu_tv()
    elif action == 'openm3u':
        openm3u(params)
    elif action == 'pluto_entry':
        pluto_entry(params)
    elif action == 'channels_pluto':
        channels_pluto(psid=(params.get('psid') or ''))
    elif action == 'channels_pluto_categories':
        channels_pluto_categories(psid=(params.get('psid') or ''))
    elif action == 'channels_pluto_category':
        channels_pluto_category(params, psid=(params.get('psid') or ''))
    elif action == 'play_pluto':
        play_pluto(params)
    elif action == 'opengroup':
        opengroup(params)
    elif action == 'play_proxy':
        play_proxy(params)
    elif action == 'menu_series':
        menu_series()
    elif action == 'list':
        list_items(params)
    elif action == 'details':
        show_details(params)
    elif action == 'search':
        search(params)
    elif action == 'open_settings':
        open_settings()
    elif action == 'clear_subtitles_cache':
        clear_subtitles_cache(notify=True)
        _complete_plugin_action(succeeded=True)
    elif action == 'clear_epg_cache':
        clear_epg_cache(notify=True)
        _complete_plugin_action(succeeded=True)
    elif action == 'clear_tv_history':
        clear_tv_history(notify=True)
        _complete_plugin_action(succeeded=True)
    elif action == 'change_tv_adult_pin':
        change_tv_adult_pin()
        _complete_plugin_action(succeeded=True)
    elif action == 'open_tv_adult_groups':
        open_tv_adult_groups(params)
    elif action == 'show_text_dialog':
        _show_text_dialog(params.get('title', 'OnePlay'), params.get('text', ''))
        _complete_plugin_action(succeeded=True)
    elif action == 'list_seasons':
        list_seasons(params)
    elif action == 'list_episodes':
        list_episodes(params)
    elif action == 'list_streams': # Rota antiga 'audio' agora é 'list_streams'
        list_streams(params)
    elif action == 'play_item_with_subtitles': # Rota nova para reprodução com legendas
        play_item_with_subtitles(params)
    elif action == 'donate':
        show_qr_code()
        _complete_plugin_action(succeeded=True)
    else:
        home()

if __name__ == '__main__':
    router(sys.argv[2][1:])