# app_shell.py
# Versão: 5.5 (Build com Reset de Hardware via CDLL)
# Data: 18/08/2025 (São Paulo, Brasil)
# Descrição: Versão final com reset de hardware usando ctypes.CDLL para compatibilidade.

import tkinter as tk
from tkinter import ttk, messagebox
import os
import sys
import traceback
import time
import threading
import ctypes
import json

try:
    from PIL import Image, ImageTk
except ImportError:
    try:
        from .i18n import get_translator, t
        get_translator()
        messagebox.showerror(t('app_shell.error_missing_library'), t('app_shell.error_missing_library_msg'))
    except Exception:
        try:
            from .i18n import TRANSLATIONS
            bundle = TRANSLATIONS.get('pt', TRANSLATIONS.get('en', {}))
            messagebox.showerror(
                bundle.get('app_shell.error_missing_library', 'Missing Library'),
                bundle.get(
                    'app_shell.error_missing_library_msg',
                    "The 'Pillow' library is required. Install with: pip install Pillow",
                ),
            )
        except Exception:
            messagebox.showerror(
                'Missing Library',
                "The 'Pillow' library is required. Install with: pip install Pillow",
            )
    sys.exit()

# Módulos da Aplicação
from .faq_screen import FaqScreen
from .license_module import LicenseScreen, LicenseManager, HardwareManager
from .antenna_module import AntennaModule
from .noise_module import NoiseModule
from .fastsurance_module import RFIDApp
from .threshold_module import ThresholdModule
from .fast_threshold_module import QuickThresholdModule
from .populacao_module import PopulacaoModule
from .simulador_modulo import SimuladorModule
from .power_constant_module import PowerConstantModule
from .rssi_power_module import RSSIPowerModule
from .calibration_module import CalibrationModule
from .fast_orientation_module import OrientationTester
from src.core.hardware_controller import get_hardware_controller
from src.core.port_manager import get_port_manager
from . import port_manager
from .i18n import get_translator, t
from .embedded_images import get_logo_image
from .app_console import fc_log

# Navegação: identificador interno (passado a navigate_to) → chave i18n do rótulo do botão
_NAV_SCREEN_ORDER = (
    "Population",
    "Simulator",
    "Threshold",
    "FastThreshold",
    "Fast Orientation",
    "Constant Power",
    "RSSI x Power",
    "FastSurance",
    "Antenna Check",
    "Noise Check",
    "Calibration",
    "License",
    "FAQ / Help",
)
_NAV_I18N_KEYS = {
    "Population": "app_shell.nav_population",
    "Simulator": "app_shell.nav_simulator",
    "Threshold": "app_shell.nav_threshold",
    "FastThreshold": "app_shell.nav_fastthreshold",
    "Fast Orientation": "app_shell.nav_fast_orientation",
    "Constant Power": "app_shell.nav_constant_power",
    "RSSI x Power": "app_shell.nav_rssi_power",
    "FastSurance": "app_shell.nav_fastsurance",
    "Antenna Check": "app_shell.nav_antenna_check",
    "Noise Check": "app_shell.nav_noise_check",
    "Calibration": "app_shell.nav_calibration",
    "License": "app_shell.nav_license",
    "FAQ / Help": "app_shell.nav_faq_help",
}

# Importa configuração de versão centralizada
try:
    from .version_config import get_software_info, get_full_name
    SOFTWARE_INFO = get_software_info()
    APP_VERSION = SOFTWARE_INFO['version']
except ImportError:
    # Fallback caso o arquivo não exista
    APP_VERSION = "3.1.2"
    SOFTWARE_INFO = {'software': '3.1.2', 'name': 'FastChecker II', 'version': '3.1.2'}

COLOR_BG, COLOR_HEADER_BG, COLOR_HEADER_FG = "#F5F5F5", "#212121", "#FFFFFF"
import os
LICENSE_DB_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "licenses.json")
LICENSE_CONFIG_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "license_config.json")

class AppShell(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master, bg=COLOR_BG)
        self.master = master
        try:
            self.master.title(get_full_name())
        except Exception:
            self.master.title(f"FastChecker II v {APP_VERSION}")
        self.master.geometry("1100x750")
        self.master.minsize(1000, 700)
        self.pack(fill="both", expand=True)

        # Descoberta otimizada: primeiro tenta sem refresh, depois com refresh se necessário
        self.com_port = port_manager.get_com_port_number(refresh=False)
        if self.com_port is None:
            self.com_port = port_manager.get_com_port_number(refresh=True)
        if self.com_port is None:
            # Fallback: varrer portas usando a própria DLL via HardwareManager
            try:
                temp_hw = HardwareManager(com_port=1)
                if temp_hw.try_detect_and_set_port():
                    self.com_port = temp_hw.com_port
            except Exception:
                pass
        if self.com_port is None:
            messagebox.showerror(t('app_shell.error_hardware_not_found'), t('app_shell.error_hardware_not_found_msg'))
            self.master.destroy()
            return
        
        if not self._perform_initial_reset(self.com_port):
            messagebox.showwarning(t('app_shell.warning_hardware_reset'), t('app_shell.warning_hardware_reset_msg'))

        self.license_manager = LicenseManager(LICENSE_DB_FILE)
        self.hardware_manager = HardwareManager(com_port=self.com_port)
        # ... (resto do __init__ igual ao anterior)
        self.license_limits = { 'min_freq': 800, 'max_freq': 1000, 'min_power': 5, 'max_power': 25 }
        self.port_manager = get_port_manager()
        
        # Sistema de traduções
        self.translator = get_translator()
        self.translator.add_language_change_listener(self._on_language_changed)
        
        # CORREÇÃO CRÍTICA: Atualiza a porta do port_manager para a porta REAL detectada
        self.port_manager.com_port = self.com_port
        fc_log("app_shell", f"[OK] Port_manager atualizado para usar porta COM{self.com_port}")
        
        # NOVO: Detecta se há hardware conectado
        self.hardware_available = self._check_hardware_availability()
        
        # NOVO: Lógica correta para licença ATIVA
        # Licença ATIVA = Licença válida + Hardware conectado
        if self.hardware_available:
            # Hardware conectado: verifica se há licença válida
            self.license_status = self.license_manager.check_any_valid_license()
            if self.license_status:
                # Licença válida + Hardware = Licença ATIVA
                self.active_license_token = self._load_active_license()
                self._activate_first_valid_license()
                fc_log("app_shell", "[OK] Licenca ATIVA: Hardware conectado + Licenca valida")
            else:
                # Hardware conectado mas sem licença válida
                self.active_license_token = None
                fc_log("app_shell", "[AVISO] Hardware conectado mas sem licenca valida - modo browser")
        else:
            # Sem hardware: força modo browser independente da licença
            self.license_status = False
            self.active_license_token = None
            fc_log("app_shell", "[AVISO] Hardware nao detectado - forcando modo browser")
        
        self.is_fullscreen = False
        self.nav_buttons = {}
        self.current_screen_frame = None
        self.test_is_running = False
        self.active_test_module = None
        self._create_header()
        self.content_area = tk.Frame(self, bg=COLOR_BG)
        self.content_area.pack(fill="both", expand=True)
        self.master.bind("<F11>", self.toggle_fullscreen)
        self.master.bind("<Escape>", self.exit_fullscreen)
        # screen_cache: instâncias reutilizadas ao voltar à aba (estado em memória).
        # Chaves usadas: "Threshold", "FastThreshold", "Fast Orientation", "Population",
        # "FastSurance", "Antenna Check", "Noise Check", "Calibration".
        # Não cacheados (recriados a cada visita): License, FAQ / Help, Simulator,
        # Constant Power, RSSI x Power (e outros ramos em navigate_to sem cache).
        self.screen_cache = {}
        self.navigate_to("License")

        # --- RF Safety: Watchdog e handlers de saída ---
        try:
            import atexit
            atexit.register(self._graceful_shutdown)
        except Exception:
            pass
        self.master.protocol("WM_DELETE_WINDOW", self._on_close)
        self._rf_heartbeat_ts = time.time()
        self._rf_watchdog_interval = 2.0  # segundos
        self._rf_watchdog_timeout = 10.0  # segundos sem batimento => STOP/CW_OFF
        self.after(int(self._rf_watchdog_interval * 1000), self._rf_watchdog_tick)

    def _load_active_license(self):
        """Carrega a licença ativa do arquivo de configuração."""
        try:
            fc_log("app_shell", f"[INFO] Tentando carregar licenca de: {LICENSE_CONFIG_FILE}")
            with open(LICENSE_CONFIG_FILE, 'r') as f:
                config = json.load(f)
                token = config.get('active_license_token')
                fc_log("app_shell", f"[INFO] Token carregado: {token[:30] if token else None}...")
                return token
        except (FileNotFoundError, json.JSONDecodeError) as e:
            fc_log("app_shell", f"[ERRO] Erro ao carregar licenca: {e}")
            return None

    def _save_active_license(self, token):
        """Salva a licença ativa no arquivo de configuração."""
        try:
            config = {'active_license_token': token}
            with open(LICENSE_CONFIG_FILE, 'w') as f:
                json.dump(config, f, indent=4)
        except Exception as e:
            fc_log("app_shell", f"Erro ao salvar licença ativa: {e}")

    def _perform_initial_reset(self, com_port):
        fc_log("app_shell", f"--- EXECUTANDO FACTORY RESET NA PORTA COM{com_port} ---")
        port_opened = False
        rfid_sdk = None
        try:
            dll_name = "UHFRFID.dll"
            if hasattr(sys, '_MEIPASS'):
                dll_path = os.path.join(sys._MEIPASS, dll_name)
            else:
                dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), dll_name)
                if not os.path.exists(dll_path):
                    dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'core', dll_name)

            # MODIFICADO: Verificação rápida se DLL existe
            if not os.path.exists(dll_path):
                fc_log("app_shell", "[AVISO] DLL UHFRFID.dll nao encontrada - pulando factory reset")
                return True  # Retorna True para não bloquear a inicialização

            ### ALTERAÇÃO CRÍTICA: Revertido para CDLL ###
            rfid_sdk = ctypes.CDLL(dll_path)
            
            rfid_sdk.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]
            rfid_sdk.UHF_RFID_Open.restype = ctypes.c_int
            rfid_sdk.UHF_RFID_Close.argtypes = [ctypes.c_ubyte]
            rfid_sdk.UHF_RFID_Close.restype = ctypes.c_int
            rfid_sdk.UHF_RFID_Set.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint)]
            rfid_sdk.UHF_RFID_Set.restype = ctypes.c_int
            
            # MODIFICADO: Tentativa rápida de conexão com timeout
            if rfid_sdk.UHF_RFID_Open(com_port, 115200) != 0:
                fc_log("app_shell", f"[AVISO] Falha ao abrir a porta COM{com_port} - pulando factory reset")
                return True
            port_opened = True
            fc_log("app_shell", "[OK] Porta aberta para o reset.")
            
            # CORRIGIDO: Usa Soft Reset (0x68) ao invés de Factory Reset - SOFT RESET FAZ BIP!
            RFID_CMD_SET_SOFTRESET = 0x68
            out_buf = ctypes.create_string_buffer(64)
            out_len = ctypes.c_uint(0)
            # Segurança: STOP e CW_OFF preventivos antes do reset
            try:
                rfid_sdk.UHF_RFID_Set(0x8C, None, 0, out_buf, ctypes.byref(out_len))  # STOP INVENTORY
                rfid_sdk.UHF_RFID_Set(0x24, ctypes.c_char_p(b'\x00'), 1, out_buf, ctypes.byref(out_len))  # CW OFF
            except Exception:
                pass
            status = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_SOFTRESET, None, 0, out_buf, ctypes.byref(out_len))
            if status == 0:
                fc_log("app_shell", "[OK] Comando de Soft Reset enviado com sucesso - BIP EMITIDO!")
            else:
                fc_log("app_shell", f"[AVISO] Falha ao enviar comando de Soft Reset. Código de erro: {status}")

            fc_log("app_shell", "...Aguardando 250ms para o hardware processar o reset...")
            time.sleep(0.25)
            fc_log("app_shell", "--- RESET INICIAL CONCLUÍDO ---")
            return True
        except Exception as e:
            fc_log("app_shell", f"[ERRO] ERRO CRITICO durante a rotina de reset: {e}")
            return True
        finally:
            if port_opened and rfid_sdk is not None:
                try:
                    rfid_sdk.UHF_RFID_Close(com_port)
                    fc_log("app_shell", "[OK] Porta fechada após o reset.")
                except Exception as ex:
                    fc_log("app_shell", f"[AVISO] Erro ao fechar porta no reset: {ex}")

    # O resto do arquivo app_shell.py continua o mesmo
    def _create_header(self):
        self.header_frame = tk.Frame(self, bg="#222", height=60)
        self.header_frame.pack(side="top", fill="x")
        self.header_frame.pack_propagate(False)

        try:
            _header_title = get_full_name()
        except Exception:
            _header_title = f"FastChecker II v {APP_VERSION}"

        # Logo - carrega imagem embutida, senão mostra texto
        try:
            # Carrega o logo do módulo embedded_images
            logo_image = get_logo_image()
            
            if logo_image is not None:
                try:
                    # Redimensiona mantendo proporção
                    original_width, original_height = logo_image.size
                    aspect_ratio = original_width / original_height
                    new_height = 35
                    new_width = int(new_height * aspect_ratio)
                    logo_image = logo_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
                    logo_photo = ImageTk.PhotoImage(logo_image)
                    logo_label = tk.Label(self.header_frame, image=logo_photo, bg="#222")
                    logo_label.image = logo_photo  # Mantém referência
                    logo_label.pack(side="left", padx=8, pady=2)
                    # Versão visível ao lado do logo (canto superior esquerdo da área da app)
                    tk.Label(
                        self.header_frame,
                        text=_header_title,
                        font=("Segoe UI", 11, "bold"),
                        bg="#222",
                        fg="#fff",
                    ).pack(side="left", padx=(0, 8), pady=2)
                    fc_log("app_shell", "✅ Logo carregado do módulo embedded_images")
                except Exception as e:
                    fc_log("app_shell", f"Erro ao processar logo embutido: {e}")
                    # Fallback para texto
                    logo_label = tk.Label(self.header_frame, text=_header_title, font=("Arial", 18, "bold"), bg="#222", fg="#fff")
                    logo_label.pack(side="left", padx=8, pady=2)
            else:
                # Fallback para texto
                logo_label = tk.Label(self.header_frame, text=_header_title, font=("Arial", 18, "bold"), bg="#222", fg="#fff")
                logo_label.pack(side="left", padx=8, pady=2)
        except Exception as e:
            # Fallback para texto em caso de erro
            logo_label = tk.Label(self.header_frame, text=_header_title, font=("Arial", 18, "bold"), bg="#222", fg="#fff")
            logo_label.pack(side="left", padx=8, pady=2)

        for screen_id in _NAV_SCREEN_ORDER:
            i18n_key = _NAV_I18N_KEYS.get(screen_id, screen_id)
            btn = tk.Button(
                self.header_frame,
                text=t(i18n_key),
                relief=tk.FLAT,
                font=("Segoe UI", 9),
                borderwidth=0,
                padx=10,
                command=lambda sid=screen_id: self.navigate_to(sid),
            )
            btn.pack(side="left", padx=1, pady=7)
            self.nav_buttons[screen_id] = btn
        self._apply_calibration_button_state()

        # Seletor de idioma (canto direito)
        self._create_language_selector()

        tk.Label(self.header_frame, text="Press F11 to FullScreen", bg=COLOR_HEADER_BG, fg=COLOR_HEADER_FG, font=("Segoe UI", 9)).pack(side="right", padx=20)
    
    def _create_language_selector(self):
        """Cria o seletor de idioma no header (canto direito)"""
        lang_frame = tk.Frame(self.header_frame, bg=COLOR_HEADER_BG)
        lang_frame.pack(side="right", padx=(0, 10))
        
        # Label ou botão mostrando idioma atual
        current_lang = self.translator.get_language().upper()
        self.lang_button = tk.Menubutton(
            lang_frame,
            text=f"🌐 {current_lang}",
            relief=tk.FLAT,
            bg=COLOR_HEADER_BG,
            fg=COLOR_HEADER_FG,
            font=("Segoe UI", 9),
            activebackground="#444",
            activeforeground=COLOR_HEADER_FG,
            borderwidth=0,
            padx=8,
            pady=4
        )
        self.lang_button.pack(side="right")
        
        # Menu dropdown
        lang_menu = tk.Menu(self.lang_button, tearoff=0, bg="#333", fg="#fff", font=("Segoe UI", 9))
        self.lang_button.config(menu=lang_menu)
        
        lang_menu.add_command(
            label="Português",
            command=lambda: self._change_language('pt')
        )
        lang_menu.add_command(
            label="English",
            command=lambda: self._change_language('en')
        )
    
    def _change_language(self, language):
        """Altera o idioma e atualiza a interface"""
        fc_log("app_shell", f"🔄 _change_language chamado com: {language}")
        old_lang = self.translator.get_language()
        self.translator.set_language(language)
        fc_log("app_shell", f"✅ Idioma alterado de {old_lang} para {language}")
        # O listener _on_language_changed será chamado automaticamente
    
    def _refresh_header_nav_labels(self):
        """Atualiza os textos da barra de navegação conforme o idioma atual."""
        if not getattr(self, "nav_buttons", None):
            return
        for screen_id, btn in self.nav_buttons.items():
            key = _NAV_I18N_KEYS.get(screen_id, screen_id)
            try:
                btn.config(text=t(key))
            except Exception:
                pass

    def _on_language_changed(self, old_lang, new_lang):
        """Callback chamado quando o idioma é alterado"""
        fc_log("app_shell", f"🔔 _on_language_changed chamado: {old_lang} -> {new_lang}")
        # Atualiza o botão de idioma
        if hasattr(self, 'lang_button'):
            self.lang_button.config(text=f"🌐 {new_lang.upper()}")
            fc_log("app_shell", f"✅ Botão de idioma atualizado para: {new_lang.upper()}")
        self._refresh_header_nav_labels()
        
        # Notifica todos os módulos para atualizarem seus textos
        # Primeiro, atualiza o módulo atual se existir
        if hasattr(self, 'current_screen_frame') and self.current_screen_frame:
            module_name = type(self.current_screen_frame).__name__
            if hasattr(self.current_screen_frame, 'refresh_language'):
                try:
                    fc_log("app_shell", f"🔄 Atualizando idioma no módulo: {module_name}")
                    self.current_screen_frame.refresh_language()
                    fc_log("app_shell", f"✅ Idioma atualizado com sucesso em {module_name}")
                except Exception as e:
                    fc_log("app_shell", f"⚠️ Erro ao atualizar idioma do módulo {module_name}: {e}")
                    import traceback
                    traceback.print_exc()
            else:
                # Módulo ainda não traduzido - não é um erro, apenas informativo
                fc_log("app_shell", f"ℹ️ Módulo {module_name} ainda não possui tradução (dual-language)")
        
    def _activate_first_valid_license(self):
        try:
            # Se já temos uma licença ativa salva, tenta usá-la primeiro
            if self.active_license_token:
                lic_data, msg = self.license_manager.parse_and_validate_token(self.active_license_token)
                if lic_data:
                    fc_log("app_shell", f"[OK] Licenca ativa restaurada: {lic_data.get('licName', 'N/A')}")
                    self.license_status = True
                    # CORREÇÃO: Atualiza license_limits quando licença é restaurada
                    self._update_license_limits_from_active_license()
                    return
            # Se não tem licença ativa ou ela é inválida, procura a primeira válida
            for uid, tokens in self.license_manager.licenses.items():
                for token in tokens:
                    lic_data, msg = self.license_manager.parse_and_validate_token(token)
                    if lic_data:
                        fc_log("app_shell", f"[OK] Ativando licenca: {lic_data.get('licName', 'N/A')}")
                        self.active_license_token = token
                        self._save_active_license(token)
                        self.license_status = True
                        # CORREÇÃO: Atualiza license_limits quando licença é ativada
                        self._update_license_limits_from_active_license()
                        return
            fc_log("app_shell", "[ERRO] Nenhuma licenca valida encontrada para ativacao automatica")
        except Exception as e:
            fc_log("app_shell", f"[ERRO] Erro ao ativar licenca automaticamente: {e}")

    # --- RF Safety: Watchdog/Shutdown Handlers ---
    def _rf_watchdog_tick(self):
        try:
            # Atualiza batimento enquanto um teste está marcado como rodando
            if getattr(self, 'test_is_running', False):
                self._rf_heartbeat_ts = time.time()
            # Se ficou tempo demais sem batimento e está marcado como running, envia STOP/CW_OFF
            if getattr(self, 'test_is_running', False):
                if time.time() - self._rf_heartbeat_ts > self._rf_watchdog_timeout:
                    try:
                        fc_log("app_shell", "[RF-SAFETY] Watchdog: Sem batimento recente. Enviando STOP/CW_OFF preventivo...")
                        pm = self.port_manager
                        threading.Thread(target=pm.force_reset, daemon=True).start()
                    except Exception:
                        pass
                    # Não altera flags de teste; apenas segurança
            # Reagenda
            self.after(int(self._rf_watchdog_interval * 1000), self._rf_watchdog_tick)
        except Exception:
            # Garante reagendamento mesmo com erros esporádicos
            try:
                self.after(int(self._rf_watchdog_interval * 1000), self._rf_watchdog_tick)
            except Exception:
                pass

    def _graceful_shutdown(self):
        try:
            fc_log("app_shell", "[RF-SAFETY] Shutdown gracioso: enviando STOP/CW_OFF...")
            threading.Thread(target=self.port_manager.force_reset, daemon=True).start()
        except Exception:
            pass

    def _on_close(self):
        try:
            # Garante persistência antes de destruir a UI (evita perda de estado em módulos como Population).
            try:
                modules = []
                if hasattr(self, 'current_screen_frame') and self.current_screen_frame:
                    modules.append(self.current_screen_frame)
                if hasattr(self, 'screen_cache') and self.screen_cache:
                    modules.extend([m for m in self.screen_cache.values() if m])

                seen_ids = set()
                for module in modules:
                    if not module:
                        continue
                    mid = id(module)
                    if mid in seen_ids:
                        continue
                    seen_ids.add(mid)

                    try:
                        if hasattr(module, '_save_persisted_data'):
                            module._save_persisted_data()
                        elif hasattr(module, '_save_complete_ui_state'):
                            module._save_complete_ui_state()
                    except Exception as _e_mod:
                        fc_log(
                            "AppShell",
                            f"WARN: falha ao persistir estado do módulo ({getattr(module, 'module_name', 'unknown')}): {_e_mod}",
                        )
            except Exception as _e:
                fc_log("AppShell", f"WARN: falha geral ao persistir módulos no close: {_e}")

            self._graceful_shutdown()
        finally:
            try:
                self.master.destroy()
            except Exception:
                pass

    def set_active_license(self, token):
        self.active_license_token = token
        self._save_active_license(token)
        self.license_status = self.license_manager.check_any_valid_license()
        # CORREÇÃO: Atualiza license_limits quando licença é alterada
        self._update_license_limits_from_active_license()
        
        # NOVO: Notifica todos os módulos sobre a mudança de licença
        self._notify_modules_license_changed()
        self._apply_calibration_button_state()
    
    def set_active_project(self, project_id):
        self.active_project_id = project_id

    def set_project_data(self, project_data):
        self.active_project_data = project_data

    def get_active_project_data(self):
        return getattr(self, 'active_project_data', None)

    def set_test_running(self, is_running, module_name=None):
        self.test_is_running = is_running
        self.active_test_module = module_name if is_running else None
        self._update_nav_buttons_state()
        
    def is_test_running(self):
        return self.test_is_running
        
    def get_active_test_module(self):
        return self.active_test_module

    def perform_strong_reader_reset_async(self):
        """
        Executa um reset forte do reader em background. Sempre agendado a partir da UI (after)
        para não travar. Operações de porta em sub-thread com timeout para evitar bloqueio.
        """
        com_port = getattr(self, 'com_port', None)
        if com_port is None:
            return

        def _do_open_and_reset(rfid_sdk, port, result_holder):
            """Abre porta, envia STOP/CW OFF/Soft Reset, fecha. Resultado em result_holder[0]."""
            try:
                if rfid_sdk.UHF_RFID_Open(port, 115200) != 0:
                    time.sleep(2.0)
                    if rfid_sdk.UHF_RFID_Open(port, 115200) != 0:
                        result_holder[0] = False
                        return
                out_buf = ctypes.create_string_buffer(64)
                out_len = ctypes.c_uint(0)
                try:
                    rfid_sdk.UHF_RFID_Set(0x8C, None, 0, out_buf, ctypes.byref(out_len))
                    rfid_sdk.UHF_RFID_Set(0x24, ctypes.c_char_p(b'\x00'), 1, out_buf, ctypes.byref(out_len))
                    time.sleep(0.15)
                    rfid_sdk.UHF_RFID_Set(0x68, None, 0, out_buf, ctypes.byref(out_len))
                except Exception:
                    pass
                try:
                    rfid_sdk.UHF_RFID_Close(port)
                except Exception:
                    pass
                result_holder[0] = True
            except Exception:
                result_holder[0] = False

        def _strong_reset_worker():
            try:
                time.sleep(1.5)
                time.sleep(3.0)
                dll_name = "UHFRFID.dll"
                if hasattr(sys, '_MEIPASS'):
                    dll_path = os.path.join(sys._MEIPASS, dll_name)
                else:
                    dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), dll_name)
                    if not os.path.exists(dll_path):
                        dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'core', dll_name)
                if not os.path.exists(dll_path):
                    fc_log("app_shell", "[AVISO] Reset forte: DLL não encontrada")
                    return
                rfid_sdk = ctypes.CDLL(dll_path)
                rfid_sdk.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]
                rfid_sdk.UHF_RFID_Open.restype = ctypes.c_int
                rfid_sdk.UHF_RFID_Close.argtypes = [ctypes.c_ubyte]
                rfid_sdk.UHF_RFID_Close.restype = ctypes.c_int
                rfid_sdk.UHF_RFID_Set.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint)]
                rfid_sdk.UHF_RFID_Set.restype = ctypes.c_int

                result = [None]
                run = threading.Thread(target=_do_open_and_reset, args=(rfid_sdk, com_port, result), daemon=True)
                run.start()
                run.join(timeout=12.0)
                if result[0] is None:
                    fc_log("app_shell", "[AVISO] Reset forte: timeout no 1º ciclo")
                time.sleep(1.2)
                result[0] = None
                run2 = threading.Thread(target=_do_open_and_reset, args=(rfid_sdk, com_port, result), daemon=True)
                run2.start()
                run2.join(timeout=12.0)
                if result[0] is None:
                    fc_log("app_shell", "[AVISO] Reset forte: timeout no 2º ciclo")
                time.sleep(0.8)
                fc_log("app_shell", "[OK] Reset forte do reader concluído.")
            except Exception as e:
                fc_log("app_shell", f"[AVISO] Reset forte do reader falhou: {e}")

        t = threading.Thread(target=_strong_reset_worker, daemon=True)
        t.start()

    def _apply_calibration_button_state(self):
        """Com licença inválida, Calibration fica sombreado e sem acesso."""
        if "Calibration" not in self.nav_buttons:
            return
        btn = self.nav_buttons["Calibration"]
        if not getattr(self, "license_status", False):
            btn.config(state="disabled", bg="#CCCCCC", fg="#666666")
        elif not self.test_is_running:
            btn.config(state="normal", bg="#F5F5F5", fg="black")

    def _update_nav_buttons_state(self):
        if self.test_is_running:
            for name, button in self.nav_buttons.items():
                if name == self.active_test_module:
                    button.config(state='normal', bg="#FFD700", fg="black", 
                                font=("Segoe UI", 9, "bold"), relief=tk.RAISED)
                else:
                    button.config(state='disabled', bg="#CCCCCC", fg="#666666", 
                                font=("Segoe UI", 9), relief=tk.FLAT)
        else:
            for name, button in self.nav_buttons.items():
                button.config(state='normal', bg="#F5F5F5", fg="black", 
                            font=("Segoe UI", 9), relief=tk.FLAT)
            if hasattr(self, 'current_screen_name') and self.current_screen_name in self.nav_buttons:
                button = self.nav_buttons[self.current_screen_name]
                button.config(font=("Segoe UI", 9, "bold"), relief=tk.RAISED, bg="#90EE90", fg="black")
            self._apply_calibration_button_state()
    
    def _notify_modules_license_changed(self):
        """NOVO: Notifica todos os módulos sobre mudança no status da licença"""
        try:
            fc_log("app_shell", f"[INFO] AppShell: Notificando modulos sobre mudanca de licenca - status: {self.license_status}")
            
            # CORREÇÃO: Verifica se há módulo atual antes de notificar
            if hasattr(self, 'current_screen_frame') and self.current_screen_frame:
                self._update_current_module_license()
            
            # Notifica módulos em cache (ex: FastSurance)
            if hasattr(self, 'screen_cache'):
                for module_name, cached_module in self.screen_cache.items():
                    if cached_module and cached_module.winfo_exists():
                        self._update_module_license(cached_module, module_name)
                        
        except Exception as e:
            fc_log("app_shell", f"[ERRO] Erro ao notificar modulos sobre mudanca de licenca: {e}")
    
    def _update_current_module_license(self):
        """NOVO: Atualiza a licença do módulo atual"""
        if not hasattr(self, 'current_screen_frame') or not self.current_screen_frame:
            return
            
        module_name = getattr(self.current_screen_frame, 'module_name', 'Unknown')
        
        # CORREÇÃO: Para o Threshold, usa update_license_status se disponível
        if module_name == "Threshold" and hasattr(self.current_screen_frame, 'update_license_status'):
            fc_log("app_shell", f"[INFO] AppShell: Atualizando Threshold via update_license_status")
            self.current_screen_frame.update_license_status(self.license_status)
        else:
            self._update_module_license(self.current_screen_frame, module_name)
    
    def _update_module_license(self, module, module_name):
        """NOVO: Atualiza a licença de um módulo específico"""
        try:
            # Módulos que usam app_shell (Threshold, FastSurance, RSSI x Power, Constant Power)
            if hasattr(module, '_update_license_limits_from_app_shell'):
                fc_log("app_shell", f"[INFO] AppShell: Atualizando {module_name} via _update_license_limits_from_app_shell")
                module._update_license_limits_from_app_shell()
                
            # Módulos que usam is_licensed (Noise, Antenna, etc.)
            elif hasattr(module, 'update_license_status'):
                fc_log("app_shell", f"[INFO] AppShell: Atualizando {module_name} via update_license_status")
                module.update_license_status(self.license_status)
                
            # Módulos que usam license_limits
            elif hasattr(module, 'license_limits'):
                fc_log("app_shell", f"[INFO] AppShell: Atualizando {module_name} via license_limits")
                if module_name == "Antenna Check":
                    new_limits = self._calculate_license_limits(["AntennaCheck", "FastChecker"])
                    module.license_limits = new_limits
                    # CORREÇÃO: Atualiza as variáveis de limite de frequência
                    if hasattr(module, 'min_freq_lic'):
                        module.min_freq_lic = new_limits.get('min_freq', 800)
                        module.max_freq_lic = new_limits.get('max_freq', 1000)
                        module.min_power_lic = new_limits.get('min_power', 5)
                        module.max_power_lic = new_limits.get('max_power', 25)
                        fc_log("app_shell", f"[OK] AppShell: Limites de frequência atualizados - {module.min_freq_lic}-{module.max_freq_lic}MHz")
                    if hasattr(module, 'update_ui_state'):
                        module.update_ui_state()
                        
        except Exception as e:
            fc_log("app_shell", f"[AVISO] Erro ao atualizar licença do módulo {module_name}: {e}")
    
    def _update_license_limits_from_active_license(self):
        """CORREÇÃO: Atualiza license_limits baseado na licença ativa"""
        if not self.active_license_token:
            self.license_limits = {'min_freq': 800, 'max_freq': 1000, 'min_power': 5, 'max_power': 25, 'is_licensed': False}
            return
        
        lic_data, _ = self.license_manager.parse_and_validate_token(self.active_license_token)
        if lic_data:
            # Atualiza com os limites da licença ativa
            self.license_limits = {'min_freq': 800, 'max_freq': 1000, 'min_power': 5, 'max_power': 25, 'is_licensed': True}
            
            try:
                if 'freqRange' in lic_data:
                    freq_ranges = lic_data['freqRange'].split(';')
                    for freq_range in freq_ranges:
                        if '@' in freq_range:
                            f_min, f_max = map(int, freq_range.split('@'))
                            if f_min < self.license_limits['min_freq']:
                                self.license_limits['min_freq'] = f_min
                            if f_max > self.license_limits['max_freq']:
                                self.license_limits['max_freq'] = f_max
                
                if 'powRange' in lic_data:
                    p_min, p_max = map(int, lic_data['powRange'].split('@'))
                    self.license_limits['min_power'] = p_min
                    self.license_limits['max_power'] = p_max
                
                fc_log("app_shell", f"[OK] License limits atualizados: Freq {self.license_limits['min_freq']}-{self.license_limits['max_freq']}MHz, Power {self.license_limits['min_power']}-{self.license_limits['max_power']}dBm")
                
                # NOVO: Notifica módulos sobre mudança de licença
                self._notify_modules_license_changed()

                # NOVO: Log consolidado dos limites ativos (faixas e gaps)
                try:
                    calc = self._calculate_license_limits(["FastChecker", "AntennaCheck", "Threshold"])
                    freq_ranges = calc.get('freq_ranges', [])
                    excluded = calc.get('excluded_ranges', []) if 'excluded_ranges' in calc else []
                    fc_log("app_shell", f"[INFO] Licença ativa: {calc.get('license_name')} | Faixas: {freq_ranges} | Gaps: {excluded} | Potência: {calc.get('min_power')}-{calc.get('max_power')} dBm")
                except Exception as _e:
                    fc_log("app_shell", f"[AVISO] Falha ao imprimir limites detalhados da licença: {_e}")
                
            except Exception as e:
                fc_log("app_shell", f"[ERRO] Erro ao processar limites da licença: {e}")
                self.license_limits['is_licensed'] = False
        else:
            self.license_limits['is_licensed'] = False

    def _calculate_license_limits(self, valid_license_names):
        limits = {
            'min_freq': 800, 'max_freq': 1000, 'min_power': 5, 'max_power': 25,
            'is_licensed': False, 'freq_ranges': [], 'license_name': None
        }
        if not self.active_license_token: 
            fc_log("app_shell", f"[INFO] _calculate_license_limits: Nenhuma licença ativa")
            return limits
        lic_data, _ = self.license_manager.parse_and_validate_token(self.active_license_token)
        
        # CORREÇÃO: Log detalhado para diagnóstico
        if lic_data:
            license_name = lic_data.get("licName", "N/A")
            fc_log("app_shell", f"[INFO] _calculate_license_limits: Licença encontrada: '{license_name}', nomes válidos: {valid_license_names}")
        else:
            fc_log("app_shell", f"[INFO] _calculate_license_limits: Licença inválida ou token corrompido")
            return limits
            
        # CORREÇÃO: Aceita qualquer licença válida para módulos genéricos como Threshold
        # Se a lista contém "Threshold" ou "FastChecker", aceita qualquer licença válida
        accepts_any_license = any(name in ["Threshold", "FastChecker"] for name in valid_license_names)
        
        if accepts_any_license or lic_data.get("licName") in valid_license_names:
            limits['is_licensed'] = True
            limits['license_name'] = lic_data.get("licName")
            fc_log("app_shell", f"[OK] _calculate_license_limits: Licença aceita para {valid_license_names} - '{limits['license_name']}'.")
            try:
                if 'freqRange' in lic_data:
                    fc_log("app_shell", f"[INFO] _calculate_license_limits: freqRange raw: '{lic_data['freqRange']}'")
                    freq_ranges = lic_data['freqRange'].split(';')
                    fc_log("app_shell", f"[INFO] _calculate_license_limits: freq_ranges split: {freq_ranges}")
                    for freq_range in freq_ranges:
                        if '@' in freq_range:
                            # Aceita decimais e vírgulas nas faixas (ex.: 915,5)
                            f_min_str, f_max_str = freq_range.split('@')
                            f_min = float(f_min_str.replace(',', '.'))
                            f_max = float(f_max_str.replace(',', '.'))
                            limits['freq_ranges'].append((f_min, f_max))
                            fc_log("app_shell", f"[OK] _calculate_license_limits: Adicionada faixa: {f_min}-{f_max} MHz")
                    if not limits['freq_ranges'] and '@' in lic_data['freqRange']:
                        f_min_str, f_max_str = lic_data['freqRange'].split('@')
                        f_min = float(f_min_str.replace(',', '.'))
                        f_max = float(f_max_str.replace(',', '.'))
                        limits['freq_ranges'].append((f_min, f_max))
                        fc_log("app_shell", f"[OK] _calculate_license_limits: Adicionada faixa única: {f_min}-{f_max} MHz")
                    if limits['freq_ranges']:
                        all_freqs = [f for r in limits['freq_ranges'] for f in r]
                        limits['min_freq'] = min(all_freqs)
                        limits['max_freq'] = max(all_freqs)
                        fc_log("app_shell", f"[INFO] _calculate_license_limits: freq_ranges final: {limits['freq_ranges']}")
                        fc_log("app_shell", f"[INFO] _calculate_license_limits: min_freq: {limits['min_freq']}, max_freq: {limits['max_freq']}")
                    else:
                        fc_log("app_shell", f"[AVISO] _calculate_license_limits: Nenhuma faixa válida encontrada!")
                
                # CORREÇÃO CRÍTICA: Processa freqGap para criar faixas excluídas
                if 'freqGap' in lic_data and lic_data['freqGap']:
                    fc_log("app_shell", f"[INFO] _calculate_license_limits: freqGap raw: '{lic_data['freqGap']}'")
                    try:
                        gap_ranges = lic_data['freqGap'].split(';')
                        for gap_range in gap_ranges:
                            if '@' in gap_range:
                                # CORREÇÃO: Converte vírgulas para pontos antes de fazer float
                                gap_parts = gap_range.split('@')
                                gap_min_str = gap_parts[0].replace(',', '.')
                                gap_max_str = gap_parts[1].replace(',', '.')
                                gap_min, gap_max = float(gap_min_str), float(gap_max_str)
                                fc_log("app_shell", f"[INFO] _calculate_license_limits: Gap detectado: {gap_min}-{gap_max} MHz")
                                # Adiciona o gap como uma faixa excluída
                                limits['excluded_ranges'] = limits.get('excluded_ranges', [])
                                limits['excluded_ranges'].append((gap_min, gap_max))
                                fc_log("app_shell", f"[OK] _calculate_license_limits: Faixa excluída adicionada: {gap_min}-{gap_max} MHz")
                    except (ValueError, IndexError) as e:
                        fc_log("app_shell", f"[AVISO] _calculate_license_limits: Erro ao processar freqGap: {e}")
                
                if 'powRange' in lic_data:
                    p_min, p_max = map(int, lic_data['powRange'].split('@'))
                    limits['min_power'] = p_min
                    limits['max_power'] = p_max
            except (ValueError, IndexError):
                limits['is_licensed'] = False
        else:
            fc_log("app_shell", f"[ERRO] _calculate_license_limits: Licença '{lic_data.get('licName', 'N/A')}' não aceita para {valid_license_names}")
        return limits

    def _invoke_tab_leave_hook(self, frame):
        """Chama on_tab_leave() no módulo ao sair da aba (opcional)."""
        if frame is None:
            return
        fn = getattr(frame, "on_tab_leave", None)
        if not callable(fn):
            return
        try:
            fn()
        except Exception as e:
            fc_log("app_shell", f"[AVISO] AppShell: on_tab_leave falhou: {e}")

    def _invoke_tab_enter_hook(self, frame):
        """Chama on_tab_enter() ou, em legado, on_tab_switch() ao entrar na aba."""
        if frame is None:
            return
        fn = getattr(frame, "on_tab_enter", None)
        if callable(fn):
            try:
                fn()
            except Exception as e:
                fc_log("app_shell", f"[AVISO] AppShell: on_tab_enter falhou: {e}")
            return
        legacy = getattr(frame, "on_tab_switch", None)
        if callable(legacy):
            try:
                legacy()
            except Exception as e:
                fc_log("app_shell", f"[AVISO] AppShell: on_tab_switch falhou: {e}")

    def navigate_to(self, screen_name):
        if self.test_is_running and screen_name != self.active_test_module:
            messagebox.showwarning(t('app_shell.warning_test_in_progress'), t('app_shell.warning_test_in_progress_msg'), parent=self.master)
            return
            
        # CORREÇÃO: Calcula o status da licença baseado na licença ativa
        if self.active_license_token:
            lic_data, _ = self.license_manager.parse_and_validate_token(self.active_license_token)
            self.license_status = lic_data is not None
        else:
            self.license_status = False
            
        if screen_name == "Calibration" and not getattr(self, "license_status", False):
            messagebox.showwarning(
                t('app_shell.warning_calibration_license_title'),
                t('app_shell.warning_calibration_license_msg'),
                parent=self.master
            )
            return

        if self.current_screen_frame:
            self._invoke_tab_leave_hook(self.current_screen_frame)
            # CORREÇÃO: Remove o módulo anterior da tela antes de mostrar o novo
            current_name = getattr(self, 'current_screen_name', None)
            if current_name in ["FastSurance", "Noise Check", "Threshold", "FastThreshold", "Fast Orientation", "Population", "Simulator", "Antenna Check", "Calibration"]:
                try:
                    # Salva no cache ANTES de remover da tela
                    self.screen_cache[current_name] = self.current_screen_frame
                    
                    # CORREÇÃO: Tenta AMBOS os métodos para garantir que seja removido
                    # Alguns módulos podem usar pack internamente mas serem adicionados com grid
                    removed = False
                    try:
                        self.current_screen_frame.pack_forget()
                        removed = True
                        fc_log("app_shell", f"[OK] {current_name}: Removido da tela (pack_forget)")
                    except:
                        pass
                    
                    if not removed:
                        try:
                            self.current_screen_frame.grid_forget()
                            removed = True
                            fc_log("app_shell", f"[OK] {current_name}: Removido da tela (grid_forget)")
                        except:
                            pass
                    
                    if not removed:
                        fc_log("app_shell", f"[AVISO] {current_name}: Não foi possível remover da tela com pack/grid_forget")
                    
                    fc_log("app_shell", f"[OK] {current_name}: Instância preservada no cache")
                except Exception as e:
                    fc_log("app_shell", f"[ERRO] {current_name}: Erro ao cachear: {e}")
                    try:
                        self.current_screen_frame.destroy()
                    except:
                        pass
                    self.screen_cache.pop(current_name, None)
                    fc_log("app_shell", f"[AVISO] {current_name}: Fallback - instância destruída")
            else:
                # Módulos que não são cacheados são destruídos
                try:
                    self.current_screen_frame.destroy()
                except:
                    pass
            self.current_screen_frame = None

        # Barreira de segurança: reset em background para NUNCA bloquear a UI (DLL pode travar se reader em proteção)
        try:
            pm = self.port_manager if hasattr(self, 'port_manager') else get_port_manager()
            threading.Thread(target=pm.force_reset, daemon=True).start()
            time.sleep(0.05)
        except Exception:
            pass
        
        self.current_screen_name = screen_name
        
        if not self.test_is_running:
            for name, button in self.nav_buttons.items():
                button.config(font=("Segoe UI", 9), relief=tk.FLAT, bg="#F5F5F5", fg="black")
            if screen_name in self.nav_buttons:
                self.nav_buttons[screen_name].config(font=("Segoe UI", 9, "bold"), relief=tk.RAISED, bg="#90EE90", fg="black")
            
        try:
            if screen_name == "Threshold":
                fc_log("app_shell", f"[INFO] AppShell: Navegando para Threshold...")
                # Reutiliza a instância existente para reter dados e gráficos
                cached = self.screen_cache.get('Threshold')
                fc_log("app_shell", f"[INFO] AppShell: Cache Threshold existe: {cached is not None}")
                if cached and cached.winfo_exists():
                    fc_log("app_shell", f"[INFO] AppShell: Reutilizando instância cached do Threshold")
                    self.current_screen_frame = cached
                    # Força redesenho completo dos gráficos ao voltar para a aba
                    if hasattr(self.current_screen_frame, '_force_graph_redraw'):
                        fc_log("app_shell", "[INFO] Threshold: Forçando redesenho dos gráficos...")
                        self.current_screen_frame._force_graph_redraw()
                        fc_log("app_shell", "[OK] Threshold: Gráficos redesenhados")
                else:
                    fc_log("app_shell", f"[INFO] AppShell: Criando nova instância do Threshold")
                    self.current_screen_frame = ThresholdModule(self.content_area, app_shell=self, com_port=self.com_port)
                    self.screen_cache['Threshold'] = self.current_screen_frame
                    fc_log("app_shell", f"[INFO] AppShell: ThresholdModule criado e armazenado no cache")
            elif screen_name == "FastThreshold":
                fc_log("app_shell", f"[INFO] AppShell: Navegando para FastThreshold...")
                # Reutiliza a instância existente para reter dados e gráficos
                cached = self.screen_cache.get('FastThreshold')
                fc_log("app_shell", f"[INFO] AppShell: Cache FastThreshold existe: {cached is not None}")
                if cached and cached.winfo_exists():
                    fc_log("app_shell", f"[INFO] AppShell: Reutilizando instância cached do FastThreshold")
                    self.current_screen_frame = cached
                else:
                    fc_log("app_shell", f"[INFO] AppShell: Criando nova instância do FastThreshold")
                    self.current_screen_frame = QuickThresholdModule(self.content_area, app_shell=self, com_port=self.com_port)
                    self.screen_cache['FastThreshold'] = self.current_screen_frame
                    fc_log("app_shell", f"[INFO] AppShell: QuickThresholdModule criado e armazenado no cache")
            elif screen_name == "Fast Orientation":
                fc_log("app_shell", f"[INFO] AppShell: Navegando para Fast Orientation...")
                cached = self.screen_cache.get('Fast Orientation')
                fc_log("app_shell", f"[INFO] AppShell: Cache Fast Orientation existe: {cached is not None}")
                if cached and cached.winfo_exists():
                    fc_log("app_shell", f"[INFO] AppShell: Reutilizando instância cached do Fast Orientation")
                    self.current_screen_frame = cached
                    if hasattr(self.current_screen_frame, 'refresh_language'):
                        try:
                            self.current_screen_frame.refresh_language()
                        except Exception:
                            pass
                else:
                    fc_log("app_shell", f"[INFO] AppShell: Criando nova instância do Fast Orientation")
                    self.current_screen_frame = OrientationTester(self.content_area, app_shell=self)
                    self.screen_cache['Fast Orientation'] = self.current_screen_frame
                    fc_log("app_shell", f"[INFO] AppShell: OrientationTester criado e armazenado no cache")
                    if hasattr(self.current_screen_frame, 'refresh_language'):
                        try:
                            self.current_screen_frame.refresh_language()
                        except Exception:
                            pass
            elif screen_name == "Population":
                fc_log("app_shell", f"[INFO] AppShell: Navegando para Population...")
                # Reutiliza a instância existente para reter dados e estado
                cached = self.screen_cache.get('Population')
                fc_log("app_shell", f"[INFO] AppShell: Cache Population existe: {cached is not None}")
                if cached and cached.winfo_exists():
                    fc_log("app_shell", f"[INFO] AppShell: Reutilizando instância cached do Population")
                    self.current_screen_frame = cached
                    # Atualiza a lista de tags para refletir qualquer mudança
                    if hasattr(self.current_screen_frame, 'update_tags_list'):
                        fc_log("app_shell", "[INFO] Population: Atualizando lista de tags...")
                        self.current_screen_frame.update_tags_list()
                        fc_log("app_shell", "[OK] Population: Lista de tags atualizada")
                else:
                    fc_log("app_shell", f"[INFO] AppShell: Criando nova instância do Population")
                    try:
                        self.current_screen_frame = PopulacaoModule(self.content_area, app_shell=self, com_port=self.com_port)
                        self.screen_cache['Population'] = self.current_screen_frame
                        fc_log("app_shell", f"[INFO] AppShell: PopulacaoModule criado e armazenado no cache")
                    except Exception as e:
                        fc_log("app_shell", f"[ERRO] AppShell: Erro ao criar PopulacaoModule: {e}")
                        import traceback
                        traceback.print_exc()
            elif screen_name == "Simulator":
                fc_log("app_shell", f"[INFO] AppShell: Criando SimuladorModule")
                try:
                    # Simulator pode receber app_shell para acesso a port_manager e license
                    self.current_screen_frame = SimuladorModule(self.content_area, app_shell=self)
                    fc_log("app_shell", f"[INFO] AppShell: SimuladorModule criado")
                except Exception as e:
                    fc_log("app_shell", f"[ERRO] AppShell: Erro ao criar SimuladorModule: {e}")
                    import traceback
                    traceback.print_exc()
                    messagebox.showerror(t('app_shell.error_module_load'), t('app_shell.error_module_load_msg').format(module='Simulator', error=str(e)), parent=self.master)
                    return
            elif screen_name == "Constant Power":
                fc_log("app_shell", f"[INFO] AppShell: Criando módulo Constant Power...")
                fc_log("app_shell", f"[INFO] AppShell: License_status atual: {self.license_status}")
                fc_log("app_shell", f"[INFO] AppShell: License_limits atual: {self.license_limits}")
                self.current_screen_frame = PowerConstantModule(self.content_area, app_shell=self, com_port=self.com_port)
                fc_log("app_shell", f"[INFO] AppShell: Módulo Constant Power criado")
                # NOVO: Atualiza limites da licença após criar o módulo
                if hasattr(self.current_screen_frame, '_update_license_limits_from_app_shell'):
                    fc_log("app_shell", f"[INFO] AppShell: Chamando _update_license_limits_from_app_shell...")
                    self.current_screen_frame._update_license_limits_from_app_shell()
                    fc_log("app_shell", f"[INFO] AppShell: _update_license_limits_from_app_shell concluído")
                else:
                    fc_log("app_shell", f"[AVISO] AppShell: Módulo Constant Power não tem _update_license_limits_from_app_shell")
            elif screen_name == "RSSI x Power":
                self.current_screen_frame = RSSIPowerModule(self.content_area, app_shell=self, com_port=self.com_port)
                # NOVO: Atualiza limites da licença após criar o módulo
                if hasattr(self.current_screen_frame, '_update_license_limits_from_app_shell'):
                    self.current_screen_frame._update_license_limits_from_app_shell()
            elif screen_name == "FastSurance":
                fc_log("app_shell", f"[INFO] AppShell: Navegando para FastSurance...")
                # Reutiliza a instância existente para reter dados
                cached = self.screen_cache.get('FastSurance')
                fc_log("app_shell", f"[INFO] AppShell: Cache FastSurance existe: {cached is not None}")
                if cached and cached.winfo_exists():
                    fc_log("app_shell", f"[INFO] AppShell: Reutilizando instância cached do FastSurance")
                    self.current_screen_frame = cached
                    # NOVO: Restaura dados de threshold ao voltar para a aba
                    fc_log("app_shell", "[INFO] FastSurance: Restaurando dados de threshold ao trocar de aba...")
                    if hasattr(self.current_screen_frame, '_restore_threshold_data_from_history'):
                        fc_log("app_shell", "[INFO] AppShell: Método _restore_threshold_data_from_history encontrado, chamando...")
                        # Chama a restauração diretamente
                        self.current_screen_frame._restore_threshold_data_from_history()
                        fc_log("app_shell", "[OK] FastSurance: Dados de threshold restaurados ao trocar de aba")
                    else:
                        fc_log("app_shell", "[AVISO] FastSurance: Método _restore_threshold_data_from_history não encontrado")
                else:
                    fc_log("app_shell", f"[INFO] AppShell: Criando nova instância do FastSurance")
                    hardware_controller = get_hardware_controller()
                    self.current_screen_frame = RFIDApp(self.content_area, hardware_controller=hardware_controller, com_port=self.com_port, app_shell=self)
                    self.screen_cache['FastSurance'] = self.current_screen_frame
                if hasattr(self.current_screen_frame, '_update_license_limits_from_app_shell'):
                    self.current_screen_frame._update_license_limits_from_app_shell()
                # Após force_reset em thread: re-sincroniza leitor ao mostrar FastSurance (nova ou cache)
                _fs = self.current_screen_frame
                if hasattr(_fs, '_initialize_hardware_on_enter'):
                    _fs.after(450, _fs._initialize_hardware_on_enter)
            elif screen_name == "Antenna Check":
                # Reutiliza a instância existente para reter dados e configurações
                cached = self.screen_cache.get('Antenna Check')
                if cached and cached.winfo_exists():
                    self.current_screen_frame = cached
                    fc_log("app_shell", f"[OK] Antenna Check: Instância reutilizada do cache")
                    # CORREÇÃO: Atualiza os limites da licença na instância existente
                    if hasattr(self.current_screen_frame, 'update_license_status'):
                        fc_log("app_shell", f"[INFO] AppShell: Atualizando Antenna Check via update_license_status")
                        self.current_screen_frame.update_license_status(self.license_status)
                else:
                    fc_log("app_shell", f"[INFO] AppShell: Criando nova instância do Antenna Check")
                    license_limits = self._calculate_license_limits(["AntennaCheck", "FastChecker"])
                    fc_log("app_shell", f"[INFO] AppShell: Antenna Check - license_limits calculados: {license_limits}")
                    fc_log("app_shell", f"[INFO] AppShell: Antenna Check - is_licensed: {license_limits.get('is_licensed', False)}")
                    self.current_screen_frame = AntennaModule(self.content_area, license_limits=license_limits, app_shell=self, com_port=self.com_port)
                    self.screen_cache['Antenna Check'] = self.current_screen_frame
                    fc_log("app_shell", f"[INFO] AppShell: Antenna Check criado e adicionado ao cache")
            elif screen_name == "Calibration":
                cached = self.screen_cache.get("Calibration")
                if cached and cached.winfo_exists():
                    self.current_screen_frame = cached
                    if hasattr(self.current_screen_frame, "refresh_license_from_app_shell"):
                        self.current_screen_frame.refresh_license_from_app_shell()
                else:
                    self.current_screen_frame = CalibrationModule(self.content_area, app_shell=self, com_port=self.com_port)
                    self.screen_cache["Calibration"] = self.current_screen_frame
                    if hasattr(self.current_screen_frame, "refresh_license_from_app_shell"):
                        self.current_screen_frame.refresh_license_from_app_shell()
            elif screen_name == "Noise Check":
                # Reutiliza a instância existente para reter dados e testes em andamento
                cached = self.screen_cache.get('Noise Check')
                if cached and cached.winfo_exists():
                    self.current_screen_frame = cached
                    fc_log("app_shell", f"[OK] Noise Check: Instância reutilizada do cache")
                    # CORREÇÃO: Atualiza a licença da instância reutilizada
                    if hasattr(self.current_screen_frame, 'update_license_status'):
                        self.current_screen_frame.update_license_status(self.license_status)
                else:
                    fc_log("app_shell", f"[INFO] AppShell: Criando NoiseModule com license_status: {self.license_status}")
                    # CORREÇÃO: Calcula limites de licença específicos para Noise Check
                    license_limits = self._calculate_license_limits(["NoiseCheck", "FastChecker"])
                    fc_log("app_shell", f"[INFO] AppShell: Noise Check - license_limits calculados: {license_limits}")
                    self.current_screen_frame = NoiseModule(self.content_area, is_licensed=self.license_status, app_shell=self, com_port=self.com_port)
                    # CORREÇÃO: Força atualização da licença após criação
                    if hasattr(self.current_screen_frame, 'update_license_status'):
                        self.current_screen_frame.update_license_status(self.license_status)
                    self.screen_cache['Noise Check'] = self.current_screen_frame
                    fc_log("app_shell", f"[INFO] AppShell: NoiseModule criado e adicionado ao cache, module_name: {getattr(self.current_screen_frame, 'module_name', 'N/A')}")
            elif screen_name == "License":
                self.current_screen_frame = LicenseScreen(self.content_area, self.hardware_manager, self.license_manager, app_shell=self)
                if hasattr(self.current_screen_frame, 'start_auto_load'):
                    self.current_screen_frame.start_auto_load()
            elif screen_name == "FAQ / Help":
                self.current_screen_frame = FaqScreen(self.content_area)

            if self.current_screen_frame:
                # CORREÇÃO: Remove todos os widgets do content_area antes de adicionar o novo módulo
                # Isso garante que apenas um módulo fique visível por vez
                try:
                    for widget in self.content_area.winfo_children():
                        try:
                            widget.pack_forget()
                        except:
                            pass
                        try:
                            widget.grid_forget()
                        except:
                            pass
                except:
                    pass
                
                # Configura geometry manager baseado no módulo
                if screen_name in ["Threshold", "Simulator", "Population"]:
                    # Esses módulos usam grid() internamente
                    try:
                        # Configura content_area para usar grid
                        self.content_area.grid_rowconfigure(0, weight=1)
                        self.content_area.grid_columnconfigure(0, weight=1)
                        # Adiciona o módulo com grid
                        self.current_screen_frame.grid(row=0, column=0, sticky="nsew")
                        fc_log("app_shell", f"[OK] {screen_name}: Adicionado à tela com grid()")
                    except Exception as e:
                        # Fallback para pack se grid falhar
                        fc_log("app_shell", f"[AVISO] Grid falhou para {screen_name}, tentando pack: {e}")
                        try:
                            self.current_screen_frame.pack(fill="both", expand=True)
                            fc_log("app_shell", f"[OK] {screen_name}: Adicionado à tela com pack()")
                        except:
                            pass
                else:
                    # Outros módulos usam pack()
                    try:
                        self.current_screen_frame.pack(fill="both", expand=True)
                        fc_log("app_shell", f"[OK] {screen_name}: Adicionado à tela com pack()")
                    except Exception as pack_err:
                        # Fallback para grid se pack falhar
                        fc_log("app_shell", f"[AVISO] Pack falhou para {screen_name}, tentando grid: {pack_err}")
                        try:
                            self.content_area.grid_rowconfigure(0, weight=1)
                            self.content_area.grid_columnconfigure(0, weight=1)
                            self.current_screen_frame.grid(row=0, column=0, sticky="nsew")
                            fc_log("app_shell", f"[OK] {screen_name}: Adicionado à tela com grid()")
                        except:
                            pass
                
                self.current_screen_name = screen_name
                
                # FORÇA atualização da interface para garantir que o módulo apareça
                self.content_area.update_idletasks()
                self._invoke_tab_enter_hook(self.current_screen_frame)

        except Exception as e:
            import traceback
            error_info = traceback.format_exc()
            messagebox.showerror(t('app_shell.error_module_debug'), t('app_shell.error_module_debug_msg').format(module=screen_name, traceback=error_info), parent=self.master)
    
    def toggle_fullscreen(self, event=None):
        self.is_fullscreen = not self.is_fullscreen
        self.master.attributes("-fullscreen", self.is_fullscreen)

    def exit_fullscreen(self, event=None):
        if self.is_fullscreen:
            self.toggle_fullscreen()

    def _check_hardware_availability(self):
        """Verifica se o hardware (reader) está disponível e conectado na porta COM."""
        try:
            if self.com_port is None:
                fc_log("app_shell", "[AVISO] Nenhuma porta COM detectada - hardware não disponível")
                return False

            dll_name = "UHFRFID.dll"
            if hasattr(sys, '_MEIPASS'):
                dll_path = os.path.join(sys._MEIPASS, dll_name)
            else:
                dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), dll_name)
                if not os.path.exists(dll_path):
                    dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'core', dll_name)
            if not os.path.exists(dll_path):
                fc_log("app_shell", "[AVISO] DLL UHFRFID.dll não encontrada - hardware não disponível")
                return False

            rfid_sdk = ctypes.CDLL(dll_path)
            rfid_sdk.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]
            rfid_sdk.UHF_RFID_Open.restype = ctypes.c_int
            rfid_sdk.UHF_RFID_Close.argtypes = [ctypes.c_ubyte]
            rfid_sdk.UHF_RFID_Close.restype = ctypes.c_int

            # Pequeno delay após o reset inicial para a porta estar livre
            time.sleep(0.15)
            max_attempts = 3
            for attempt in range(max_attempts):
                result = rfid_sdk.UHF_RFID_Open(self.com_port, 115200)
                if result == 0:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    fc_log("app_shell", f"[OK] Hardware detectado e respondendo na porta COM{self.com_port}")
                    return True
                if attempt < max_attempts - 1:
                    time.sleep(0.2)
            fc_log("app_shell", f"[AVISO] Hardware nao responde na porta COM{self.com_port} após {max_attempts} tentativas - modo browser")
            return False
        except Exception as e:
            fc_log("app_shell", f"[AVISO] Erro ao verificar hardware: {e} - modo browser")
            return False


def _get_app_icon_path():
    """Retorna o caminho para o ícone FastChecker (janela e exe). Funciona em dev e no executável compilado."""
    if getattr(sys, 'frozen', False):
        base = os.path.dirname(sys.executable)
    else:
        base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    return os.path.join(base, 'assets', 'icons', 'FastChecker.ico')


def _get_app_icon_paths():
    """Retorna lista de caminhos a tentar para o ícone (ico, png, ou fasttag_logo como no FC III)."""
    if getattr(sys, 'frozen', False):
        base = os.path.dirname(sys.executable)
    else:
        base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    return [
        os.path.join(base, 'assets', 'icons', 'FastChecker.ico'),
        os.path.join(base, 'assets', 'icons', 'FastChecker.png'),
        os.path.join(base, 'assets', 'images', 'fasttag_logo.png'),
    ]


def _set_app_icon(root):
    """Define o ícone da janela (barra de título e taskbar). Tenta ficheiros em assets; se não existirem, usa o logo embutido."""
    try:
        for path in _get_app_icon_paths():
            if not os.path.isfile(path):
                continue
            if path.lower().endswith('.ico'):
                root.iconbitmap(path)
                return
            if path.lower().endswith('.png'):
                img = Image.open(path)
                photo = ImageTk.PhotoImage(img)
                root._icon_photo = photo
                root.iconphoto(True, photo)
                return
        # Fallback: usar logo embutido (mesmo do header) para ícone da janela e da taskbar
        logo_image = get_logo_image()
        if logo_image is not None:
            size = 48
            logo_image = logo_image.resize((size, size), Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(logo_image)
            root._icon_photo = photo
            root.iconphoto(True, photo)
    except Exception as e:
        fc_log("app_shell", f"[AVISO] Ícone da aplicação não definido: {e}")


def run_app():
    try:
        from core.console_stdio import configure_stdio_safe
        configure_stdio_safe()
    except Exception:
        pass
    root = tk.Tk()
    _set_app_icon(root)
    try:
        app = AppShell(master=root)
        # Verifica se a aplicação foi inicializada corretamente
        if hasattr(app, 'com_port'):
            fc_log("app_shell", f"[OK] Aplicacao inicializada com porta COM{app.com_port}")
            app.mainloop()
        else:
            fc_log("app_shell", "[AVISO] Aplicacao nao inicializada corretamente, mas mantendo janela aberta")
            # Mantém a janela aberta mesmo com erro
            root.mainloop()
    except Exception as e:
        fc_log("app_shell", f"[ERRO] Erro na inicializacao da aplicacao: {e}")
        import traceback
        traceback.print_exc()
        # Cria uma janela de erro simples
        error_label = tk.Label(root, text=f"Erro na inicialização:\n{str(e)}", fg="red")
        error_label.pack(pady=20)
        root.mainloop()


if __name__ == '__main__':
    run_app()