# app_shell.py
# Versão software: 3.1.3 (FastChecker II — ver version_config.py)

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.3"
    SOFTWARE_INFO = {'software': '3.1.3', 'name': 'FastChecker II', 'version': '3.1.3'}

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()