import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')

from gi.repository import Gtk, Gdk, Adw, GLib
import subprocess, random, string, json, socket, os, time
from pathlib import Path
from utils.game_detector import GameDetector

from utils.config import Config
import subprocess, os, tempfile, threading
from gi.repository import GLib
from utils.i18n import _
from utils.icons import create_icon_widget
from ui.sunshine_preferences import SunshineConfigManager
class HostView(Gtk.Box):
    def __init__(self):
        self.loading_settings = True
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        self.config = Config()
        self.is_hosting = False
        self.process = None # Initialize to avoid AttributeError
        self.pin_code = None
        self.private_audio_apps = set()
        
        from host.sunshine_manager import SunshineHost
        self.sunshine = SunshineHost(Path.home() / '.config' / 'big-remoteplay' / 'sunshine')
        
        if self.sunshine.is_running():
            self.is_hosting = True
            
        self.available_monitors = self.detect_monitors()
        self.available_gpus = self.detect_gpus()
        self.setup_ui()
        
        self.game_detector = GameDetector()
        self.detected_games = {'Steam': [], 'Lutris': []}
        self.load_settings()
        self.connect_settings_signals()
        self.loading_settings = False
        
        # Ensure config is correct (API enabled)
        if hasattr(self, '_ensure_sunshine_config'):
             self._ensure_sunshine_config()
             
        self.sync_ui_state()
        
    def detect_monitors(self):
        monitors = [(_('Automatic'), 'auto')]
        is_wayland = os.environ.get('XDG_SESSION_TYPE') == 'wayland'
        
        # Method: GDK (Most consistent for labels and Wayland indices)
        names = []
        try:
            display = Gdk.Display.get_default()
            if display:
                monitor_list = display.get_monitors()
                for i in range(monitor_list.get_n_items()):
                    monitor = monitor_list.get_item(i)
                    conn = monitor.get_connector()
                    if conn:
                        manufacturer = monitor.get_manufacturer() or ""
                        model = monitor.get_model() or ""
                        label_parts = []
                        if manufacturer: label_parts.append(manufacturer)
                        if model: label_parts.append(model)
                        label = " ".join(label_parts) if label_parts else "Monitor"
                        
                        # Value logic: Wayland uses 0, 1, 2... | X11 uses HDMI-A-1, etc.
                        val = str(i) if is_wayland else conn
                        full_label = f"{label} ({conn})"
                        monitors.append((full_label, val))
                        names.append(conn)
        except Exception as e:
            print(f"Error detecting GDK monitors: {e}")

        # Fallback for X11/DRM if GDK didn't find everything
        if not is_wayland:
            # Xrandr (Reinforcement for X11)
            try:
                cmd = "xrandr --listmonitors | tail -n +2 | awk '{print $NF}'"
                res = subprocess.check_output(cmd, shell=True, text=True)
                for n in res.splitlines():
                    n = n.strip()
                    if n and n not in names:
                        monitors.append((f"Display ({n})", n))
                        names.append(n)
            except: pass

            # DRM (Reinforcement for KMS/DRM)
            try:
                from pathlib import Path
                for p in Path('/sys/class/drm').glob('card*-*'):
                    if (p/'status').exists() and (p/'status').read_text().strip() == 'connected':
                        name = p.name.split('-', 1)[1]
                        if name not in names:
                            monitors.append((f"DRM Display ({name})", name))
                            names.append(name)
            except: pass
            
        return monitors

    def detect_gpus(self):
        gpus = []
        try:
            lspci = subprocess.check_output(['lspci'], text=True).lower()
            if 'nvidia' in lspci:
                try:
                    subprocess.check_call(['nvidia-smi'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                    gpus.append({'label': 'NVENC (NVIDIA)', 'encoder': 'nvenc', 'adapter': 'auto'})
                except: pass
            if 'intel' in lspci: gpus.append({'label': 'VAAPI (Intel Quicksync)', 'encoder': 'vaapi', 'adapter': '/dev/dri/renderD128'})
        except: pass
        try:
            from pathlib import Path
            if Path('/dev/dri').exists():
                for node in sorted(list(Path('/dev/dri').glob('renderD*'))):
                    if not any(str(node) == g['adapter'] for g in gpus):
                        gpus.append({'label': f"VAAPI (Adapter {node.name})", 'encoder': 'vaapi', 'adapter': str(node)})
        except: pass
        gpus.extend([{'label':'Vulkan (Exp)', 'encoder':'vulkan', 'adapter':'auto'}, {'label':'Software', 'encoder':'software', 'adapter':'auto'}])
        return gpus
        
    def setup_ui(self):

        clamp = Adw.Clamp()
        clamp.set_maximum_size(800)
        for margin in ['top', 'bottom', 'start', 'end']:
            getattr(clamp, f'set_margin_{margin}')(24)
        
        content = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=24)
        
        self.loading_bar = Gtk.ProgressBar()
        self.loading_bar.add_css_class('osd')
        self.loading_bar.set_visible(False)
        content.append(self.loading_bar)
        
        from .performance_monitor import PerformanceMonitor
        self.perf_monitor = PerformanceMonitor(sunshine=self.sunshine)
        self.perf_monitor.set_visible(True)
        self.perf_monitor.set_connection_status("Localhost", _("Sunshine Offline"), False)
        
        self.header = Adw.PreferencesGroup()
        self.header.set_header_suffix(create_icon_widget('network-server-symbolic', size=24))
        self.header.set_title(_('Host Server'))
        self.header.set_description(_('Configure and share your game for friends to connect'))
        
        
        game_group = Adw.PreferencesGroup()
        game_group.set_title(_('Game Configuration'))
        
        reset_btn = Gtk.Button()
        reset_btn.set_child(create_icon_widget("edit-undo-symbolic", size=16))
        reset_btn.add_css_class("flat")
        reset_btn.set_tooltip_text(_("Reset to Defaults"))
        reset_btn.connect("clicked", self.on_reset_clicked)
        game_group.set_header_suffix(reset_btn)
        
        self.game_mode_row = Adw.ComboRow(); self.game_mode_row.set_title(_('Game Mode')); self.game_mode_row.set_subtitle(_('Select game source'))
        modes = Gtk.StringList()
        for m in [_('Full Desktop'), 'Steam', 'Lutris', _('Custom App')]: modes.append(m)
        self.game_mode_row.set_model(modes); self.game_mode_row.set_selected(0); self.game_mode_row.connect('notify::selected', self.on_game_mode_changed); game_group.add(self.game_mode_row)
        
        self.platform_games_expander = Adw.ExpanderRow()
        self.platform_games_expander.set_title(_("Game Selection"))
        self.platform_games_expander.set_subtitle(_("Choose game from list"))
        self.platform_games_expander.set_visible(False)
        
        self.game_list_row = Adw.ComboRow()
        self.game_list_row.set_title(_('Select Game'))
        self.game_list_row.set_subtitle(_('Choose game from list'))
        self.game_list_model = Gtk.StringList()
        self.game_list_row.set_model(self.game_list_model)
        self.platform_games_expander.add_row(self.game_list_row)
        game_group.add(self.platform_games_expander)
        
        self.custom_app_expander = Adw.ExpanderRow()
        self.custom_app_expander.set_title(_("Application Details"))
        self.custom_app_expander.set_subtitle(_("Configure name and command"))
        self.custom_app_expander.set_visible(False)
        
        self.custom_name_entry = Adw.EntryRow()
        self.custom_name_entry.set_title(_('Application Name'))
        self.custom_app_expander.add_row(self.custom_name_entry)
        
        self.custom_cmd_entry = Adw.EntryRow()
        self.custom_cmd_entry.set_title(_('Command'))
        self.custom_app_expander.add_row(self.custom_cmd_entry)
        game_group.add(self.custom_app_expander)
        
        self.streaming_expander = Adw.ExpanderRow()
        self.streaming_expander.set_title(_('Streaming Settings'))
        self.streaming_expander.set_subtitle(_('Quality and Players'))
        self.streaming_expander.set_icon_name('preferences-desktop-display-symbolic')
        
        # Resolution Row
        self.resolution_row = Adw.ComboRow()
        self.resolution_row.set_title(_("Resolution"))
        self.resolution_row.set_subtitle(_("Stream resolution"))
        res_model = Gtk.StringList()
        for res in ["720p", "1080p", "1440p", "4K", _("Custom")]:
            res_model.append(res)
        self.resolution_row.set_model(res_model)
        self.resolution_row.set_selected(1) # Default 1080p
        self.streaming_expander.add_row(self.resolution_row)
        
        # FPS Row
        self.fps_row = Adw.ComboRow()
        self.fps_row.set_title(_("Frame Rate (FPS)"))
        self.fps_row.set_subtitle(_("Frames per second"))
        fps_model = Gtk.StringList()
        for fps in ["30", "60", "120", "144", _("Custom")]:
            fps_model.append(fps)
        self.fps_row.set_model(fps_model)
        self.fps_row.set_selected(1) # Default 60
        self.streaming_expander.add_row(self.fps_row)
        
        # Bandwidth Row
        self.bandwidth_row = Adw.SpinRow()
        self.bandwidth_row.set_title(_("Bandwidth Limit (Mbps)"))
        self.bandwidth_row.set_subtitle(_("Max bitrate (0 = Unlimited)"))
        
        # Use simple numeric adjustment
        adj = Gtk.Adjustment(value=0, lower=0, upper=500, step_increment=5, page_increment=10)
        self.bandwidth_row.set_adjustment(adj)
        self.streaming_expander.add_row(self.bandwidth_row)

        game_group.add(self.streaming_expander)
        
        self.hardware_expander = Adw.ExpanderRow()
        self.hardware_expander.set_title(_('Hardware and Capture'))
        self.hardware_expander.set_subtitle(_('Monitor, GPU, and Capture Method'))
        self.hardware_expander.set_icon_name('video-display-symbolic')

        self.monitor_row = Adw.ComboRow()
        self.monitor_row.set_title(_('Monitor / Display'))
        self.monitor_row.set_subtitle(_('Select the display to capture'))
        monitor_model = Gtk.StringList()
        for label, _val in self.available_monitors: monitor_model.append(label)
        self.monitor_row.set_model(monitor_model)
        self.monitor_row.set_selected(0)
        self.hardware_expander.add_row(self.monitor_row)
        
        self.gpu_row = Adw.ComboRow()
        self.gpu_row.set_title(_('Graphics Card / Encoder'))
        self.gpu_row.set_subtitle(_('Choose hardware for video encoding'))
        gpu_model = Gtk.StringList()
        for gpu_info in self.available_gpus: gpu_model.append(gpu_info['label'])
        self.gpu_row.set_model(gpu_model)
        self.gpu_row.set_selected(0)
        self.hardware_expander.add_row(self.gpu_row)
        
        self.platform_row = Adw.ComboRow()
        self.platform_row.set_title(_('Capture Method'))
        self.platform_row.set_subtitle(_('Wayland (recommended), X11 (legacy), or KMS (direct)'))
        platform_model = Gtk.StringList()
        for p in [_('Automatic'), 'Wayland', 'X11', _('KMS (Direct)')]: platform_model.append(p)
        self.platform_row.set_model(platform_model)
        import os
        session_type = os.environ.get('XDG_SESSION_TYPE', '').lower()
        self.platform_row.set_selected(1 if session_type == 'wayland' else 2 if session_type == 'x11' else 0)
        self.hardware_expander.add_row(self.platform_row)
        
        # New "Performance" settings as requested
        self.codecs_row = Adw.SwitchRow()
        self.codecs_row.set_title(_("Efficient Codecs (HEVC/AV1)"))
        self.codecs_row.set_subtitle(_("Enable H.265/AV1 for better quality at lower bitrate (Requires support)"))
        self.codecs_row.set_active(True)
        self.hardware_expander.add_row(self.codecs_row)
        
        self.optimization_row = Adw.ComboRow()
        self.optimization_row.set_title(_("Optimization Mode"))
        self.optimization_row.set_subtitle(_("Balance between responsiveness and image quality"))
        opt_model = Gtk.StringList()
        opt_model.append(_("Low Latency (Fastest)"))
        opt_model.append(_("Balanced (Default)"))
        opt_model.append(_("High Quality (Best Image)"))
        self.optimization_row.set_model(opt_model)
        self.optimization_row.set_selected(1) # Balanced default
        self.hardware_expander.add_row(self.optimization_row)
        
        self.wifi_row = Adw.SwitchRow()
        self.wifi_row.set_title(_("Wi-Fi / Unstable Network Mode"))
        self.wifi_row.set_subtitle(_("Increases error correction (FEC) to prevent glitches"))
        self.wifi_row.set_active(False)
        self.hardware_expander.add_row(self.wifi_row)
        
        game_group.add(self.hardware_expander)
        
        # --- Audio Group ---
        audio_group = Adw.PreferencesGroup()
        audio_group.set_title(_('Audio'))
        audio_group.set_description(_('Sound settings'))

        # 1. Host Output (Always visible, serves as the "Host" part of Host+Guest)
        self.audio_output_row = Adw.ComboRow()
        self.audio_output_row.set_title(_('Host Audio Output'))
        self.audio_output_row.set_subtitle(_('Where YOU will hear the game sound'))
        self.audio_output_row.set_icon_name('audio-speakers-symbolic')
        self.audio_output_row.connect('notify::selected', self.on_audio_output_changed)
        audio_group.add(self.audio_output_row)

        # 2. Audio Mode ComboRow
        self.audio_mode_row = Adw.ComboRow()
        self.audio_mode_row.set_title(_('Audio Output Mode'))
        self.audio_mode_row.set_subtitle(_('Determine where the game sound will be played'))
        self.audio_mode_row.set_icon_name('audio-volume-medium-symbolic')
        
        mode_model = Gtk.StringList()
        mode_model.append(_('Automatic'))    # Index 0
        mode_model.append(_('Guest'))        # Index 1
        mode_model.append(_('Host'))         # Index 2
        mode_model.append(_('Guest + Host')) # Index 3
        
        self.audio_mode_row.set_model(mode_model)
        self.audio_mode_row.connect('notify::selected', self.on_audio_mode_changed)
        audio_group.add(self.audio_mode_row)


        
        # 3. Mixer (Only if Streaming is Enabled)
        self.audio_mixer_expander = Adw.ExpanderRow()
        self.audio_mixer_expander.set_title(_("Audio Mixer (Sources)"))
        self.audio_mixer_expander.set_subtitle(_("Manage audio sources"))
        self.audio_mixer_expander.set_icon_name('audio-volume-high-symbolic')
        self.audio_mixer_expander.set_visible(True) # Visibility controlled by switch
        
        audio_group.add(self.audio_mixer_expander)
        
        self.load_audio_outputs()
        
        self.advanced_expander = Adw.ExpanderRow()
        self.advanced_expander.set_title(_('Advanced Settings'))
        self.advanced_expander.set_subtitle(_('Input, Network, and Access'))
        self.advanced_expander.set_icon_name('preferences-system-symbolic')
        
        self.upnp_row = Adw.SwitchRow()
        self.upnp_row.set_title(_('Automatic UPnP'))
        self.upnp_row.set_subtitle(_('Automatically configure router ports'))
        self.upnp_row.set_active(True)
        self.advanced_expander.add_row(self.upnp_row)

        self.ipv6_row = Adw.SwitchRow()
        self.ipv6_row.set_title(_('Address Family (IPv4 + IPv6)'))
        self.ipv6_row.set_subtitle(_('Enable simultaneous IPv4 and IPv6 support on server'))
        self.ipv6_row.set_active(True)
        self.advanced_expander.add_row(self.ipv6_row)
        
        self.webui_anyone_row = Adw.SwitchRow()
        self.webui_anyone_row.set_title(_('Origin Web UI Allowed (WAN)'))
        self.webui_anyone_row.set_subtitle(_('Allows anyone to access the web interface (Anyone may access Web UI)'))
        self.webui_anyone_row.set_active(False)
        self.advanced_expander.add_row(self.webui_anyone_row)
        
        self.firewall_row = Adw.ActionRow()
        self.firewall_row.set_title(_("Configure Firewall (IPv6)"))
        self.firewall_row.set_subtitle(_("Open TCP/UDP ports required for external connection"))
        self.firewall_row.set_icon_name('network-workgroup-symbolic')
        
        fw_btn = Gtk.Button(label=_("Configure"))
        fw_btn.connect("clicked", self.on_configure_firewall_clicked)
        fw_btn.set_valign(Gtk.Align.CENTER)
        self.firewall_row.add_suffix(fw_btn)
        self.advanced_expander.add_row(self.firewall_row)
        
        game_group.add(self.advanced_expander)
        
        button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        button_box.set_halign(Gtk.Align.CENTER)
        button_box.set_margin_top(12)
        button_box.set_margin_bottom(24)
        
        self.start_button = Gtk.Button()
        self.start_button.add_css_class('pill')
        self.start_button.add_css_class('suggested-action')
        self.start_button.set_size_request(200, 50)
        
        btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        btn_box.set_halign(Gtk.Align.CENTER)
        self.start_btn_spinner = Gtk.Spinner()
        self.start_btn_spinner.set_visible(False)
        self.start_btn_label = Gtk.Label(label=_('Start Server'))
        btn_box.append(self.start_btn_spinner)
        btn_box.append(self.start_btn_label)
        self.start_button.set_child(btn_box)
        
        self.start_button.connect('clicked', self.toggle_hosting)
        
        self.configure_button = Gtk.Button(label=_('Configure Sunshine'))
        self.configure_button.add_css_class('pill')
        self.configure_button.set_size_request(200, 50)
        self.configure_button.connect('clicked', self.open_sunshine_config)
        
        button_box.append(self.start_button)
        button_box.append(self.configure_button)
        
        self.pin_button = Gtk.Button(label=_('Enter PIN'))
        self.pin_button.add_css_class('pill')
        self.pin_button.add_css_class('success-action')
        self.pin_button.set_size_request(180, -1)
        self.pin_button.set_visible(False)
        self.pin_button.connect('clicked', self.open_pin_dialog)
        button_box.append(self.pin_button)
        
        self.create_summary_box()
        
        # View Switcher and Stack
        self.view_stack = Adw.ViewStack()
        self.view_stack.set_vexpand(True)
        
        # 1. Information Page
        info_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        info_page.append(self.summary_box)
        self.view_stack.add_titled_with_icon(info_page, "info", _("Information"), "dialog-information-symbolic")
        
        # 2. Configuration Page
        config_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        config_page.append(game_group)
        self.view_stack.add_titled_with_icon(config_page, "config", _("Game"), "preferences-system-symbolic")
        
        # 3. Audio Page
        audio_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        audio_page.append(audio_group)
        self.view_stack.add_titled_with_icon(audio_page, "audio", _("Audio"), "audio-x-generic-symbolic")

        # 4. PIN Code Page
        pin_page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12)
        pin_group = Adw.PreferencesGroup()
        pin_group.set_title(_("Use PIN Code to Connect"))
        pin_group.set_description(_("Share this with the guest. Local network is required."))
        
        pin_row = Adw.ActionRow()
        pin_row.set_title(_("PIN Code"))
        pin_row.set_icon_name("dialog-password-symbolic")
        
        self.pin_display_label = Gtk.Label(label="000000")
        self.pin_display_label.add_css_class("title-1")
        self.pin_display_label.set_selectable(True)
        
        copy_btn = Gtk.Button()
        copy_btn.set_child(create_icon_widget("edit-copy-symbolic", size=16))
        copy_btn.add_css_class("flat")
        copy_btn.set_valign(Gtk.Align.CENTER)
        copy_btn.set_tooltip_text(_("Copy PIN"))
        copy_btn.connect("clicked", lambda b: self.copy_field_value('pin'))
        
        pin_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
        pin_box.append(self.pin_display_label)
        pin_box.append(copy_btn)
        
        pin_row.add_suffix(pin_box)
        pin_group.add(pin_row)
        pin_page.append(pin_group)
        self.view_stack.add_titled_with_icon(pin_page, "pin_code", _("PIN Code"), "dialog-password-symbolic")
        self.pin_page = pin_page
        self.pin_stack_page = self.view_stack.get_page(pin_page)
        self.pin_stack_page.set_visible(True) # Always visible
        self.pin_page.set_sensitive(False) # But blocked by default
        
        # Register PIN for updates (it was removed from summary_box)
        self.field_widgets['pin'] = {
            'label': self.pin_display_label,
            'real_value': '',
            'revealed': True
        }

        # Switcher setup
        view_switcher = Adw.ViewSwitcher()
        view_switcher.set_stack(self.view_stack)
        view_switcher.set_policy(Adw.ViewSwitcherPolicy.WIDE)
        view_switcher.set_halign(Gtk.Align.CENTER)
        view_switcher.set_margin_top(12)
        view_switcher.set_margin_bottom(12)
        
        content.append(self.header)
        content.append(self.perf_monitor)
        content.append(button_box)
        content.append(view_switcher)
        content.append(self.view_stack)
        
        clamp.set_child(content)
        scroll = Gtk.ScrolledWindow()
        scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        scroll.set_vexpand(True)
        scroll.set_child(clamp)
        self.append(scroll)
        
        self.start_audio_watchdog()

    def start_audio_watchdog(self):
        # Watchdog simplified or removed, as flow is explicitly controlled
        pass

    def _check_audio_state(self):
        return True

    def _get_sunshine_conf_path(self):
        from pathlib import Path
        return Path.home() / '.config' / 'big-remoteplay' / 'sunshine' / 'sunshine.conf'

    def _get_sunshine_creds(self):
        conf_file = self._get_sunshine_conf_path()
        if not conf_file.exists(): return None
        
        user = None
        password = None
        
        try:
            with open(conf_file, 'r') as f:
                for line in f:
                    if '=' in line:
                        key, val = line.strip().split('=', 1)
                        key = key.strip()
                        val = val.strip()
                        if key == 'sunshine_user': user = val
                        elif key == 'sunshine_password': password = val
            
            if user and password:
                return (user, password)
        except: pass
        return None

    def _save_sunshine_creds(self, user, password):
        conf_file = self._get_sunshine_conf_path()
        lines = []
        if conf_file.exists():
            try:
                with open(conf_file, 'r') as f:
                    lines = f.readlines()
            except: pass
        
        new_lines = []
        found_user = False
        found_pass = False
        
        for line in lines:
            if '=' in line:
                key, _ = line.split('=', 1)
                key = key.strip()
                if key == 'sunshine_user':
                    new_lines.append(f"sunshine_user = {user}\n")
                    found_user = True
                    continue
                elif key == 'sunshine_password':
                    new_lines.append(f"sunshine_password = {password}\n")
                    found_pass = True
                    continue
            new_lines.append(line)
            
        if not found_user: new_lines.append(f"sunshine_user = {user}\n")
        if not found_pass: new_lines.append(f"sunshine_password = {password}\n")

        # Configs required for API and operation
        required = {
            "credentials": f"sunshine:{password}",
            "log_level": "2",
            "port": "47989",
            "webserver": "0.0.0.0",
            "enable_api_endpoints": "true"
        }
        
        final_lines = []
        existing_keys = set()
        
        # Process existing + updated user/pass lines
        for line in new_lines:
            if '=' in line:
                key = line.split('=')[0].strip()
                if key in required:
                    final_lines.append(f"{key} = {required[key]}\n")
                    existing_keys.add(key)
                    continue
            final_lines.append(line)
            
        # Append missing required configs
        for k, v in required.items():
            if k not in existing_keys:
                final_lines.append(f"{k} = {v}\n")
        
        try:
            conf_file.parent.mkdir(parents=True, exist_ok=True)
            with open(conf_file, 'w') as f:
                f.writelines(final_lines)
        except Exception as e:
            print(f"Error saving Sunshine creds: {e}")

    def _ensure_sunshine_config(self):
        """Ensures sunshine.conf has required API settings"""
        conf_file = self._get_sunshine_conf_path()
        if not conf_file.exists(): return
        
        try:
            lines = []
            with open(conf_file, 'r') as f:
                lines = f.readlines()
            
            config_map = {}
            for line in lines:
                if '=' in line:
                    k, v = line.split('=', 1)
                    config_map[k.strip()] = v.strip()
            
            pwd = config_map.get('sunshine_password', '')
            
            # If we have a password but no credentials line or missing API config
            updates_needed = False
            required = {
                "log_level": "2",
                "port": "47989",
                "webserver": "0.0.0.0",
                "enable_api_endpoints": "true"
            }
            if pwd and 'credentials' not in config_map:
                 required['credentials'] = f"sunshine:{pwd}"
            
            for k, v in required.items():
                if k not in config_map:
                    updates_needed = True
            
            if updates_needed:
                final_lines = lines.copy()
                if final_lines and not final_lines[-1].endswith('\n'):
                    final_lines[-1] += '\n'
                    
                for k, v in required.items():
                    if k not in config_map:
                        final_lines.append(f"{k} = {v}\n")
                
                with open(conf_file, 'w') as f:
                    f.writelines(final_lines)
                
        except Exception as e:
            print(f"Error ensuring sunshine config: {e}")

    def open_pin_dialog(self, _widget):
        self._ensure_sunshine_config() # Ensure config before trying to use API
        
        # Load saved credentials
        conf = SunshineConfigManager()
        saved_user = conf.get('sunshine_user', '')
        saved_pass = conf.get('sunshine_password', '')
        if saved_user == 'None': saved_user = ''
        if saved_pass == 'None': saved_pass = ''
        
        dialog = Adw.MessageDialog(
            heading=_("Insert PIN"), 
            body=_("Enter the PIN displayed on the client device (Moonlight).")
        )
        dialog.set_transient_for(self.get_root())
        
        # Grupo de preferências para conter os campos
        grp = Adw.PreferencesGroup()
        
        pin_row = Adw.EntryRow(title=_("PIN"))
        
        name_row = Adw.EntryRow(title=_("Device Name"))
        name_row.set_text(socket.gethostname())
        
        user_row = Adw.EntryRow(title=_("Sunshine User"))
        if saved_user: user_row.set_text(saved_user)
        
        pass_row = Adw.PasswordEntryRow(title=_("Sunshine Password"))
        if saved_pass: pass_row.set_text(saved_pass)
        
        save_chk = Adw.SwitchRow(title=_("Save Password"))
        save_chk.set_subtitle(_("Save credentials to Sunshine preferences"))
        save_chk.set_active(bool(saved_user and saved_pass))
        
        grp.add(pin_row)
        grp.add(name_row)
        grp.add(user_row)
        grp.add(pass_row)
        grp.add(save_chk)
        
        dialog.set_extra_child(grp)
        
        dialog.add_response("cancel", _("Cancel"))
        dialog.add_response("ok", _("Send"))
        dialog.set_response_appearance("ok", Adw.ResponseAppearance.SUGGESTED)
        
        def on_response(d, r):
            if r == "ok":
                pin = pin_row.get_text().strip()
                device_name = name_row.get_text().strip()
                u = user_row.get_text().strip()
                p = pass_row.get_text().strip()
                save = save_chk.get_active()

                if not pin: return
                
                # Update saved credentials if requested
                if save and u and p:
                    conf.set('sunshine_user', u)
                    conf.set('sunshine_password', p)
                
                auth = (u, p) if (u and p) else None
                success, msg = self.sunshine.send_pin(pin, name=device_name, auth=auth)
                
                if success:
                    self.show_toast(_("PIN sent successfully"))
                elif "Authentication Failed" in msg or "Falha de Autenticação" in msg or "401" in msg:
                    self.show_error_dialog(_("Authentication Failed"), _("Invalid username or password."))
                elif "307" in msg:
                    # Se retornar Redirect 307, significa que não tem usuário criado
                    self.prompt_create_user(pin)
                else:
                    self.show_error_dialog(_("PIN Error"), msg)
                
        dialog.connect("response", on_response)
        dialog.present()
        
    def prompt_create_user(self, pin_retry):
        dialog = Adw.MessageDialog(
            heading=_("User Not Found"), 
            body=_("No user has been created in Sunshine. It is necessary to configure a user through the browser.")
        )
        dialog.set_transient_for(self.get_root())
        dialog.add_response("cancel", _("Cancel"))
        dialog.add_response("open", _("Open Configuration"))
        dialog.set_response_appearance("open", Adw.ResponseAppearance.SUGGESTED)
        
        def on_resp(d, r):
            if r == "open":
                import webbrowser
                webbrowser.open("https://localhost:47990")
        
        dialog.connect("response", on_resp)
        dialog.present()
        
    def open_create_user_dialog(self, pin_retry):
        # This seems unused given the web prompt above, but keping for reference or alternative flow
        dialog = Adw.MessageDialog(
            heading=_("Create Sunshine User"), 
            body=_("Define a username and password for Sunshine.")
        )
        dialog.set_transient_for(self.get_root())
        
        grp = Adw.PreferencesGroup()
        user_row = Adw.EntryRow(title=_("New User"))
        user_row.set_text("admin")
        pass_row = Adw.PasswordEntryRow(title=_("New Password"))
        
        grp.add(user_row); grp.add(pass_row)
        dialog.set_extra_child(grp)
        
        dialog.add_response("cancel", _("Cancel"))
        dialog.add_response("save", _("Save and Continue"))
        dialog.set_response_appearance("save", Adw.ResponseAppearance.SUGGESTED)
        
        def on_create(d, r):
            if r == "save":
                user = user_row.get_text().strip()
                pwd = pass_row.get_text().strip()
                if not user or not pwd: return
                
                success, msg = self.sunshine.create_user(user, pwd)
                if success:
                    self.show_toast(_("User created!"))
                    # Save creds since we just created them
                    self._save_sunshine_creds(user, pwd)
                    
                    ok, p_msg = self.sunshine.send_pin(pin_retry, auth=(user, pwd))
                    if ok: self.show_toast(_("PIN sent successfully"))
                    else: self.show_error_dialog(_("Error sending PIN after creation"), p_msg)
                else:
                    self.show_error_dialog(_("Error creating user"), msg)
                    
        dialog.connect("response", on_create)
        dialog.present()
        
    def open_sunshine_auth_dialog(self, pin_to_retry: str, device_name: str = None):
        # Prefill with existing if available (for correction)
        creds = self._get_sunshine_creds()
        curr_user = creds[0] if creds else "admin"
        
        dialog = Adw.MessageDialog(
            heading=_("Sunshine Authentication"), 
            body=_("Sunshine requires login. Enter your credentials (default: admin / password created during installation).")
        )
        dialog.set_transient_for(self.get_root())
        
        grp = Adw.PreferencesGroup()
        user_row = Adw.EntryRow(title=_("Username"))
        user_row.set_text(curr_user)
        pass_row = Adw.PasswordEntryRow(title=_("Password"))
        
        grp.add(user_row); grp.add(pass_row)
        dialog.set_extra_child(grp)
        
        dialog.add_response("cancel", _("Cancel"))
        dialog.add_response("login", _("Confirm"))
        dialog.set_response_appearance("login", Adw.ResponseAppearance.SUGGESTED)
        
        def on_auth_resp(d, r):
            if r == "login":
                user = user_row.get_text().strip()
                pwd = pass_row.get_text().strip()
                if not user or not pwd: return
                
                # Tentar novamente com credenciais
                success, msg = self.sunshine.send_pin(pin_to_retry, name=device_name, auth=(user, pwd))
                if success: 
                    self.show_toast(_("PIN sent successfully"))
                    # Save credentials on success
                    self._save_sunshine_creds(user, pwd)
                else: 
                    self.show_error_dialog(_("Failed with credentials"), msg)
                
        dialog.connect("response", on_auth_resp)
        dialog.present()

    def create_summary_box(self):
        self.summary_box = Adw.PreferencesGroup()
        self.summary_box.set_title(_("Server Information"))
        self.summary_box.set_header_suffix(create_icon_widget('preferences-other-symbolic', size=24))
        self.summary_box.set_visible(False); self.field_widgets = {}
        for l, k, i, r in [('Host', 'hostname', 'computer-symbolic', True), ('IPv4', 'ipv4', 'network-wired-symbolic', False), ('IPv6', 'ipv6', 'network-wired-symbolic', False), ('IPv4 Global', 'ipv4_global', 'network-transmit-receive-symbolic', False), ('IPv6 Global', 'ipv6_global', 'network-transmit-receive-symbolic', False)]: self.create_masked_row(l, k, i, r)

    def on_audio_mode_changed(self, row, param):
        if getattr(self, 'loading_settings', False): return
        
        idx = row.get_selected()
        # Mode Guest (1), Automatic (0) or Guest+Host (3) means mixer should be visible
        show_mixer = idx in [0, 1, 3]
        self.audio_mixer_expander.set_visible(show_mixer)
        
        if self.is_hosting:
            self._run_audio_enforcer()
        self.save_host_settings()


    def load_audio_outputs(self):

        try:
            from utils.audio import AudioManager
            if not hasattr(self, 'audio_manager'):
                self.audio_manager = AudioManager()
            
            devices = self.audio_manager.get_passive_sinks()
            self.audio_devices = devices
            
            model = Gtk.StringList()
            if not devices:
                model.append(_("System Default"))
            else:
                for dev in devices:
                    model.append(dev.get('description', dev.get('name', 'Unknown')))
            
            self.audio_output_row.set_model(model)
            # Try to keep selection if possible, or use config
            h = self.config.get('host', {})
            self.audio_output_row.set_selected(h.get('audio_output_idx', 0))
            
        except Exception as e:
            print(f"Error loading audio: {e}")
            model = Gtk.StringList()
            model.append(_("Error loading audio"))
            self.audio_output_row.set_model(model)

    def on_audio_output_changed(self, row, param):
        if getattr(self, 'loading_settings', False): return
        
        idx = row.get_selected()
        if self.audio_devices and 0 <= idx < len(self.audio_devices):
            new_sink = self.audio_devices[idx]['name']
        else:
            new_sink = self.audio_manager.get_default_sink()
        
        # If hosting and audio active, need to reconfigure loopback
        if self.is_hosting and self.audio_mode_row.get_selected() in [0, 1, 3]:
            if hasattr(self, 'active_host_sink') and self.active_host_sink != new_sink:
                print(f"Changing host output in real-time to: {new_sink}")
                self.active_host_sink = new_sink
                # Restart audio streaming to change loopback destination
                self.audio_manager.enable_streaming_audio(new_sink)
                self.show_toast(_("Output changed to: {}").format(new_sink))
        
        self.save_host_settings()

    def on_configure_firewall_clicked(self, _widget):
        self.show_toast(_("Configuring firewall... (Password may be requested)"))
        
        try:
            # Locate the script relative to this file
            # src/ui/host_view.py -> src/ui -> src -> root -> scripts
            script_path = Path(__file__).parent.parent / 'scripts' / 'configure_firewall.sh'
            
            if not script_path.exists():
                script_path = Path("/usr/share/big-remote-play/scripts/configure_firewall.sh")
            
            if not script_path.exists():
                self.show_error_dialog(_("Error"), f"Script not found: {script_path}")
                return

            # Run with pkexec
            cmd = ['pkexec', str(script_path)]
            
            def on_done(ok, out):
                if ok: self.show_toast(_("Success: {}").format(out.strip()))
                else: self.show_error_dialog(_("Firewall Error"), out if out else _("Execution failed or cancelled."))
            
            def run():
                try:
                    res = subprocess.run(cmd, capture_output=True, text=True)
                    GLib.idle_add(on_done, res.returncode == 0, res.stdout + res.stderr)
                except Exception as e:
                    GLib.idle_add(on_done, False, str(e))
                    
            threading.Thread(target=run, daemon=True).start()
            
        except Exception as e:
            self.show_toast(_("Error executing script: {}").format(e))

    def create_masked_row(self, title, key, icon_name='text-x-generic-symbolic', default_revealed=False):
        row = Adw.ActionRow()
        row.set_title(title)
        row.add_prefix(create_icon_widget(icon_name, size=16))
        
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        box.set_valign(Gtk.Align.CENTER)
        
        value_lbl = Gtk.Label(label='••••••' if not default_revealed else '')
        value_lbl.set_margin_end(8)
        
        eye_btn = Gtk.Button()
        eye_btn.set_child(create_icon_widget('view-reveal-symbolic' if not default_revealed else 'view-conceal-symbolic', size=16))
        eye_btn.add_css_class('flat')
        copy_btn = Gtk.Button()
        copy_btn.set_child(create_icon_widget('edit-copy-symbolic', size=16))
        copy_btn.add_css_class('flat')
        
        box.append(value_lbl); box.append(eye_btn); box.append(copy_btn)
        row.add_suffix(box)
        self.summary_box.add(row)
        
        self.field_widgets[key] = {'label': value_lbl, 'real_value': '', 'revealed': default_revealed, 'btn_eye': eye_btn}
        eye_btn.connect('clicked', lambda b: self.toggle_field_visibility(key))
        copy_btn.connect('clicked', lambda b: self.copy_field_value(key))
        
    def toggle_field_visibility(self, key):
        field = self.field_widgets[key]
        field['revealed'] = not field['revealed']
        field['btn_eye'].set_child(create_icon_widget('view-conceal-symbolic' if field['revealed'] else 'view-reveal-symbolic', size=16))
        field['label'].set_text(field['real_value'] if field['revealed'] else '••••••')
            
    def copy_field_value(self, key):
        if val := self.field_widgets[key]['real_value']:
             self.get_root().get_clipboard().set(val); self.show_toast(_("Copied!"))

    def toggle_hosting(self, button):
        self.show_toast(_("Clicked Start Server..."))
        self.start_button.set_sensitive(False)
        self.start_btn_spinner.set_visible(True)
        self.start_btn_spinner.start()
        
        # Defer action slightly to allow UI to paint
        GLib.timeout_add(100, self._perform_toggle_hosting)

    def _perform_toggle_hosting(self):
        if self.is_hosting: self.stop_hosting()
        else: self.start_hosting()
        return False
            
    def sync_ui_state(self):
        if self.is_hosting:
            self.header.set_description(_('Server Active - Guests can now connect'))
            self.perf_monitor.set_connection_status("Sunshine", _("Active - Waiting for Connections"), True)
            self.perf_monitor.start_monitoring()
            
            # Button State: Hosting -> Stop
            self.start_btn_label.set_label(_('Stop'))
            self.start_button.remove_css_class('suggested-action')
            self.start_button.add_css_class('destructive-action')
            self.start_btn_spinner.set_visible(False)
            self.start_btn_spinner.stop()
            
            self.header.set_visible(True)
            self.configure_button.set_sensitive(True)
            self.configure_button.add_css_class('suggested-action')
            for r in [self.game_mode_row, self.hardware_expander, self.streaming_expander, self.advanced_expander]: r.set_sensitive(False)
            
            if hasattr(self, 'pin_button'): self.pin_button.set_visible(True)
            if hasattr(self, 'summary_box'):
                self.summary_box.set_visible(True)
                self.populate_summary_fields()
            
            # Enable PIN tab when hosting
            if hasattr(self, 'pin_page'):
                self.pin_page.set_sensitive(True)
                self.pin_stack_page.set_icon_name("dialog-password-symbolic")
        else:
            self.header.set_description(_('Configure and share your game for friends to connect'))
            self.perf_monitor.set_connection_status("Sunshine", _("Inactive"), False)
            self.perf_monitor.stop_monitoring()
            self.header.set_visible(True)
            
            if hasattr(self, 'pin_button'): self.pin_button.set_visible(False)
            if hasattr(self, 'summary_box'):
                self.summary_box.set_visible(True)
                self.populate_summary_fields()
            
            # Disable PIN tab when not hosting
            if hasattr(self, 'pin_page'):
                self.pin_page.set_sensitive(False)
                self.pin_stack_page.set_icon_name("changes-prevent-symbolic")
                
                # Switch to info tab if we were on PIN tab and it's now blocked
                if self.view_stack.get_visible_child_name() == "pin_code":
                    self.view_stack.set_visible_child_name("info")
            
            # Button State: Stopped -> Start
            self.start_btn_label.set_label(_('Start Server'))
            self.start_button.remove_css_class('destructive-action')
            self.start_button.add_css_class('suggested-action')
            self.start_btn_spinner.set_visible(False)
            self.start_btn_spinner.stop()
            
            self.configure_button.set_sensitive(False)
            self.configure_button.remove_css_class('suggested-action')
            for r in [self.game_mode_row, self.hardware_expander, self.streaming_expander, self.advanced_expander]: r.set_sensitive(True)

    def populate_summary_fields(self):
        import socket, threading
        from utils.network import NetworkDiscovery
        self.update_field('hostname', socket.gethostname())
        if self.pin_code: self.update_field('pin', self.pin_code)
        ipv4, ipv6 = self.get_ip_addresses()
        self.update_field('ipv4', ipv4); self.update_field('ipv6', ipv6)
        
        def fetch_globals():
            net = NetworkDiscovery()
            g_ipv4 = net.get_global_ipv4(); g_ipv6 = net.get_global_ipv6()
            
            # Wrap IPv6 in brackets for compatibility
            if g_ipv6 and g_ipv6 != "None" and ':' in g_ipv6 and not g_ipv6.startswith('['):
                g_ipv6 = f"[{g_ipv6}]"
                
            GLib.idle_add(self.update_field, 'ipv4_global', g_ipv4)
            GLib.idle_add(self.update_field, 'ipv6_global', g_ipv6)
        threading.Thread(target=fetch_globals, daemon=True).start()
        
    def update_field(self, key, value):
        if key in self.field_widgets:
            self.field_widgets[key]['real_value'] = value
            if self.field_widgets[key]['revealed']: self.field_widgets[key]['label'].set_text(value)

    def start_audio_mixer_refresh(self):
        self.stop_audio_mixer_refresh()
        self.private_audio_apps = set() # Track names of private apps (unchecked in UI)
        self.mixer_source_id = GLib.timeout_add(2000, self._refresh_audio_mixer_ui)
        self.enforcer_source_id = GLib.timeout_add(1000, self._run_audio_enforcer)
        self._refresh_audio_mixer_ui()
        return True

    def stop_audio_mixer_refresh(self):
        if hasattr(self, 'mixer_source_id'):
            GLib.source_remove(self.mixer_source_id)
            del self.mixer_source_id
        if hasattr(self, 'enforcer_source_id'):
            GLib.source_remove(self.enforcer_source_id)
            del self.enforcer_source_id

    def _run_audio_enforcer(self):
        if not self.is_hosting: return True
        if not hasattr(self, 'active_host_sink') or not self.active_host_sink: return True
        
        shared_sink = "SunshineGameSink"
        private_sink = self.active_host_sink
        
        # Determine behavior based on Audio Mode Selection
        # 0: Automatic, 1: Guest, 2: Host, 3: Guest + Host
        mode_idx = self.audio_mode_row.get_selected()
        
        streaming_enabled = mode_idx in [0, 1, 3]
        
        # Audio Monitoring logic
        should_monitor = False
        if mode_idx == 3: # Guest + Host
            should_monitor = True
        elif mode_idx == 2: # Host Only
            should_monitor = True # Doesn't matter much as everything moves to private_sink
            streaming_enabled = False
        elif mode_idx == 1: # Guest Only
            should_monitor = False
        elif mode_idx == 0: # Automatic
            # Check for localhost guest
            has_localhost = False
            if hasattr(self, 'perf_monitor'):
                for guest in getattr(self.perf_monitor, '_known_devices', {}).values():
                     if guest.get('status') == 'active' or (time.time() - guest.get('last_seen', 0) < 5):
                          ip = guest.get('ip', '')
                          if ip in ['127.0.0.1', '::1', 'localhost']:
                               has_localhost = True; break
            should_monitor = not has_localhost

        if hasattr(self, 'audio_manager'):
            try:
                # Update loopback
                if not hasattr(self, '_last_monitor_state') or self._last_monitor_state != should_monitor:
                    self.audio_manager.set_host_monitoring(private_sink, should_monitor)
                    self._last_monitor_state = should_monitor

                # Default sink hijack fix
                current_default = self.audio_manager.get_default_sink()
                if current_default and current_default != private_sink:
                    if "sunshine" in current_default.lower() and "stereo" in current_default.lower():
                        self.audio_manager.set_default_sink(private_sink)

                apps = self.audio_manager.get_apps()
                for app in apps:
                    app_id, name = app['id'], app.get('name', '')
                    if 'sunshine' in name.lower() or 'loopback' in name.lower() or 'moonlight' in name.lower(): continue
                    
                    target = private_sink if (not streaming_enabled or name in self.private_audio_apps) else shared_sink
                    if app.get('sink_name', '') != target:
                        print(f"Enforcer: Moving {name} -> {target}")
                        self.audio_manager.move_app(app_id, target)
            except Exception as e:
                print(f"Enforcer Error: {e}")
        return True



    def _refresh_audio_mixer_ui(self):
        if not self.audio_mixer_expander.get_visible(): return True
        if not hasattr(self, 'audio_manager'): return True
        
        apps = self.audio_manager.get_apps()
        seen_ids = set()
        
        if not hasattr(self, 'mixer_rows'): self.mixer_rows = {}
        
        for app in apps:
            app_id = app['id']
            app_name = app.get('name', 'App')
            seen_ids.add(app_id)
            
            # Default state: Active (Shared) unless explicitly set to Private
            is_shared = (app_name not in self.private_audio_apps)
            
            if app_id in self.mixer_rows:
                row = self.mixer_rows[app_id]
                # Avoid signal loop
                if row.get_active() != is_shared:
                    row.disconnect_by_func(self._on_app_toggled)
                    row.set_active(is_shared)
                    row.connect('notify::active', self._on_app_toggled, app_name)
                
                row.set_subtitle(_("Host + Guest") if is_shared else _("Host Only"))
            else:
                row = Adw.SwitchRow()
                row.set_title(app_name)
                row.set_subtitle(_("Host + Guest") if is_shared else _("Host Only"))
                if app.get('icon'): row.set_icon_name(app['icon'])
                row.set_active(is_shared)
                row.connect('notify::active', self._on_app_toggled, app_name)
                self.audio_mixer_expander.add_row(row)
                self.mixer_rows[app_id] = row
                
        # Cleanup
        to_remove = [aid for aid in self.mixer_rows if aid not in seen_ids]
        for aid in to_remove:
            self.audio_mixer_expander.remove(self.mixer_rows[aid])
            del self.mixer_rows[aid]
            
        return True

    def _on_app_toggled(self, row, param, app_name):
        is_shared = row.get_active()
        if is_shared:
            if app_name in self.private_audio_apps:
                self.private_audio_apps.remove(app_name)
        else:
            self.private_audio_apps.add(app_name)
            
        row.set_subtitle(_("Host + Guest") if is_shared else _("Host Only"))
        self._run_audio_enforcer()
             
    def start_hosting(self, b=None):
        self.loading_bar.set_visible(True); self.loading_bar.pulse()
            
        try:
            if self.sunshine.is_running():
                self.sunshine.stop()
                import time; time.sleep(1)
            
            self.pin_code = ''.join(random.choices(string.digits, k=6))
            from utils.network import NetworkDiscovery
            self.stop_pin_listener = NetworkDiscovery().start_pin_listener(self.pin_code, socket.gethostname())
            
            mode_idx = self.game_mode_row.get_selected()
            
            # Always use Desktop mode in apps.json - we launch games directly
            self.sunshine.update_apps([{"name": "Desktop", "output": "", "cmd": "", "detached": ["sleep infinity"]}])
            
            # Store game launch info to execute AFTER Sunshine starts
            self._game_launch_info = None
            self._game_processes = []
            
            if mode_idx == 1:  # Steam
                idx = self.game_list_row.get_selected()
                if idx != Gtk.INVALID_LIST_POSITION:
                    games = self.detected_games.get('Steam', [])
                    if 0 <= idx < len(games):
                        game = games[idx]
                        app_id = game.get('id', '')
                        self._game_launch_info = {
                            'type': 'steam',
                            'app_id': app_id,
                            'name': game['name']
                        }
            elif mode_idx == 2:  # Lutris
                idx = self.game_list_row.get_selected()
                if idx != Gtk.INVALID_LIST_POSITION:
                    games = self.detected_games.get('Lutris', [])
                    if 0 <= idx < len(games):
                        game = games[idx]
                        self._game_launch_info = {
                            'type': 'lutris',
                            'cmd': game['cmd'],
                            'name': game['name']
                        }
            elif mode_idx == 3:  # Custom App
                name = self.custom_name_entry.get_text(); cmd = self.custom_cmd_entry.get_text()
                if name and cmd:
                    self._game_launch_info = {
                        'type': 'custom',
                        'cmd': cmd,
                        'name': name
                    }
                     
            # Determine FPS
            fps_idx = self.fps_row.get_selected()
            fps_map = {0: 30, 1: 60, 2: 120, 3: 144, 4: 60} # Custom defaults to 60
            fps = fps_map.get(fps_idx, 60)
            
            # Determine Bitrate (Kbps)
            # Sunshine uses min_bitrate in config, but we can pass 'bitrate' (CBR target) here if needed.
            # However, Sunshine v0.20+ generally prefers min_bitrate in config for VBR floor.
            # If bandwidth_row > 0, set bitrate. Else default.
            bw_mbps = self.bandwidth_row.get_value()
            bitrate = int(bw_mbps * 1000) if bw_mbps > 0 else 20000 # Default 20Mbps if unlim
            
            selected_gpu_info = self.available_gpus[self.gpu_row.get_selected()]
            sunshine_config = {
                'sunshine_name': socket.gethostname(),
                'encoder': selected_gpu_info['encoder'], 'bitrate': bitrate, 'fps': fps,
                'videocodec': 'h264', 'gamepad': 'x360', 'min_threads': 4, 
                'min_log_level': 2, # Info level to see connections
                'channels': 2, # Force Stereo
                'pkey': 'pkey.pem', 'cert': 'cert.pem', 
                'upnp': 'enabled' if self.upnp_row.get_active() else 'disabled',
                'address_family': 'both' if self.ipv6_row.get_active() else 'ipv4',
                'origin_web_ui_allowed': 'wan' if self.webui_anyone_row.get_active() else 'lan',
                'webserver': '0.0.0.0',
                'enable_api_endpoints': 'true',
                'port': '47989'
            }
            

            # Audio Configuration
            # Audio Configuration - ALWAYS setup PulseAudio infrastructure
            # This allows toggling streaming on/off without restarting server or destroying sinks
            sunshine_config['audio'] = 'pulse'
            
            # Identify Host Sink
            host_sink_idx = self.audio_output_row.get_selected()
            if hasattr(self, 'audio_devices') and 0 <= host_sink_idx < len(self.audio_devices):
                host_sink = self.audio_devices[host_sink_idx]['name']
            else:
                host_sink = self.audio_manager.get_default_sink()
            
            # PROTECT AGAINST SELF-LOOP IF DEFAULT SINK IS STILL VIRTUAL
            if host_sink == "SunshineGameSink" or self.audio_manager.is_virtual(host_sink):
                print(f"WARNING: Host sink '{host_sink}' is virtual. finding fallback hardware sink.")
                hw_sinks = self.audio_manager.get_passive_sinks()
                if hw_sinks:
                    host_sink = hw_sinks[0]['name'] # or 'id' depending on get_passive_sinks return
                    # usually get_passive_sinks returns dict with 'name' (id) and description
                    # wait, check utils/audio.py get_passive_sinks returns dict with 'name' -> PA Name
                    
            # Store it for the enforcer
            self.active_host_sink = host_sink

            # Enable Host+Guest Streaming (Create Sink)
            if self.audio_manager:
                # Determine loopback based on Mode
                mode_idx = self.audio_mode_row.get_selected()
                # If mode is Guest (1), guest_only is True
                # If mode is Guest+Host (3), guest_only is False
                # If mode is Automatic (0), we start with guest_only=False (will be auto-muted by enforcer if needed)
                guest_only = (mode_idx == 1)
                
                if self.audio_manager.enable_streaming_audio(host_sink, guest_only=guest_only):
                    sunshine_config['audio_sink'] = "SunshineGameSink"
                    
                    self._last_monitor_state = not guest_only
                    self.start_audio_mixer_refresh()

                    GLib.timeout_add(500, lambda: (self.audio_manager.set_default_sink(host_sink), self.show_toast(_("Host output restored: {}").format(host_sink)))[1])
                else:


                     print("Failed to enable streaming sinks, falling back to default")
                     self.show_toast(_("Failed to create Virtual Audio"))
                     # Fallback to none if creation failed
                     sunshine_config['audio'] = 'none' 
                     self.audio_manager.disable_streaming_audio(None)

            platforms = ['auto', 'wayland', 'x11', 'kms']
            platform = platforms[self.platform_row.get_selected()]
            if platform == 'auto':
                session = os.environ.get('XDG_SESSION_TYPE', '').lower()
                platform = 'wayland' if session == 'wayland' else 'x11'
            sunshine_config['platform'] = platform


            # Set output_name if a specific monitor is selected, others set to None to remove from config
            sunshine_config['output_name'] = None
            monitor_idx = self.monitor_row.get_selected()
            if 0 < monitor_idx < len(self.available_monitors):
                mon_name = self.available_monitors[monitor_idx][1]
                if mon_name != 'auto':
                    sunshine_config['output_name'] = mon_name
            
            # Set adapter_name only if specific adapter chosen
            sunshine_config['adapter_name'] = None
            if selected_gpu_info['encoder'] == 'vaapi' and selected_gpu_info['adapter'] != 'auto':
                sunshine_config['adapter_name'] = selected_gpu_info['adapter']
            
            if platform == 'wayland':
                sunshine_config['wayland.display'] = os.environ.get('WAYLAND_DISPLAY', 'wayland-0')
            if platform == 'x11' and (not sunshine_config['output_name']):
                sunshine_config['output_name'] = ':0'
            
            self.sunshine.configure(sunshine_config)
            success, msg = self.sunshine.start()
            
            if success:
                self.is_hosting = True
                self.sync_ui_state()
                self.show_toast(_('Server started'))
                
                # DIRECT LAUNCH: Open game/platform immediately
                self._launch_game_direct()
            else:
                self.is_hosting = False
                self.sync_ui_state()
                self.show_start_error_dialog(msg)
            
        except Exception as e:
            self.show_error_dialog(_('Error'), str(e))
            self.is_hosting = False
            self.sync_ui_state() # Revert state
        finally: 
            self.loading_bar.set_visible(False)
            self.start_button.set_sensitive(True) # Reabilitar botão
            self.start_btn_spinner.stop()
            self.start_btn_spinner.set_visible(False)
        
    def _launch_game_direct(self):
        """Directly launch game/platform via subprocess - radical approach"""
        info = getattr(self, '_game_launch_info', None)
        if not info:
            print("Game Mode: Desktop (no game to launch)")
            return
        
        if not hasattr(self, '_game_processes'):
            self._game_processes = []
            
        env = os.environ.copy()
        
        try:
            if info['type'] == 'steam':
                app_id = info['app_id']
                game_name = info['name']
                print(f"DIRECT LAUNCH: Steam Big Picture + {game_name} (ID: {app_id})")
                
                # 1. Open Steam Big Picture Mode
                p1 = subprocess.Popen(
                    ['steam', 'steam://open/bigpicture'],
                    env=env, start_new_session=True,
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                )
                self._game_processes.append(p1)
                self.show_toast(_("Opening Steam Big Picture..."))
                
                # 2. Launch the game after a delay (give Big Picture time to open)
                def _delayed_game_launch():
                    import time
                    time.sleep(4)
                    try:
                        p2 = subprocess.Popen(
                            ['steam', f'steam://rungameid/{app_id}'],
                            env=env, start_new_session=True,
                            stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                        )
                        self._game_processes.append(p2)
                        GLib.idle_add(self.show_toast, _("Launching {}...").format(game_name))
                        print(f"DIRECT LAUNCH: Game {game_name} launched (PID: {p2.pid})")
                    except Exception as e:
                        print(f"Error launching game: {e}")
                        GLib.idle_add(self.show_toast, _("Error launching game: {}").format(e))
                
                threading.Thread(target=_delayed_game_launch, daemon=True).start()
                    
            elif info['type'] == 'lutris':
                cmd = info['cmd']
                game_name = info['name']
                print(f"DIRECT LAUNCH: Lutris - {game_name} ({cmd})")
                
                p = subprocess.Popen(
                    cmd.split(),
                    env=env, start_new_session=True,
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                )
                self._game_processes.append(p)
                self.show_toast(_("Launching {}...").format(game_name))
                    
            elif info['type'] == 'custom':
                cmd = info['cmd']
                game_name = info['name']
                print(f"DIRECT LAUNCH: Custom - {game_name} ({cmd})")
                
                p = subprocess.Popen(
                    cmd, shell=True,
                    env=env, start_new_session=True,
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                )
                self._game_processes.append(p)
                self.show_toast(_("Launching {}...").format(game_name))
                    
        except Exception as e:
            print(f"Error in _launch_game_direct: {e}")
            self.show_toast(_("Error launching game: {}").format(e))
    
    def _stop_game_direct(self):
        """Kill any directly launched game processes"""
        info = getattr(self, '_game_launch_info', None)
        
        # Close Steam Big Picture if we opened it
        if info and info.get('type') == 'steam':
            try:
                subprocess.Popen(
                    ['steam', 'steam://close/bigpicture'],
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
                )
                print("Closing Steam Big Picture")
            except: pass
        
        # Kill tracked processes
        for p in getattr(self, '_game_processes', []):
            try:
                if p.poll() is None:  # Still running
                    import signal
                    os.killpg(os.getpgid(p.pid), signal.SIGTERM)
            except: pass
        
        self._game_processes = []
        self._game_launch_info = None

    def stop_hosting(self, b=None):
        self.show_toast(_("Stopping server..."))
        self.loading_bar.set_visible(True); self.loading_bar.pulse()
        
        # Stop directly launched games FIRST
        self._stop_game_direct()
        # Update mixer visibility based on mode
        self.audio_mixer_expander.set_visible(self.audio_mode_row.get_selected() in [0, 1, 3])
        self.stop_audio_mixer_refresh()
        
        if hasattr(self, 'stop_pin_listener') and self.stop_pin_listener:
            try: self.stop_pin_listener()
            except: pass
            self.stop_pin_listener = None
            
        # Restore audio configuration
        if hasattr(self, 'audio_manager') and hasattr(self, 'active_host_sink') and self.active_host_sink:
            try:
                self.audio_manager.disable_streaming_audio(self.active_host_sink)
            except Exception as e:
                print(f"Error restoring audio: {e}")
            
        try:
            self.sunshine.stop()
        except Exception as e:
            print(f"Error stopping Sunshine: {e}")
        
        # Hard kill fallback
        subprocess.run(['pkill', '-9', 'sunshine'], stderr=subprocess.DEVNULL)
            
        self.is_hosting = False
        self.sync_ui_state()
        self.loading_bar.set_visible(False)
        self.start_button.set_sensitive(True) 
        self.start_btn_spinner.stop()
        self.start_btn_spinner.set_visible(False)
        self.show_toast(_('Server stopped'))
        
    def update_status_info(self):
        sunshine_running = self.check_process_running('sunshine')
        
        # If it was supposed to be hosting but sunshine is not running
        if self.is_hosting and not sunshine_running:
             self.is_hosting = False
             self.sync_ui_state()
             self.show_toast(_("Sunshine stopped unexpectedly"))
             return True

        if not self.is_hosting: return True
        
        if hasattr(self, 'sunshine_val'):
            self.sunshine_val.set_markup('<span color="#2ec27e">On-line</span>' if sunshine_running else '<span color="#e01b24">Parado</span>')
        ipv4, ipv6 = self.get_ip_addresses()
        self.update_field('ipv4', ipv4); self.update_field('ipv6', ipv6)
        return True
        
    def check_process_running(self, process_name):
        try:
            subprocess.check_output(["pgrep", "-x", process_name])
            return True
        except: return False
            
    def get_ip_addresses(self):
        ipv4 = ipv6 = "None"
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
                s.connect(("1.1.1.1", 80)); ipv4 = s.getsockname()[0]
        except: pass
        try:
            res = subprocess.run(['ip', '-j', 'addr'], capture_output=True, text=True)
            if res.returncode == 0:
                for iface in json.loads(res.stdout):
                    name = iface['ifname']
                    # Pular interfaces de loopback, desligadas ou virtuais conhecidas
                    if name == 'lo' or 'UP' not in iface['flags']: continue
                    if any(x in name for x in ['docker', 'veth', 'virbr', 'vboxnet', 'tailscale', 'zerotier', 'br-']): continue
                    for addr in iface.get('addr_info', []):
                        if addr['family'] == 'inet':
                            if ipv4 == "None": ipv4 = addr['local']
                        elif addr['family'] == 'inet6':
                            # Prioritize global but accept link-local
                            if addr.get('scope') == 'global':
                                ipv6 = addr['local']
                                break # Found global, stop searching for this interface
                            elif ipv6 == "None":
                                # Fallback to link-local with scope ID
                                ipv6 = f"{addr['local']}%{name}"
        except: pass

        
        # No longer wrapping in brackets as per user feedback
            
        return ipv4, ipv6
        
    def show_start_error_dialog(self, message):
        if not message: message = _("Check logs for details.")
        
        body = _("Sunshine failed to start.\n\nError: {}\n\nIf this is a dependency issue (missing libraries), try the 'Fix Dependencies' button.").format(message)
        
        # Use simple MessageDialog constructor for custom response handling if needed, 
        # or simplified new() if we connect signal later.
        dialog = Adw.MessageDialog(heading=_("Server Failed to Start"), body=body)
        dialog.set_transient_for(self.get_root())
        dialog.add_response("cancel", _("Close"))
        dialog.add_response("logs", _("View Logs"))
        dialog.add_response("fix", _("Fix Dependencies"))
        
        dialog.set_response_appearance("fix", Adw.ResponseAppearance.SUGGESTED)
        
        def on_response(d, r):
            if r == "logs":
                try:
                    log_path = self.sunshine.config_dir / 'sunshine.log'
                    subprocess.Popen(['xdg-open', str(log_path)])
                except: pass
            elif r == "fix":
                app = self.get_root().get_application()
                if hasattr(app, 'show_preferences'):
                    app.show_preferences(tab='sunshine')
                    
        dialog.connect("response", on_response)
        dialog.present()

    def show_error_dialog(self, title, message):
        dialog = Adw.MessageDialog.new(self.get_root(), title, message)
        dialog.add_response('ok', 'OK')
        dialog.present()
    
    def show_toast(self, message):
        window = self.get_root()
        if hasattr(window, 'show_toast'): window.show_toast(message)
        else: print(f"Toast: {message}")
        
    def open_sunshine_config(self, button):
        subprocess.Popen(['xdg-open', 'https://localhost:47990'])

    def on_game_mode_changed(self, row, param):
        idx = row.get_selected()
        self.platform_games_expander.set_visible(idx in [1, 2])
        self.platform_games_expander.set_expanded(idx in [1, 2])
        self.custom_app_expander.set_visible(idx == 3)
        self.custom_app_expander.set_expanded(idx == 3)
        if idx in [1, 2]:
            plat = {1: 'Steam', 2: 'Lutris'}[idx]
            self.platform_games_expander.set_title(f"{plat} Games")
            self.populate_game_list(idx)
            
    def populate_game_list(self, mode_idx):
        plat = {1: 'Steam', 2: 'Lutris'}.get(mode_idx)
        if not plat: return
        if not self.detected_games[plat]:
             if plat == 'Steam': self.detected_games['Steam'] = self.game_detector.detect_steam()
             elif plat == 'Lutris': self.detected_games['Lutris'] = self.game_detector.detect_lutris()
        games = self.detected_games[plat]
        new_model = Gtk.StringList()
        if not games: new_model.append(f"No games found on {plat}")
        else:
            for game in games: new_model.append(game['name'])
        self.game_list_row.set_model(new_model)

    def save_host_settings(self, *args):
        if getattr(self, 'loading_settings', False): return
        h = self.config.get('host', {})
        h.update({
            'mode_idx': self.game_mode_row.get_selected(),
            'game_list_idx': self.game_list_row.get_selected(),
            'custom_name': self.custom_name_entry.get_text(),
            'custom_cmd': self.custom_cmd_entry.get_text(),
            
            # New Separate Settings
            'resolution_idx': self.resolution_row.get_selected(),
            'fps_idx': self.fps_row.get_selected(),
            'bandwidth_mbps': self.bandwidth_row.get_value(),
            'monitor_idx': self.monitor_row.get_selected(),
            'gpu_idx': self.gpu_row.get_selected(),
            'platform_idx': self.platform_row.get_selected(),
            'audio_mode': self.audio_mode_row.get_selected(),
            'audio_output_idx': self.audio_output_row.get_selected(),
            'upnp': self.upnp_row.get_active(),

            'ipv6': self.ipv6_row.get_active(),
            'webui_anyone': self.webui_anyone_row.get_active(),
            # New settings
            'efficient_codecs': self.codecs_row.get_active(),
            'optimization_mode': self.optimization_row.get_selected(),
            'wifi_mode': self.wifi_row.get_active()
        })

        self.config.set('host', h)
        
        # Update monitor target FPS live
        fps_idx = self.fps_row.get_selected()
        fps_val = {0: 30, 1: 60, 2: 120, 3: 144, 4: 60}.get(fps_idx, 60.0)
        self.perf_monitor.set_target_fps(fps_val)
        
        # Update monitor target Bandwidth live
        self.perf_monitor.set_target_bandwidth(self.bandwidth_row.get_value())
        
        # Sync to Sunshine Config
        try:
            from ui.sunshine_preferences import SunshineConfigManager
            scm = SunshineConfigManager()
            
            # Map Host Settings -> Sunshine Settings
            scm.set('upnp', 'enabled' if self.upnp_row.get_active() else 'disabled')
            scm.set('address_family', 'both' if self.ipv6_row.get_active() else 'ipv4')
            scm.set('origin_web_ui_allowed', 'wan' if self.webui_anyone_row.get_active() else 'lan')
            streaming_active = self.audio_mode_row.get_selected() in [0, 1, 3]
            scm.set('stream_audio', 'true' if streaming_active else 'false')
            
            # Map Codecs
            # If enabled -> advertised(1). If disabled -> disabled(0)
            codec_val = '1' if self.codecs_row.get_active() else '0'
            scm.set('hevc_mode', codec_val)
            scm.set('av1_mode', codec_val)
            
            # Map Wi-Fi Mode (FEC)
            # Enabled -> 20%. Disabled -> 5%
            scm.set('fec_percentage', '20' if self.wifi_row.get_active() else '5')
            
            # Map Optimization Mode
            # 0=Low Latency, 1=Balanced, 2=High Quality
            opt_idx = self.optimization_row.get_selected()
            if opt_idx == 0: # Low Latency
                scm.set('nvenc_preset', '1') # P1
                scm.set('amd_quality', 'speed')
                scm.set('sw_preset', 'ultrafast')
                scm.set('nvenc_twopass', 'disabled')
            elif opt_idx == 2: # High Quality
                scm.set('nvenc_preset', '7') # P7
                scm.set('amd_quality', 'quality')
                scm.set('sw_preset', 'medium')
                scm.set('nvenc_twopass', 'quarter_res')
            else: # Balanced
                scm.set('nvenc_preset', '4') # P4
                scm.set('amd_quality', 'balanced')
                scm.set('sw_preset', 'veryfast')
                scm.set('nvenc_twopass', 'disabled') # Or quarter_res depending on preference
            
            # Map Bandwidth to min_bitrate
            # 0 = Unlimited (default 0 or very high)
            bw = int(self.bandwidth_row.get_value() * 1000) # Mbps -> Kbps
            scm.set('min_bitrate', str(bw) if bw > 0 else '0')
                
        except Exception as e:
            print(f"Error syncing to Sunshine config: {e}")

    def load_settings(self):
        self.loading_settings = True
        try:
            # Sync from Sunshine Config first
            try:
                from ui.sunshine_preferences import SunshineConfigManager
                scm = SunshineConfigManager()
                
                # Update Host Config based on Sunshine Config (Source of Truth for these fields)
                h = self.config.get('host', {})
                h['upnp'] = scm.get('upnp', 'enabled') == 'enabled'
                h['ipv6'] = scm.get('address_family', 'both') == 'both'
                h['webui_anyone'] = scm.get('origin_web_ui_allowed', 'lan') == 'wan'
                h['audio'] = scm.get('stream_audio', 'true').lower() == 'true'
                
                # Reverse Map Codecs
                # If hevc_mode >= 1 OR av1_mode >= 1 -> Enabled
                hevc = scm.get('hevc_mode', '0')
                av1 = scm.get('av1_mode', '0')
                h['efficient_codecs'] = (hevc != '0' or av1 != '0')
                
                # Reverse Map Wi-Fi (FEC)
                # If FEC >= 15 -> Enabled
                fec = int(scm.get('fec_percentage', '10'))
                h['wifi_mode'] = (fec >= 15)
                
                # Reverse Map Optimization
                # Heuristic based on nvenc_preset
                nv_preset = scm.get('nvenc_preset', '4')
                if nv_preset in ['1', '2']: h['optimization_mode'] = 0 # Low Latency
                elif nv_preset in ['5', '6', '7']: h['optimization_mode'] = 2 # High Quality
                else: h['optimization_mode'] = 1 # Balanced
                
                # Reverse Map Bandwidth
                bw_kbps = int(scm.get('min_bitrate', '0'))
                h['bandwidth_mbps'] = bw_kbps / 1000.0
                
                self.config.set('host', h)
            except Exception as e:
                print(f"Error syncing from Sunshine config: {e}") 


            h = self.config.get('host', {})
            if not h: return
            self.game_mode_row.set_selected(h.get('mode_idx', 0))
            # Restore game list selection after populating
            mode_idx = h.get('mode_idx', 0)
            if mode_idx in [1, 2]:
                self.populate_game_list(mode_idx)
                game_list_idx = h.get('game_list_idx', 0)
                if game_list_idx is not None:
                    self.game_list_row.set_selected(game_list_idx)
            self.custom_name_entry.set_text(h.get('custom_name', ''))
            self.custom_cmd_entry.set_text(h.get('custom_cmd', ''))
            
            # New Separate Settings
            self.resolution_row.set_selected(h.get('resolution_idx', 1)) # Default 1080p
            fps_idx = h.get('fps_idx', 1)
            self.fps_row.set_selected(fps_idx) 
            
            # Update monitor target FPS
            fps_val = {0: 30, 1: 60, 2: 120, 3: 144, 4: 60}.get(fps_idx, 60.0)
            self.perf_monitor.set_target_fps(fps_val)
            
            bw_val = h.get('bandwidth_mbps', 0)
            self.bandwidth_row.set_value(bw_val) 
            self.perf_monitor.set_target_bandwidth(bw_val)
            
            self.monitor_row.set_selected(h.get('monitor_idx', 0))
            self.gpu_row.set_selected(h.get('gpu_idx', 0))
            self.platform_row.set_selected(h.get('platform_idx', 0))
            
            # Audio Mode
            audio_mode = h.get('audio_mode', 0)
            self.audio_mode_row.set_selected(audio_mode)
            show_mixer = audio_mode in [0, 1, 3]
            self.audio_mixer_expander.set_visible(show_mixer)


            
            self.audio_output_row.set_selected(h.get('audio_output_idx', 0))
            
            self.upnp_row.set_active(h.get('upnp', True))
            self.ipv6_row.set_active(h.get('ipv6', True))
            self.webui_anyone_row.set_active(h.get('webui_anyone', False))
            
            # New settings
            self.codecs_row.set_active(h.get('efficient_codecs', True))
            self.optimization_row.set_selected(h.get('optimization_mode', 1))
            self.wifi_row.set_active(h.get('wifi_mode', False))
        finally:
            self.loading_settings = False

    def connect_settings_signals(self):
        for r in [self.upnp_row, self.ipv6_row, self.webui_anyone_row, self.codecs_row, self.wifi_row]:
            r.connect('notify::active', self.save_host_settings)

        for r in [self.audio_mode_row, self.game_mode_row, self.game_list_row, self.monitor_row, self.gpu_row, self.platform_row, self.audio_output_row, self.optimization_row, self.resolution_row, self.fps_row]:
            r.connect('notify::selected', self.save_host_settings)


        for r in [self.bandwidth_row]:
            r.connect('notify::value', self.save_host_settings)
        for r in [self.custom_name_entry, self.custom_cmd_entry]:
            r.connect('notify::text', self.save_host_settings)

    def on_reset_clicked(self, button):
        diag = Adw.MessageDialog(heading=_('Restore Defaults'), body=_('Do you want to restore default settings?'))
        diag.add_response('cancel', _('Cancel')); diag.add_response('reset', _('Restore'))
        diag.set_response_appearance('reset', Adw.ResponseAppearance.DESTRUCTIVE)
        def on_resp(d, r):
            if r == 'reset': self.reset_to_defaults()
        diag.connect('response', on_resp); diag.present()

    def reset_to_defaults(self):
        self.config.set('host', self.config.default_config()['host'])
        self.load_settings(); self.show_toast(_("Settings Restored"))

    def cleanup(self):
        if hasattr(self, 'perf_monitor'): self.perf_monitor.stop_monitoring()
        if hasattr(self, 'stop_pin_listener'): self.stop_pin_listener()
        
        # Only cleanup audio if we are NOT hosting, because Sunshine depends on these sinks.
        # If we are hosting, the user expects the stream to continue working.
        # This also avoids the feedback loop (microfonia) when the app is closed while Moonlight/Sunshine are active.
        if not self.is_hosting:
            if hasattr(self, 'audio_manager'): self.audio_manager.cleanup()

