"""
Módulo Population - FastChecker II
Interface para testes de população de tags RFID
"""
import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog
import os
import sys
import ctypes
import threading
import time
import struct
from datetime import datetime
import numpy as np
import pandas as pd
import json
import traceback
from typing import Optional
import pickle

try:
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    MATPLOTLIB_OK = True
except ImportError:
    MATPLOTLIB_OK = False

# Importa configuração de versão centralizada
try:
    from .version_config import get_software_info
    SOFTWARE_INFO = get_software_info()
except ImportError:
    SOFTWARE_INFO = {'software': '3.1.2', 'name': 'FastChecker II', 'version': '3.1.2'}

# Importa componentes do threshold para reutilizar
from .threshold_database import ThresholdDatabase
from .populacao_database import PopulacaoDatabase
from .register_tags_popup import RegisterTagsPopup
from .rename_popup import RenamePopup
from .delete_confirmation_popup import DeleteConfirmationPopup
from .export_popup import ExportPopup
from .state_persistence import StatePersistence
from .i18n import get_translator, t
from .embedded_images import get_risk_image
from .shell_open import open_path_with_default_app

# --- Bloco de Controle de Hardware ---
DLL_NAME = "UHFRFID.dll"
try:
    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)
    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
    HARDWARE_AVAILABLE = True
    try:
        print("Sistema RFID inicializado - Modulo Populacao")
    except Exception:
        pass
except (OSError, AttributeError) as e:
    rfid_sdk = None
    HARDWARE_AVAILABLE = False
    try:
        print(f"Hardware RFID nao disponivel - Modulo Populacao: {e}")
    except Exception:
        pass

# Constantes RFID
RFID_CMD_INV_TAG = 0x80
RFID_CMD_SET_TXPOWER = 0x10
RFID_CMD_SET_FREQ_TABLE = 0x14
RFID_CMD_STOP_INVENTORY = 0x8C
RFID_CMD_SET_REGION = 0x2C
RFID_CMD_GET_TEMPERATURE = 0x34
RFID_CMD_GET_PORT_LOSS = 0x32
# self.com_port = 4 ### ALTERADO: Removido - porta agora é detectada dinamicamente
BAUD_RATE = 115200
RFID_CMD_SET_SOFTRESET = 0x68
TEMPERATURE_LIMIT = 50.0
REFLECTED_POWER_LIMIT = 10.0  # dBm - Potência refletida máxima aceitável

# Tabela para conversão ADC -> RSSI
RxAdcTable = [
    0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0005, 0x0007, 0x000B, 0x0010, 0x0116, 0x011D, 0x0126,
    0x0131, 0x013E, 0x024C, 0x0260, 0x0374, 0x048B, 0x05A5, 0x06C3, 0x08E6, 0x09FF, 0x0BFF, 0x0EFF,
    0x10FF, 0x14FF, 0x17FF, 0x1CFF, 0x21FF, 0x26FF, 0x2DFF, 0x34FF, 0x3CFF, 0x46FF, 0x50FF, 0x5DFF,
    0x6AFF, 0x7AFF, 0x8BFF, 0x9EFF, 0xB4FF, 0xCCFF, 0xE7FF, 0xFFFF
]

class TagInfo:
    def __init__(self, epc, rssi):
        self.epc = epc
        self.rssi = rssi

class PopulacaoModule(tk.Frame):
    # Arquivos de persistência
    # Usa caminho absoluto para persistência (evita perder dados quando o CWD muda).
    # Ex.: <fastChecker_python>/data
    PERSISTENCE_DIR = os.path.join(
        os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
        "data",
    )
    TAGS_FILE = os.path.join(PERSISTENCE_DIR, "populacao_tags.json")
    PARAMS_FILE = os.path.join(PERSISTENCE_DIR, "populacao_params.json")
    TEST_DATA_FILE = os.path.join(PERSISTENCE_DIR, "populacao_test_data.json")
    SELECTED_TAGS_FILE = os.path.join(PERSISTENCE_DIR, "populacao_selected_tags.json")
    UI_STATE_FILE = os.path.join(PERSISTENCE_DIR, "populacao_ui_state.json")
    TABLE_STATE_FILE = os.path.join(PERSISTENCE_DIR, "populacao_table_state.json")
    GRAPH_STATE_FILE = os.path.join(PERSISTENCE_DIR, "populacao_graph_state.json")
    
    def __init__(self, parent, app_shell=None, com_port=4):
        super().__init__(parent)
        print("🔍 DEBUG: Iniciando PopulacaoModule.__init__")
        self.parent = parent
        self.app_shell = app_shell
        self._com_port_fallback = com_port  ### FALLBACK: Usado apenas se app_shell não estiver disponível
        self.com_port_lock = threading.Lock()
        # CORREÇÃO: Usa banco de dados separado para População
        self.database = PopulacaoDatabase()
        # Mantém referência ao banco do Threshold para importação/exportação
        self.threshold_database = ThresholdDatabase()
        
        # CORREÇÃO: Inicializa port_manager se app_shell estiver disponível
        if self.app_shell and hasattr(self.app_shell, 'port_manager'):
            self.port_manager = self.app_shell.port_manager
            print(f"✅ Population: Port_manager inicializado via app_shell no __init__")
        else:
            self.port_manager = None
            print(f"⚠️ Population: Port_manager não disponível no __init__, aguardando...")
        
        # --- INTEGRAÇÃO COM FASTCHECKER ---
        self.license_limits = self._get_license_limits()
        print(f"🔍 Population: __init__ - license_limits iniciais: {self.license_limits}")
        self.module_name = "Population"  # Para controle de teste ativo
        
        # Sistema de tradução
        self.translator = get_translator()
        self._widget_refs = {}
        self.translator.add_language_change_listener(self._on_language_changed)
        
        # --- Sistema de Persistência de Estado ---
        self.state_persistence = StatePersistence()
        self.state_changed = False
        
        # Cria diretório de persistência se não existir
        self._ensure_persistence_dir()
        
        # --- Variáveis da Interface ---
        self.test_in_progress = False
        self.global_test_cancelled = False
        self.active_test_thread = None
        
        # --- Gerenciamento de Tags ---
        self.selected_tags = set()
        self.tags_data = []
        self.test_history = []
        
        # --- Inventário Contínuo ---
        self.continuous_search_running = False
        self.continuous_search_thread = None
        self.continuous_search_cancelled = False
        self.live_tags = {}  # Tags detectadas em tempo real
        self.search_count = 0
        self.inventory_stats = {
            'total_scans': 0,
            'tags_found': 0,
            'last_scan_time': None,
            'scan_rate': 0.0
        }
        
        
        # --- Organização das Variáveis da Interface ---
        self.action_var = tk.StringVar(value=t('population.select_an_action'))
        
        # --- Parâmetros de Teste ---
        self.frequency_var = tk.StringVar(value="")
        self.power_var = tk.StringVar(value="")
        self.distance_var = tk.StringVar(value="")
        self.attenuator_var = tk.StringVar(value="")
        self.description_var = tk.StringVar(value="")
        
        # --- Dados do Teste ---
        self.test_data = []
        self.start_time = None
        self.end_time = None
        self.first_detection_registry = {}  # Registra primeira detecção de cada tag por potência
        self.power_epcs_map = {}  # Mapeia potência -> lista de EPCs detectados pela primeira vez
        
        # --- Tags detectadas permanentemente (para persistência de cores) ---
        self.detected_tags_permanent = set()
        self.DETECTED_TAGS_FILE = os.path.join(self.PERSISTENCE_DIR, "detected_tags.json")
        
        # --- Tags NÃO lidas (persistência entre sessões) ---
        self.not_read_tags_permanent = set()
        self.NOT_READ_TAGS_FILE = os.path.join(self.PERSISTENCE_DIR, "not_read_tags.json")
        
        # --- Variáveis de controle de ordenação da tabela ---
        self.current_sort_column = None
        self.current_sort_reverse = False
        
        # --- Estado da Interface para Persistência ---
        self.ui_state = {
            'table_scroll_position': 0,
            'table_selection': [],
            'graph_zoom': {'xlim': None, 'ylim': None},
            'graph_view': {'xlim': None, 'ylim': None},
            'window_geometry': None,
            'last_action': None,
            'interface_ready': False
        }
        
        # --- Controle de Auto-save ---
        self.auto_save_enabled = True
        self.auto_save_interval = 30  # segundos
        self.last_auto_save = None
        self.auto_save_timer = None
        
        # --- Configuração da Interface ---
        try:
            print("🔍 DEBUG: Iniciando setup_ui")
            self.setup_ui()
            print("✅ DEBUG: setup_ui concluído")
        except Exception as e:
            print(f"❌ DEBUG: Erro em setup_ui: {e}")
            import traceback
            traceback.print_exc()
        
        # --- Carrega Dados Persistidos (após criar a interface) ---
        try:
            print("🔍 DEBUG: Chamando _load_persisted_data")
            self._load_persisted_data()
            print("✅ DEBUG: _load_persisted_data concluído")
        except Exception as e:
            print(f"❌ DEBUG: Erro em _load_persisted_data: {e}")
            import traceback
            traceback.print_exc()
        
        # --- Carrega Apelidos das Tags ---
        self._load_tag_apelidos()
        
        # --- Carrega Tags Detectadas Permanentemente ---
        print("🔄 Inicializando - carregando tags detectadas permanentemente...")
        self._load_detected_tags()
        print(f"🔄 Inicialização completa - detected_tags_permanent: {len(self.detected_tags_permanent)} tags")
        
        # --- Carrega Tags NÃO lidas persistidas (apenas carrega para memória, não adiciona na tabela ainda) ---
        try:
            if os.path.exists(self.NOT_READ_TAGS_FILE):
                with open(self.NOT_READ_TAGS_FILE, 'r', encoding='utf-8') as f:
                    _nr = json.load(f)
                    self.not_read_tags_permanent = set(_nr.get('not_read_tags', []))
                print(f"📂 Tags NÃO lidas carregadas: {len(self.not_read_tags_permanent)}")
                # NÃO adiciona na tabela aqui - será feito em _load_persisted_data
        except Exception as _e_load_nr:
            print(f"⚠️ Erro ao carregar tags NÃO lidas: {_e_load_nr}")
        
        # Atualiza gráfico 3D com as cores persistentes
        self.after(1000, self._update_3d_with_persistent_colors)
        
        # --- Carrega Estado Anterior ---
        self._load_previous_state()
        
        # Configura salvamento automático ao fechar
        self.bind('<Destroy>', self._on_destroy)
        
        # Inicia auto-save
        self._start_auto_save()
        
        # Marca interface como pronta
        self.ui_state['interface_ready'] = True
        
        # CORREÇÃO: Reconfigura tooltip após inicialização completa
        self.after(500, self._setup_bar_tooltip)
        
        print("✅ Módulo Population inicializado")
        
        # Garante que a mensagem de 'modo browser' seja aplicada na inicialização
        try:
            self._update_ui_for_license_status()
        except Exception as _e:
            print(f"⚠️ Population: Falha ao aplicar estado inicial de licença: {_e}")
        
        # Atualiza idioma após criar a interface
        self.after(150, self.refresh_language)
        
        # REQUISITOS: Captura porta + Reset ao entrar
        self._initialize_hardware_on_enter()

    def _initialize_hardware_on_enter(self):
        """REQUISITOS 1+4: Captura porta e reseta hardware ao entrar no módulo"""
        if not HARDWARE_AVAILABLE or not rfid_sdk:
            return
        
        try:
            print(f"🔧 {self.module_name}: Inicializando hardware...")
            
            # REQUISITO 1: Abre porta (captura)
            if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) == 0:
                print(f"✅ {self.module_name}: Porta COM{self.com_port} capturada")
                
                # REQUISITO 3: Para comandos ativos
                out_buf = ctypes.create_string_buffer(64)
                out_len = ctypes.c_uint(0)
                rfid_sdk.UHF_RFID_Set(RFID_CMD_STOP_INVENTORY, None, 0, out_buf, ctypes.byref(out_len))
                
                # Fecha porta (será reaberta quando necessário)
                rfid_sdk.UHF_RFID_Close(self.com_port)
                
                print(f"✅ {self.module_name}: Hardware inicializado (sem bip)")
            else:
                print(f"⚠️ {self.module_name}: Falha ao capturar porta")
                
        except Exception as e:
            print(f"⚠️ {self.module_name}: Erro ao inicializar hardware: {e}")

    @property
    def com_port(self):
        """
        Propriedade que retorna a porta COM atual.
        SEMPRE busca a porta do app_shell para garantir que está atualizada.
        """
        if self.app_shell and hasattr(self.app_shell, 'com_port'):
            return self.app_shell.com_port
        return self._com_port_fallback

    def get_temperature(self):
        """Lê a temperatura do hardware RFID"""
        if not HARDWARE_AVAILABLE or not rfid_sdk:
            return None
        try:
            output_buffer = ctypes.create_string_buffer(64)
            output_len = ctypes.c_uint(0)
            status = rfid_sdk.UHF_RFID_Set(RFID_CMD_GET_TEMPERATURE, None, 0, output_buffer, ctypes.byref(output_len))
            if status == 0 and output_len.value >= 3:
                temp_val = struct.unpack('>h', output_buffer.raw[1:3])[0]
                return temp_val / 100.0
        except Exception as e:
            print(f"⚠️ Erro ao ler temperatura: {e}")
        return None

    def get_reflected_power(self):
        """Lê a Potência Refletida (dBm) do sistema irradiante via RFID_CMD_GET_PORT_LOSS.

        Alinhado ao padrão do Threshold/FastSurance:
        - Usa port_manager centralizado quando disponível (evita conflito de porta).
        - Só faz abertura direta de porta como fallback protegido por lock.
        """
        if not HARDWARE_AVAILABLE or not rfid_sdk:
            print("⚠️ Population: Hardware não disponível para ler potência refletida")
            return None

        try:
            # Caminho principal: usa port_manager se estiver configurado
            if hasattr(self, "port_manager") and self.port_manager:

                def _get_reflected_power_operation():
                    output_buffer = ctypes.create_string_buffer(64)
                    output_len = ctypes.c_uint(0)

                    status = rfid_sdk.UHF_RFID_Set(
                        RFID_CMD_GET_PORT_LOSS,
                        b"",
                        0,
                        output_buffer,
                        ctypes.byref(output_len),
                    )
                    print(
                        f"🔍 Population: Status comando 0x32 (via port_manager): "
                        f"{status}, Tamanho resposta: {output_len.value}"
                    )

                    if status == 0 and output_len.value >= 3:
                        adc_value = struct.unpack(">H", output_buffer.raw[1:3])[0]
                        reflected_power_dbm = -25 + next(
                            (i for i, val in enumerate(RxAdcTable) if adc_value <= val),
                            len(RxAdcTable) - 1,
                        )
                        print(
                            f"📊 Population: ADC={adc_value}, "
                            f"Potência Refletida={reflected_power_dbm:.1f} dBm"
                        )
                        return reflected_power_dbm

                    print(
                        "⚠️ Population: Leitura de Potência Refletida retornou "
                        f"status={status} ou buffer insuficiente"
                    )
                    return None

                # Usa o nome do módulo para identificação no port_manager
                module_name = getattr(self, "module_name", "Population")
                result = self.port_manager.execute_with_port(
                    _get_reflected_power_operation, module_name
                )
                if result is not None:
                    return result

            # Fallback: abre porta própria usando lock local
            print(
                f"🔍 Population: Abrindo porta COM{self.com_port} para ler "
                "potência refletida (fallback)"
            )
            with self.com_port_lock:
                if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) != 0:
                    print(f"❌ Population: Falha ao abrir porta COM{self.com_port}")
                    return None

                try:
                    output_buffer = ctypes.create_string_buffer(64)
                    output_len = ctypes.c_uint(0)
                    status = rfid_sdk.UHF_RFID_Set(
                        RFID_CMD_GET_PORT_LOSS,
                        b"",
                        0,
                        output_buffer,
                        ctypes.byref(output_len),
                    )
                    print(
                        f"🔍 Population: Status comando 0x32 (fallback): "
                        f"{status}, Tamanho resposta: {output_len.value}"
                    )

                    if status == 0 and output_len.value >= 3:
                        adc_value = struct.unpack(">H", output_buffer.raw[1:3])[0]
                        reflected_power_dbm = -25 + next(
                            (i for i, val in enumerate(RxAdcTable) if adc_value <= val),
                            len(RxAdcTable) - 1,
                        )
                        print(
                            f"📊 Population: ADC={adc_value}, "
                            f"Potência Refletida={reflected_power_dbm:.1f} dBm"
                        )
                        return reflected_power_dbm

                    print(
                        "⚠️ Population: Leitura de Potência Refletida (fallback) "
                        f"retornou status={status} ou buffer insuficiente"
                    )
                finally:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    print(f"🔍 Population: Porta COM{self.com_port} fechada (fallback)")

        except Exception as e:
            print(f"⚠️ Population: Erro ao ler Potência Refletida: {e}")
        return None

    def _on_language_changed(self, old_language=None, new_language=None):
        """Callback quando o idioma é alterado"""
        try:
            self.refresh_language()
        except Exception as e:
            print(f"⚠️ Erro ao atualizar idioma no Population: {e}")
    
    def refresh_language(self):
        """Atualiza todos os textos da interface quando o idioma é alterado"""
        try:
            # Atualiza títulos de frames
            if 'tags_frame' in self._widget_refs:
                self._widget_refs['tags_frame'].config(text=t('population.registered_tags'))
            if 'test_frame' in self._widget_refs:
                self._widget_refs['test_frame'].config(text=t('population.test_management'))
            if 'params_frame' in self._widget_refs:
                self._widget_refs['params_frame'].config(text=t('population.test_config'))
            
            # Atualiza labels
            if 'freq_label' in self._widget_refs:
                self._widget_refs['freq_label'].config(text=t('population.frequency_mhz'))
            if 'power_label' in self._widget_refs:
                self._widget_refs['power_label'].config(text=t('population.power_max_dbm'))
            if 'dist_label' in self._widget_refs:
                self._widget_refs['dist_label'].config(text=t('population.distance_m'))
            if 'step_label' in self._widget_refs:
                self._widget_refs['step_label'].config(text=t('population.power_step_db'))
            if 'desc_label' in self._widget_refs:
                self._widget_refs['desc_label'].config(text=t('population.description'))
            
            # Atualiza botões
            if 'save_test_button' in self._widget_refs:
                self._widget_refs['save_test_button'].config(text=t('population.save_test'))
            if 'import_test_button' in self._widget_refs:
                self._widget_refs['import_test_button'].config(text=t('population.import_test'))
            if 'clear_test_button' in self._widget_refs:
                self._widget_refs['clear_test_button'].config(text=t('population.clear_test'))
            if 'start_button' in self._widget_refs:
                self._widget_refs['start_button'].config(text=t('population.start_test'))
            if 'stop_button' in self._widget_refs:
                self._widget_refs['stop_button'].config(text=t('population.stop'))
            if 'pdf_report_button' in self._widget_refs:
                self._widget_refs['pdf_report_button'].config(text=t('population.pdf_report'))
            
            # Atualiza dropdown de ações e ajusta largura para acomodar textos
            if 'action_dropdown' in self._widget_refs:
                simplified_actions = [
                    t('population.register_new_tags'),
                    t('population.import_tag_json'),
                    t('population.select_all_tags'),
                    t('population.deselect_all_tags'),
                    t('population.erase_selected_tags'),
                    t('population.save_selected_tags')
                ]
                current_selection = self.action_var.get()
                self._widget_refs['action_dropdown'].config(values=simplified_actions)
                # Ajusta largura do dropdown para acomodar textos em inglês
                dropdown_width = 16 if hasattr(self, 'small_screen') and self.small_screen else 22
                self._widget_refs['action_dropdown'].config(width=dropdown_width)
                # Se o valor atual não está nas novas opções, reseta para padrão
                if current_selection not in simplified_actions:
                    self.action_var.set(t('population.select_an_action'))
            
            # Atualiza headings da treeview e ajusta largura da coluna para EPCs completos
            if hasattr(self, 'tags_tree'):
                self.tags_tree.heading('tag', text=t('population.tag_epc'))
                # Ajusta largura da coluna para garantir que EPCs completos sejam visíveis
                self.tags_tree.column('tag', width=200, minwidth=180, stretch=True)
            
            # Contadores: não resetar para zero — refresh_language corre após update_tags_count
            # e deve refletir os totais reais do banco e de self.selected_tags
            if hasattr(self, 'update_tags_count'):
                self.update_tags_count()
            
            # Atualiza título da tabela de dados
            if hasattr(self, 'table_frame'):
                for widget in self.table_frame.winfo_children():
                    if isinstance(widget, tk.Frame):
                        for label in widget.winfo_children():
                            if isinstance(label, tk.Label) and label.cget('text') and '🔍' not in label.cget('text'):
                                if 'Dados do Teste' in label.cget('text') or 'Test Data' in label.cget('text'):
                                    label.config(text=t('population.test_data_title'))
            
            # Atualiza headings da tabela de histórico
            if hasattr(self, 'tree') and hasattr(self, 'column_translations'):
                columns = self.tree['columns']
                for col in columns:
                    if col in self.column_translations:
                        col_text = t(self.column_translations[col])
                        self.tree.heading(col, text=f'{col_text} ↕', command=lambda c=col: self.sort_treeview(c))
            
            # Atualiza gráficos (título e eixos)
            if MATPLOTLIB_OK and hasattr(self, 'ax'):
                self.ax.set_title(t('population.graph_title'), fontsize=12)
                self.ax.set_xlabel(t('population.graph_xlabel'), fontsize=10)
                self.ax.set_ylabel(t('population.graph_ylabel'), fontsize=10)
                
                # Atualiza legendas
                lines = self.ax.get_lines()
                for line in lines:
                    label = line.get_label()
                    if label == 'Progresso da Potência' or label == 'Power Progress':
                        line.set_label(t('population.progress_label'))
                    elif label == 'Tags Detectadas' or label == 'Tags Detected':
                        line.set_label(t('population.tags_detected'))
                self.ax.legend()
                
                # Redesenha o gráfico
                if hasattr(self, 'canvas'):
                    self.canvas.draw()
            
            # Atualiza gráfico 3D
            if MATPLOTLIB_OK and hasattr(self, 'ax_3d'):
                self.ax_3d.set_title(t('population.graph_3d_title'), fontsize=12)
                if hasattr(self, 'canvas_3d'):
                    self.canvas_3d.draw()
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar idioma no Population: {e}")
            import traceback
            traceback.print_exc()
    
    def destroy(self):
        """REQUISITOS 2+3+4: Libera porta, para comandos, reseta ao sair"""
        try:
            print(f"🔄 {self.module_name}: Iniciando cleanup...")
            
            # Cancela testes
            if hasattr(self, 'test_in_progress') and self.test_in_progress:
                self.global_test_cancelled = True
                self.test_in_progress = False
            
            if hasattr(self, 'continuous_search_running') and self.continuous_search_running:
                self.continuous_search_running = False
            
            # Remove listener de mudança de idioma
            if hasattr(self, 'translator') and self.translator:
                try:
                    self.translator.remove_language_change_listener(self._on_language_changed)
                except:
                    pass
            
            # REQUISITOS 2+3+4: Para comandos, reseta, libera porta
            if HARDWARE_AVAILABLE and rfid_sdk:
                try:
                    # Abre porta para enviar comandos
                    if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) == 0:
                        out_buf = ctypes.create_string_buffer(64)
                        out_len = ctypes.c_uint(0)
                        
                        # REQUISITO 3: Para comandos ativos
                        rfid_sdk.UHF_RFID_Set(RFID_CMD_STOP_INVENTORY, None, 0, out_buf, ctypes.byref(out_len))
                    
                    # REQUISITO 2: Fecha/libera porta
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    print(f"✅ {self.module_name}: Comandos parados, porta liberada (sem bip)")
                except Exception as e:
                    print(f"⚠️ {self.module_name}: Erro: {e}")
            
            print(f"✅ {self.module_name}: Cleanup concluído")
        except:
            pass
        
        try:
            super().destroy()
        except:
            pass

    # --- LICENÇA: Helpers de validação ---
    def _is_frequency_allowed_by_license(self, frequency_mhz: float) -> bool:
        try:
            return self._validate_frequency_against_license(float(frequency_mhz))
        except Exception:
            return False

    def _clamp_power_to_license(self, power_dbm: float) -> float:
        try:
            min_power = float(self.license_limits.get('min_power', 5))
            max_power = float(self.license_limits.get('max_power', 25))
            if power_dbm < min_power:
                print(f"⚠️ Potência {power_dbm} dBm abaixo do mínimo de licença ({min_power} dBm). Ajustando.")
                return min_power
            if power_dbm > max_power:
                print(f"⚠️ Potência {power_dbm} dBm acima do máximo de licença ({max_power} dBm). Ajustando.")
                return max_power
            return power_dbm
        except Exception:
            return power_dbm

    def _validate_frequency_against_license(self, freq: float) -> bool:
        """Valida frequência contra faixas permitidas e faixas excluídas da licença."""
        print(f"🔍 Population VALIDATION: Validando frequência {freq} MHz")
        print(f"🔍 Population VALIDATION: license_limits = {self.license_limits}")
        
        if not self.license_limits.get('is_licensed', False):
            print(f"⚠️ Population VALIDATION: Sem licença ativa - permitindo frequência {freq} MHz")
            return True
        
        freq_ranges = self.license_limits.get('freq_ranges', [])
        excluded_ranges = self.license_limits.get('excluded_ranges', [])
        
        print(f"🔍 Population VALIDATION: freq_ranges = {freq_ranges}")
        print(f"🔍 Population VALIDATION: excluded_ranges = {excluded_ranges}")
        
        if freq_ranges:
            for range_min, range_max in freq_ranges:
                print(f"🔍 Population VALIDATION: Verificando faixa {range_min}-{range_max} MHz")
                if range_min <= freq <= range_max:
                    # Verifica faixas excluídas
                    for excl_min, excl_max in excluded_ranges:
                        print(f"🔍 Population VALIDATION: Verificando faixa excluída {excl_min}-{excl_max} MHz")
                        if excl_min <= freq <= excl_max:
                            print(f"❌ Population: Frequência {freq} MHz está na faixa EXCLUÍDA {excl_min}-{excl_max} MHz")
                            return False
                    print(f"✅ Population: Frequência {freq} MHz ACEITA na faixa {range_min}-{range_max} MHz")
                    return True
            print(f"❌ Population: Frequência {freq} MHz fora das faixas permitidas")
            return False
        else:
            # Fallback simples
            min_freq = self.license_limits.get('min_freq', 800)
            max_freq = self.license_limits.get('max_freq', 1000)
            print(f"🔍 Population VALIDATION: Usando validação simples {min_freq}-{max_freq} MHz")
            return min_freq <= freq <= max_freq

    def on_tab_switch(self):
        """Chamado quando o usuário muda para esta aba - força salvamento imediato"""
        try:
            print("🔄 Usuário mudou para aba Population - salvando estado atual")
            self._save_complete_ui_state()
        except Exception as e:
            print(f"⚠️ Erro ao salvar estado na mudança de aba: {e}")

    def on_tab_leave(self):
        """Chamado quando o usuário sai desta aba - força salvamento imediato"""
        try:
            print("🔄 Usuário saiu da aba Population - salvando estado atual")
            self._save_complete_ui_state()
        except Exception as e:
            print(f"⚠️ Erro ao salvar estado ao sair da aba: {e}")

    def _start_auto_save(self):
        """Inicia o sistema de auto-save"""
        if self.auto_save_enabled:
            self._schedule_auto_save()
            print("🔄 Auto-save iniciado (30 segundos)")

    def _schedule_auto_save(self):
        """Agenda o próximo auto-save"""
        if self.auto_save_timer:
            self.after_cancel(self.auto_save_timer)
        
        self.auto_save_timer = self.after(self.auto_save_interval * 1000, self._perform_auto_save)

    def _perform_auto_save(self):
        """Executa o auto-save"""
        try:
            if self.ui_state['interface_ready']:
                self._save_complete_ui_state()
                self.last_auto_save = datetime.now()
                print(f"💾 Auto-save executado: {self.last_auto_save.strftime('%H:%M:%S')}")
        except Exception as e:
            print(f"⚠️ Erro no auto-save: {e}")
        finally:
            # Agenda próximo auto-save
            self._schedule_auto_save()

    def _save_complete_ui_state(self):
        """Salva o estado completo da interface"""
        try:
            # Salva estado da tabela
            self._save_table_state()
            
            # Salva estado do gráfico
            self._save_graph_state()
            
            # Salva estado geral da UI
            self._save_ui_state()
            
            # Salva tags detectadas permanentemente
            self._save_detected_tags()
            
        except Exception as e:
            print(f"⚠️ Erro ao salvar estado completo da UI: {e}")

    def _save_table_state(self):
        """Salva o estado da tabela"""
        try:
            # Salva dados da tabela atual
            table_data = []
            for item in self.tree.get_children():
                values = self.tree.item(item)['values']
                table_data.append(values)
            
            table_state = {
                'sort_column': self.current_sort_column,
                'sort_reverse': self.current_sort_reverse,
                'scroll_position': self._get_table_scroll_position(),
                'selection': self._get_table_selection(),
                'data_count': len(self.test_data),
                'table_data': table_data,  # Dados atuais da tabela
                'last_update': datetime.now().isoformat()
            }
            
            with open(self.TABLE_STATE_FILE, 'w', encoding='utf-8') as f:
                json.dump(table_state, f, indent=2, ensure_ascii=False)
                
        except Exception as e:
            print(f"⚠️ Erro ao salvar estado da tabela: {e}")

    def _save_graph_state(self):
        """Salva o estado do gráfico"""
        try:
            if MATPLOTLIB_OK and hasattr(self, 'ax'):
                # Verifica se o grid está habilitado de forma segura
                grid_enabled = False
                try:
                    # Tenta verificar se o grid está ativo
                    if hasattr(self.ax, 'grid_on'):
                        grid_enabled = self.ax.grid_on
                    else:
                        # Fallback: verifica se há linhas de grid visíveis
                        grid_enabled = len(self.ax.get_xgridlines()) > 0 or len(self.ax.get_ygridlines()) > 0
                except:
                    grid_enabled = False
                
                # Salva dados do gráfico para reconstrução
                graph_data = []
                try:
                    for line in self.ax.get_lines():
                        x_data = line.get_xdata()
                        y_data = line.get_ydata()
                        if len(x_data) > 0 and len(y_data) > 0:
                            # Converte arrays numpy para listas Python
                            x_list = x_data.tolist() if hasattr(x_data, 'tolist') else list(x_data)
                            y_list = y_data.tolist() if hasattr(y_data, 'tolist') else list(y_data)
                            
                            graph_data.append({
                                'x': x_list,
                                'y': y_list,
                                'color': line.get_color(),
                                'linewidth': line.get_linewidth(),
                                'label': line.get_label()
                            })
                            print(f"📈 Linha salva: {len(x_list)} pontos, cor: {line.get_color()}")
                except Exception as e:
                    print(f"⚠️ Erro ao salvar linhas do gráfico: {e}")
                
                # Salva dados das barras se existirem
                bar_data = []
                try:
                    patches = self.ax.patches
                    print(f"🔍 Debug: Encontradas {len(patches)} barras no gráfico")
                    for i, patch in enumerate(patches):
                        try:
                            # Converte cor para formato compatível
                            face_color = patch.get_facecolor()
                            if hasattr(face_color, '__iter__') and len(face_color) >= 3:
                                # Se é um array de cores (RGBA), converte para string
                                color_str = f"#{int(face_color[0]*255):02x}{int(face_color[1]*255):02x}{int(face_color[2]*255):02x}"
                            else:
                                # Se é uma string, usa diretamente
                                color_str = str(face_color)
                            
                            # CORREÇÃO: Salva a posição central da barra em vez da borda esquerda
                            bar_x = float(patch.get_x())
                            bar_width = float(patch.get_width())
                            bar_center_x = bar_x + (bar_width / 2)
                            
                            bar_info = {
                                'x': bar_center_x,  # Posição central da barra
                                'y': float(patch.get_y()),
                                'width': bar_width,
                                'height': float(patch.get_height()),
                                'color': color_str
                            }
                            bar_data.append(bar_info)
                            print(f"📊 Barra {i+1} salva: x={bar_info['x']}, altura={bar_info['height']}, cor={color_str}")
                        except Exception as e:
                            print(f"❌ Erro ao salvar barra {i+1}: {e}")
                            continue
                    print(f"✅ Total de {len(bar_data)} barras salvas com sucesso")
                except Exception as e:
                    print(f"⚠️ Erro ao salvar barras do gráfico: {e}")
                
                graph_state = {
                    'xlim': self.ax.get_xlim(),
                    'ylim': self.ax.get_ylim(),
                    'title': self.ax.get_title(),
                    'xlabel': self.ax.get_xlabel(),
                    'ylabel': self.ax.get_ylabel(),
                    'grid_enabled': grid_enabled,
                    'data_points': len(self.test_data),
                    'graph_data': graph_data,  # Dados das linhas
                    'bar_data': bar_data,      # Dados das barras
                    'last_update': datetime.now().isoformat()
                }
                
                with open(self.GRAPH_STATE_FILE, 'w', encoding='utf-8') as f:
                    json.dump(graph_state, f, indent=2, ensure_ascii=False)
                    
        except Exception as e:
            print(f"⚠️ Erro ao salvar estado do gráfico: {e}")

    def _save_ui_state(self):
        """Salva o estado geral da interface"""
        try:
            ui_state = {
                'frequency': self.frequency_var.get(),
                'power': self.power_var.get(),
                'distance': self.distance_var.get(),
                'attenuator': self.attenuator_var.get(),
                'description': self.description_var.get(),
                'action_selected': self.action_var.get(),
                'test_in_progress': self.test_in_progress,
                'continuous_search_running': self.continuous_search_running,
                'selected_tags_count': len(self.selected_tags) if hasattr(self, 'selected_tags') and isinstance(self.selected_tags, (set, list, tuple)) else 0,
                'test_data_count': len(self.test_data),
                'live_tags_count': len(self.live_tags),
                'last_update': datetime.now().isoformat()
            }
            
            with open(self.UI_STATE_FILE, 'w', encoding='utf-8') as f:
                json.dump(ui_state, f, indent=2, ensure_ascii=False)
                
        except Exception as e:
            print(f"⚠️ Erro ao salvar estado da UI: {e}")

    def _get_table_scroll_position(self):
        """Obtém a posição de scroll da tabela"""
        try:
            # Obtém a posição do scroll vertical
            scrollbar = None
            for child in self.tree.winfo_children():
                if isinstance(child, ttk.Scrollbar):
                    scrollbar = child
                    break
            
            if scrollbar:
                return scrollbar.get()
            return (0.0, 1.0)
        except:
            return (0.0, 1.0)

    def _get_table_selection(self):
        """Obtém a seleção atual da tabela"""
        try:
            return [self.tree.item(item)['values'] for item in self.tree.selection()]
        except:
            return []

    def _restore_table_state(self):
        """Restaura o estado da tabela"""
        try:
            if os.path.exists(self.TABLE_STATE_FILE):
                with open(self.TABLE_STATE_FILE, 'r', encoding='utf-8') as f:
                    table_state = json.load(f)
                
                # CORREÇÃO: Só restaura dados da tabela se não houver dados carregados dos arquivos JSON
                # Isso evita sobrescrever EPCs completos com dados antigos truncados
                # Nota: `self.test_data` existe desde a inicialização (mesmo vazio),
                # então a verificação correta é baseada no conteúdo e não em `hasattr`.
                test_data_present = bool(getattr(self, 'test_data', []))
                if table_state.get('table_data') and not test_data_present:
                    # Verifica se os dados têm o número correto de colunas (8 colunas)
                    first_row = table_state['table_data'][0] if table_state['table_data'] else []
                    if len(first_row) == 8:  # EPC, Apelido, Comentario, Coordenadas, Potencia, RSSI, Frequencia, Data/Hora
                        # Limpa tabela atual
                        for item in self.tree.get_children():
                            self.tree.delete(item)
                        
                        # Recria dados da tabela
                        for row_data in table_state['table_data']:
                            self.tree.insert('', 'end', values=row_data)
                        
                        print(f"📊 Dados da tabela restaurados: {len(table_state['table_data'])} linhas")
                    else:
                        print(f"⚠️ Dados da tabela têm {len(first_row)} colunas, esperado 8. Ignorando restauração para evitar deslocamento.")
                elif test_data_present:
                    print(f"📊 Dados da tabela já carregados dos arquivos JSON - ignorando restauração de estado")
                
                # Restaura ordenação
                if table_state.get('sort_column'):
                    self.current_sort_column = table_state['sort_column']
                    self.current_sort_reverse = table_state.get('sort_reverse', False)
                    self.sort_treeview(table_state['sort_column'])
                
                # Restaura posição de scroll
                scroll_pos = table_state.get('scroll_position', (0.0, 1.0))
                self.after(100, lambda: self._set_table_scroll_position(scroll_pos))
                
                print(f"📊 Estado da tabela restaurado: {table_state.get('data_count', 0)} pontos")
            else:
                print("📊 Arquivo de estado da tabela não encontrado. Tabela será populada com dados do arquivo de teste.")
                
        except Exception as e:
            print(f"⚠️ Erro ao restaurar estado da tabela: {e}")

    def _restore_graph_state(self):
        """Restaura o estado do gráfico"""
        try:
            if os.path.exists(self.GRAPH_STATE_FILE) and MATPLOTLIB_OK and hasattr(self, 'ax'):
                with open(self.GRAPH_STATE_FILE, 'r', encoding='utf-8') as f:
                    graph_state = json.load(f)
                
                self.ax.clear()
                
                if graph_state.get('title'):
                    self.ax.set_title(graph_state['title'])
                if graph_state.get('xlabel'):
                    self.ax.set_xlabel(graph_state['xlabel'])
                if graph_state.get('ylabel'):
                    self.ax.set_ylabel(graph_state['ylabel'])
                if graph_state.get('grid_enabled'):
                    self.ax.grid(True, alpha=0.3)
                
                # A CORREÇÃO PRINCIPAL ESTÁ AQUI:
                # Ele agora procura por 'bars', que é a chave correta no seu arquivo JSON.
                bar_key_to_check = 'bars' if 'bars' in graph_state else 'bar_data'
                restored_bars_count = 0
                if graph_state.get(bar_key_to_check):
                    bar_list = graph_state[bar_key_to_check]
                    print(f"📊 Restaurando {len(bar_list)} barras do gráfico")
                    for i, bar_data in enumerate(bar_list):
                        try:
                            color = bar_data['color']
                            x_pos = float(bar_data.get('x', 0))
                            height = float(bar_data.get('height', 0))
                            width = float(bar_data.get('width', 0.4))
                            y_pos = float(bar_data.get('y', 0))

                            # CORREÇÃO: Usa a posição central diretamente (já foi salva como central)
                            # Não precisa mais ajustar pois agora salvamos a posição central
                            self.ax.bar(x_pos, height, bottom=y_pos,
                                        width=width,
                                        color=color,
                                        alpha=0.8)
                            restored_bars_count += 1
                        except Exception as e:
                            print(f"   ❌ Erro ao restaurar barra {i+1}: {e}")
                            continue
                
                if graph_state.get('xlim') and graph_state.get('ylim'):
                    self.ax.set_xlim(graph_state['xlim'])
                    self.ax.set_ylim(graph_state['ylim'])
                    # CORREÇÃO: Reconfigura os ticks usando a função padrão
                    self._setup_plot_scales()
                else:
                    self.ax.relim()
                    self.ax.autoscale_view()
                
                self.canvas.draw()
                print(f"📈 Estado do gráfico restaurado.")
                
                # Reconfigura tooltip após restaurar gráfico
                self.after(200, self._setup_bar_tooltip)
                
                return restored_bars_count
                
        except Exception as e:
            print(f"⚠️ Erro ao restaurar estado do gráfico: {e}")
        return 0

    def _set_table_scroll_position(self, position):
        """Define a posição de scroll da tabela"""
        try:
            scrollbar = None
            for child in self.tree.winfo_children():
                if isinstance(child, ttk.Scrollbar):
                    scrollbar = child
                    break
            
            if scrollbar:
                scrollbar.set(*position)
        except:
            pass

    def _get_license_limits(self):
        """Obtém limites de licença do app_shell"""
        try:
            if self.app_shell and hasattr(self.app_shell, '_calculate_license_limits'):
                # Usa cálculo completo para incluir freq_ranges e excluded_ranges
                valid_lics = ["Populacao", "População", "FastChecker"]
                limits = self.app_shell._calculate_license_limits(valid_lics)
                if limits:
                    print(f"✅ Population: Limites de licença obtidos do app_shell (com faixas)")
                    return limits
            if self.app_shell and hasattr(self.app_shell, 'license_limits'):
                return self.app_shell.license_limits
        except Exception as e:
            print(f"⚠️ Population: Falha ao obter limites completos de licença: {e}")
        return {
            'min_freq': 800, 'max_freq': 1000, 
            'min_power': 5, 'max_power': 25, 
            'is_licensed': False
        }

    def update_license_status(self, new_license_status):
        """CORREÇÃO: Atualiza o status da licença e a interface do usuário"""
        try:
            print(f"🔔 Population: Atualizando status da licença de {self.license_limits.get('is_licensed', False)} para {new_license_status}")
            
            # CORREÇÃO: Verifica se a licença é realmente válida para o módulo População
            # Mesmo que o app_shell diga que há licença, verifica se é específica para População
            if new_license_status and self.app_shell:
                try:
                    # Verifica se há licença específica para População
                    valid_lics = ["Populacao", "População", "FastChecker"]
                    license_limits = self.app_shell._calculate_license_limits(valid_lics)
                    if license_limits.get('is_licensed', False):
                        self.license_limits = license_limits
                        print(f"✅ Population: Licença válida confirmada: {license_limits.get('license_name', 'N/A')}")
                    else:
                        print("⚠️ Population: AppShell diz que há licença, mas não é válida para População")
                        new_license_status = False
                except Exception as e:
                    print(f"⚠️ Population: Erro ao verificar licença específica: {e}")
                    new_license_status = False
            
            # Atualiza o status da licença
            self.license_limits['is_licensed'] = new_license_status
            
            # Atualiza a interface
            self._update_ui_for_license_status()
            
            print(f"✅ Population: Status da licença atualizado para: {self.license_limits.get('is_licensed', False)}")
            
        except Exception as e:
            print(f"❌ Erro ao atualizar status da licença no Population: {e}")
    
    def _update_ui_for_license_status(self):
        """CORREÇÃO: Atualiza a UI baseado no status da licença"""
        is_licensed = self.license_limits.get('is_licensed', False)
        
        if is_licensed:
            print("✅ Population: Licença ativa - interface habilitada")
            if hasattr(self, 'browser_frame'):
                try:
                    self.browser_frame.grid_remove()
                except Exception:
                    self.browser_frame.pack_forget()
            # Habilita controles de teste
            self._set_test_controls_enabled(True)
        else:
            print("⚠️ Population: Modo browser - funcionalidade limitada")
            try:
                # Cria aviso dentro da janela de configuração (usa GRID, pois config_frame usa grid)
                target_parent = getattr(self, 'config_frame', self.sidebar)
                if not hasattr(self, 'browser_frame'):
                    self.browser_frame = tk.Frame(target_parent, bg='#f0f8ff', bd=0, relief='flat')
                    tk.Label(self.browser_frame, text="•", fg="blue", font=("Helvetica", 10), bg='#f0f8ff').pack(side='left', anchor='w')
                    tk.Label(self.browser_frame, text=f" {t('population.browser_mode')}", fg="blue", font=("Helvetica", 10), bg='#f0f8ff').pack(side='left', anchor='w')
                # Posiciona logo abaixo da linha "Descrição:" (row 4), então usa row 5
                try:
                    self.browser_frame.grid(row=5, column=0, columnspan=2, sticky='ew', padx=5, pady=(4, 4))
                except Exception:
                    # Fallback se grid falhar
                    self.browser_frame.pack(fill='x', pady=(5,5), padx=5)
                # Desabilita controles de teste
                self._set_test_controls_enabled(False)
            except Exception as e:
                print(f"⚠️ Population: Falha ao exibir aviso de modo browser: {e}")

    def _set_test_controls_enabled(self, enabled: bool):
        """Ativa/Desativa SOMENTE controles que acionam o reader/teste.
        Ações de browser (salvar, limpar, importar, editar) permanecem liberadas.
        """
        try:
            state_enabled = 'normal' if enabled else 'disabled'
            # Botões principais de teste
            if hasattr(self, 'start_button') and self.start_button:
                self.start_button.config(state=state_enabled)
            # O botão Stop deve permanecer disponível apenas quando teste estiver rodando
            # mas em modo browser impedimos iniciar; se não estiver rodando, pode ficar disabled
            if hasattr(self, 'stop_button') and self.stop_button:
                # Se desabilitando, sempre desabilita Stop; se habilitando, volta ao padrão
                self.stop_button.config(state='disabled' if not enabled else ('normal' if self.continuous_search_running else 'disabled'))
            # Botão Limpar Teste: desabilita enquanto houver teste em andamento
            if hasattr(self, 'clear_test_button') and self.clear_test_button:
                self.clear_test_button.config(state='disabled' if self.continuous_search_running else 'normal')
            # Não altere botões de browser: salvar, importar (mantidos)
        except Exception as e:
            print(f"⚠️ Population: Erro ao alternar estado dos controles de teste: {e}")

    def setup_ui(self):
        """Configura a interface do usuário"""
        try:
            self.dpi_scale = self.winfo_fpixels('1i') / 72.0
        except tk.TclError:
            self.dpi_scale = 1.0
        
        # Detecta resolução da tela e ajusta tamanhos
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        
        # Ajusta tamanhos mínimos baseado na resolução - LARGURA MÍNIMA FUNCIONAL
        # CORREÇÃO: Aumentado para acomodar textos em inglês e EPCs completos
        if screen_width < 1366:  # Laptop pequeno
            sidebar_min = 220  # Largura mínima funcional (tabela + padding + EPC completo)
            main_min = 750     # Máximo espaço para gráficos
            self.small_screen = True
        elif screen_width < 1920:  # Laptop médio
            sidebar_min = 240  # Largura mínima funcional
            main_min = 850     # Máximo espaço para gráficos
            self.small_screen = False
        else:  # Monitor grande
            sidebar_min = 260  # Largura mínima funcional
            main_min = 950     # Máximo espaço para gráficos
            self.small_screen = False
        
        self.grid_columnconfigure(0, weight=0, minsize=sidebar_min)
        self.grid_columnconfigure(1, weight=3, minsize=main_min)
        self.grid_rowconfigure(0, weight=1)
        
        self.setup_sidebar()
        self.setup_main_area()
        
        # Atualiza o contador de tags na inicialização
        self.after(100, self.update_tags_count)

    def setup_sidebar(self):
        """Configura a barra lateral com controles"""
        sidebar = tk.Frame(self, bg='#f8f8f8', relief='raised', bd=1)
        # Disponibiliza a sidebar para outras rotinas (ex.: modo browser)
        self.sidebar = sidebar
        sidebar.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
        sidebar.grid_rowconfigure(2, weight=2)  # Tags ocupam menos espaço
        sidebar.grid_rowconfigure(3, weight=0)  # Gerenciamento de Testes
        sidebar.grid_rowconfigure(4, weight=0)  # Parâmetros ocupam menos espaço
        sidebar.grid_rowconfigure(5, weight=0)  # Botões de controle
        
        # --- Título removido ---
        
        # --- Ações ---
        action_frame = tk.Frame(sidebar, bg='#f8f8f8')
        action_frame.grid(row=1, column=0, sticky='ew', pady=2, padx=2)
        
        simplified_actions = [
            t('population.register_new_tags'),
            t('population.import_tag_json'),
            t('population.select_all_tags'),
            t('population.deselect_all_tags'),
            t('population.erase_selected_tags'),
            t('population.save_selected_tags')
        ]
        # CORREÇÃO: Aumentado para acomodar textos em inglês
        dropdown_width = 16 if hasattr(self, 'small_screen') and self.small_screen else 22
        self.action_dropdown = ttk.Combobox(action_frame, textvariable=self.action_var, values=simplified_actions, state="readonly", width=dropdown_width)
        self.action_dropdown.pack(fill='x', pady=5)
        self.action_dropdown.bind('<<ComboboxSelected>>', self.on_action_selected)
        self._widget_refs['action_dropdown'] = self.action_dropdown
        
        # --- Indicador de Tags Registradas ---
        self.tags_count_label = tk.Label(action_frame, text=t('population.tags_count_registered_none'), 
                                       font=("Helvetica", 9), bg='#f8f8f8', fg='#2c3e50')
        self.tags_count_label.pack(pady=(0, 2))
        self._widget_refs['tags_count_label'] = self.tags_count_label
        
        # --- Indicador de Tags Selecionadas ---
        self.selected_tags_count_label = tk.Label(action_frame, text=t('population.tags_count_selected_none'), 
                                                font=("Helvetica", 9), bg='#f8f8f8', fg='#27ae60')
        self.selected_tags_count_label.pack(pady=(0, 5))
        self._widget_refs['selected_tags_count_label'] = self.selected_tags_count_label
        
        # --- Tags Registradas com Checkboxes ---
        tags_frame = tk.LabelFrame(sidebar, text=t('population.registered_tags'), labelanchor="n", padx=2, pady=2)
        tags_frame.grid(row=2, column=0, sticky='nsew', pady=2, padx=2)
        tags_frame.grid_rowconfigure(1, weight=1)
        tags_frame.grid_columnconfigure(0, weight=1)
        self._widget_refs['tags_frame'] = tags_frame
        
        # Treeview com checkboxes - altura reduzida para caber tudo
        tree_height = 8 if hasattr(self, 'small_screen') and self.small_screen else 10
        self.tags_tree = ttk.Treeview(tags_frame, columns=('checkbox', 'tag'), show='headings', height=tree_height)
        self.tags_tree.heading('checkbox', text='')
        self.tags_tree.heading('tag', text=t('population.tag_epc'))
        # Configuração de colunas com larguras otimizadas
        # CORREÇÃO: Aumentado para acomodar EPCs completos de 24 dígitos
        self.tags_tree.column('checkbox', width=25, minwidth=25, stretch=False, anchor='center')
        self.tags_tree.column('tag', width=200, minwidth=180, stretch=True, anchor='w')
        
        # Configuração de fonte muito pequena para ganhar espaço horizontal
        style = ttk.Style()
        style.configure("Tiny.Treeview", font=("Arial", 8))
        self.tags_tree.configure(style="Tiny.Treeview")
        
        # CORREÇÃO: Configura cores para tags sem coordenadas (vermelho)
        self.tags_tree.tag_configure('no_coords', foreground='red')
        self.tags_tree.tag_configure('normal', foreground='black')
        
        self.tags_tree.grid(row=1, column=0, sticky='nsew', pady=5)
        self.tags_tree.bind('<Button-1>', self.on_tree_click)
        self.tags_tree.bind('<Double-Button-1>', self.on_tags_tree_double_click)
        # Configuração de eventos - apenas duplo clique
        self.tags_tree.bind('<Double-Button-1>', self.on_tags_tree_double_click)
        self.tags_tree.bind('<Button-1>', self.on_tree_click)
        
        # Scrollbar para tags
        tags_scrollbar = ttk.Scrollbar(tags_frame, orient='vertical', command=self.tags_tree.yview)
        self.tags_tree.configure(yscrollcommand=tags_scrollbar.set)
        tags_scrollbar.grid(row=1, column=1, sticky='ns')
        
        # --- Gerenciamento de Testes ---
        test_frame = tk.LabelFrame(sidebar, text=t('population.test_management'), 
                                 labelanchor="n", padx=5, pady=5)
        test_frame.grid(row=3, column=0, sticky='ew', pady=5, padx=5)
        self._widget_refs['test_frame'] = test_frame
        
        # Botão Salvar Teste
        self.save_test_button = tk.Button(test_frame, text=t('population.save_test'), 
                                         command=self.save_test,
                                         font=("Helvetica", 9), height=1)
        self.save_test_button.pack(fill='x', pady=1)
        self._widget_refs['save_test_button'] = self.save_test_button
        
        # Botão Importar Teste
        self.import_test_button = tk.Button(test_frame, text=t('population.import_test'), 
                                           command=self.import_test,
                                           font=("Helvetica", 9), height=1)
        self.import_test_button.pack(fill='x', pady=1)
        self._widget_refs['import_test_button'] = self.import_test_button
        
        # Botão Limpar Teste
        self.clear_test_button = tk.Button(test_frame, text=t('population.clear_test'), 
                                           command=self.clear_test,
                                           font=("Helvetica", 9), height=1)
        self.clear_test_button.pack(fill='x', pady=1)
        self._widget_refs['clear_test_button'] = self.clear_test_button
        
        # --- Configuração de Teste ---
        params_frame = tk.LabelFrame(sidebar, text=t('population.test_config'), 
                                    labelanchor="n", padx=5, pady=5)
        params_frame.grid(row=4, column=0, sticky='ew', pady=5, padx=5)
        # Disponibiliza o container para outras rotinas (ex.: modo browser)
        self.config_frame = params_frame
        self._widget_refs['params_frame'] = params_frame
        
        # Frequência
        freq_label = tk.Label(params_frame, text=t('population.frequency_mhz'), bg='#f8f8f8')
        freq_label.grid(row=0, column=0, sticky='w', pady=1)
        self._widget_refs['freq_label'] = freq_label
        freq_entry = tk.Entry(params_frame, textvariable=self.frequency_var, width=10)
        freq_entry.grid(row=0, column=1, sticky='ew', pady=1, padx=(5, 0))
        freq_entry.bind('<FocusOut>', self._validate_frequency)
        
        # Potência
        power_label = tk.Label(params_frame, text=t('population.power_max_dbm'), bg='#f8f8f8')
        power_label.grid(row=1, column=0, sticky='w', pady=1)
        self._widget_refs['power_label'] = power_label
        power_entry = tk.Entry(params_frame, textvariable=self.power_var, width=10)
        power_entry.grid(row=1, column=1, sticky='ew', pady=1, padx=(5, 0))
        power_entry.bind('<FocusOut>', self._validate_power)
        
        # Distância
        dist_label = tk.Label(params_frame, text=t('population.distance_m'), bg='#f8f8f8')
        dist_label.grid(row=2, column=0, sticky='w', pady=1)
        self._widget_refs['dist_label'] = dist_label
        dist_entry = tk.Entry(params_frame, textvariable=self.distance_var, width=10)
        dist_entry.grid(row=2, column=1, sticky='ew', pady=1, padx=(5, 0))
        
        # Step de Potência
        step_label = tk.Label(params_frame, text=t('population.power_step_db'), bg='#f8f8f8')
        step_label.grid(row=3, column=0, sticky='w', pady=1)
        self._widget_refs['step_label'] = step_label
        att_entry = ttk.Combobox(params_frame, textvariable=self.attenuator_var, 
                                values=['1.0', '0.5'], state="readonly", width=8)
        att_entry.grid(row=3, column=1, sticky='ew', pady=1, padx=(5, 0))
        
        # Descrição
        desc_label = tk.Label(params_frame, text=t('population.description'), bg='#f8f8f8')
        desc_label.grid(row=4, column=0, sticky='w', pady=1)
        self._widget_refs['desc_label'] = desc_label
        desc_entry = tk.Entry(params_frame, textvariable=self.description_var, width=10)
        desc_entry.grid(row=4, column=1, sticky='ew', pady=1, padx=(5, 0))
        
        params_frame.grid_columnconfigure(1, weight=1)
        
        # Exibe/atualiza a mensagem de modo browser após todos os campos da seção
        try:
            self._update_ui_for_license_status()
        except Exception as e:
            print(f"⚠️ Population: Falha ao atualizar modo browser no final da seção: {e}")
        
        # --- Botões de Controle ---
        control_frame = tk.Frame(sidebar, bg='#f8f8f8')
        control_frame.grid(row=5, column=0, sticky='ew', pady=5, padx=5)
        
        # Botão Start Test
        self.start_button = tk.Button(control_frame, text=t('population.start_test'), 
                                     command=self.start_continuous_search,
                                     font=("Helvetica", 9), height=1)
        self.start_button.pack(fill='x', pady=1)
        self._widget_refs['start_button'] = self.start_button
        
        # Botão Stop
        self.stop_button = tk.Button(control_frame, text=t('population.stop'), 
                                    command=self.stop_continuous_search,
                                    font=("Helvetica", 9), height=1, state='disabled')
        self.stop_button.pack(fill='x', pady=1)
        self._widget_refs['stop_button'] = self.stop_button
        
        # Botão Relatório PDF
        self.pdf_report_button = tk.Button(control_frame, text=t('population.pdf_report'), 
                                          command=self.generate_pdf_report,
                                          font=("Helvetica", 9), height=1)
        self.pdf_report_button.pack(fill='x', pady=1)
        self._widget_refs['pdf_report_button'] = self.pdf_report_button
        
        

    def setup_main_area(self):
        """Configura a área principal com gráfico e tabela"""
        main_area = tk.Frame(self, bg='white')
        main_area.grid(row=0, column=1, sticky='nsew', padx=5, pady=5)
        main_area.grid_rowconfigure(0, weight=2)  # Gráfico ocupa mais espaço
        main_area.grid_rowconfigure(1, weight=1)  # Tabela ocupa menos espaço
        
        # CONFIGURAÇÃO CRÍTICA: Garantir exatamente 50% para cada coluna
        main_area.grid_columnconfigure(0, weight=1, uniform="graph")  # Gráfico atual - 50%
        main_area.grid_columnconfigure(1, weight=1, uniform="graph")  # Gráfico 3D - 50%
        
        # --- Frame do Gráfico Atual (EXATAMENTE 50% da largura) ---
        self.graph_frame = tk.Frame(main_area, bg='white', relief='ridge', bd=2)
        self.graph_frame.grid(row=0, column=0, sticky='nsew', padx=(0, 2), pady=2)
        
        if MATPLOTLIB_OK:
            self.setup_realtime_plot()
        else:
            tk.Label(self.graph_frame, text=t('population.matplotlib_unavailable'), 
                    font=("Helvetica", 12), bg='white').pack(expand=True)
        
        # --- Frame do Gráfico 3D (EXATAMENTE 50% da largura) ---
        self.graph_3d_frame = tk.Frame(main_area, bg='white', relief='ridge', bd=2)
        self.graph_3d_frame.grid(row=0, column=1, sticky='nsew', padx=(2, 0), pady=2)
        
        # Configura o gráfico 3D
        self.setup_3d_plot()
        
        # --- Frame da Tabela ---
        self.table_frame = tk.Frame(main_area, bg='white')
        self.table_frame.grid(row=1, column=0, columnspan=2, sticky='nsew', padx=5, pady=(2, 5))
        
        self.setup_data_table()


    def setup_realtime_plot(self):
        """Configura o gráfico em tempo real"""
        # Configuração ajustada para metade da largura
        self.fig = Figure(figsize=(10, 6), dpi=100)
        self.ax = self.fig.add_subplot(111)
        self.ax.set_title(t('population.graph_title'), fontsize=12)
        self.ax.set_xlabel(t('population.graph_xlabel'), fontsize=10)
        self.ax.set_ylabel(t('population.graph_ylabel'), fontsize=10)
        self.ax.grid(True, alpha=0.3)
        
        # Configuração das escalas dos eixos
        self._setup_plot_scales()
        
        # Inicializa linha de progresso dinâmica (começa em 5 dBm)
        self.progress_line, = self.ax.plot([5, 5], [0, 0], color='red', linewidth=6, alpha=1.0, label=t('population.progress_label'), zorder=15)
        print("🔴 Linha de progresso inicializada em 5 dBm")
        
        self.canvas = FigureCanvasTkAgg(self.fig, self.graph_frame)
        self.canvas.get_tk_widget().pack(fill='both', expand=True, padx=5, pady=5)
        
        # Inicializa linha do gráfico
        self.line, = self.ax.plot([], [], 'b-', linewidth=2, label=t('population.tags_detected'))
        self.ax.legend()
        
        # Força o redimensionamento do gráfico
        self.fig.set_tight_layout(True)
        self.canvas.draw()
        
        # Configura tooltip para as barras
        self._setup_bar_tooltip()
        
        # CORREÇÃO: Garante que o tooltip seja reconfigurado quando o módulo for mostrado
        self.bind('<Visibility>', self._on_visibility_change)
        self.bind('<FocusIn>', self._on_focus_in)
        self.bind('<Map>', self._on_map)

    def _on_visibility_change(self, event):
        """Reconfigura tooltip quando o módulo se torna visível"""
        try:
            if event.state == 'VisibilityUnobscured':
                # Módulo se tornou visível - reconfigura tooltip e atualiza gráfico 3D
                print("🔄 Módulo se tornou visível - reconfigurando tooltip e atualizando gráfico 3D")
                self.after(100, self._setup_bar_tooltip)
                self.after(200, self._update_3d_with_persistent_colors)
        except Exception as e:
            print(f"⚠️ Erro ao reconfigurar tooltip (visibility): {e}")

    def _on_focus_in(self, event):
        """Reconfigura tooltip quando o módulo recebe foco"""
        try:
            print("🔄 Módulo recebeu foco - reconfigurando tooltip")
            self.after(100, self._setup_bar_tooltip)
        except Exception as e:
            print(f"⚠️ Erro ao reconfigurar tooltip (focus): {e}")

    def _on_map(self, event):
        """Reconfigura tooltip quando o módulo é mapeado (mostrado)"""
        try:
            print("🔄 Módulo foi mapeado - reconfigurando tooltip")
            self.after(100, self._setup_bar_tooltip)
        except Exception as e:
            print(f"⚠️ Erro ao reconfigurar tooltip (map): {e}")

    def _force_tooltip_reconfiguration(self):
        """Força a reconfiguração completa do tooltip"""
        try:
            print("🔧 Forçando reconfiguração completa do tooltip...")
            
            # Remove tooltip anterior se existir
            if hasattr(self, 'tooltip_annotation') and self.tooltip_annotation:
                try:
                    self.tooltip_annotation.remove()
                except:
                    pass
            
            # Remove event handlers anteriores
            if hasattr(self, '_tooltip_cids'):
                for cid in self._tooltip_cids:
                    try:
                        self.canvas.mpl_disconnect(cid)
                    except:
                        pass
            
            # Reconfigura o tooltip
            self._setup_bar_tooltip()
            print("✅ Tooltip reconfigurado com sucesso")
            
        except Exception as e:
            print(f"⚠️ Erro na reconfiguração forçada do tooltip: {e}")

    def _setup_bar_tooltip(self):
        """Configura tooltip para mostrar EPCs das tags nas barras"""
        try:
            # CORREÇÃO: Verifica se o canvas e ax existem
            if not hasattr(self, 'canvas') or not hasattr(self, 'ax') or not self.canvas or not self.ax:
                print("⚠️ Canvas ou ax não disponível para tooltip")
                return
            
            # CORREÇÃO: Verifica se o canvas tem o método mpl_connect
            if not hasattr(self.canvas, 'mpl_connect'):
                print("⚠️ Canvas não tem método mpl_connect")
                return
            
            # CORREÇÃO: Remove tooltip anterior se existir
            if hasattr(self, 'tooltip_annotation') and self.tooltip_annotation:
                try:
                    self.tooltip_annotation.remove()
                except:
                    pass
            
            # CORREÇÃO: Remove event handlers anteriores se existirem
            if hasattr(self, '_tooltip_cids'):
                for cid in self._tooltip_cids:
                    try:
                        self.canvas.mpl_disconnect(cid)
                    except:
                        pass
            
            # Cria nova anotação para tooltip com configuração mais robusta
            self.tooltip_annotation = self.ax.annotate('', xy=(0, 0), xytext=(20, 20),
                                                      textcoords="offset points",
                                                      bbox=dict(boxstyle="round,pad=0.5", 
                                                               facecolor="lightblue", 
                                                               alpha=0.9,
                                                               edgecolor="navy",
                                                               linewidth=1),
                                                      arrowprops=dict(arrowstyle="->", 
                                                                     connectionstyle="arc3,rad=0",
                                                                     color="navy",
                                                                     lw=1),
                                                      fontsize=9, 
                                                      ha='left',
                                                      va='bottom',
                                                      wrap=True)
            self.tooltip_annotation.set_visible(False)
            
            # CORREÇÃO: Armazena IDs dos event handlers para limpeza posterior
            self._tooltip_cids = []
            try:
                cid1 = self.canvas.mpl_connect("motion_notify_event", self._on_hover)
                cid2 = self.canvas.mpl_connect("axes_leave_event", self._on_leave)
                self._tooltip_cids.extend([cid1, cid2])
                print(f"✅ Tooltip reconfigurado com sucesso - CIDs: {self._tooltip_cids}")
            except Exception as e:
                print(f"⚠️ Erro ao conectar eventos do tooltip: {e}")
                return
            
        except Exception as e:
            print(f"⚠️ Erro ao configurar tooltip: {e}")

    def _on_hover(self, event):
        """Handler para evento de hover do mouse"""
        try:
            if event.inaxes != self.ax:
                # CORREÇÃO: Esconde tooltip se sair do gráfico
                if hasattr(self, 'tooltip_annotation') and self.tooltip_annotation and self.tooltip_annotation.get_visible():
                    self.tooltip_annotation.set_visible(False)
                    self.canvas.draw_idle()
                return
                
            # Verifica se há barras no gráfico
            patches = self.ax.patches
            if not patches:
                # CORREÇÃO: Esconde tooltip se não há barras
                if hasattr(self, 'tooltip_annotation') and self.tooltip_annotation and self.tooltip_annotation.get_visible():
                    self.tooltip_annotation.set_visible(False)
                    self.canvas.draw_idle()
                return
                
            # CORREÇÃO: Evita processamento desnecessário
            if not hasattr(self, 'tooltip_annotation') or not self.tooltip_annotation:
                return
                
            # CORREÇÃO: Flag para controlar se encontrou uma barra válida
            found_bar = False
            
            # Encontra a barra mais próxima do cursor
            for patch in patches:
                if patch.contains(event)[0]:
                    found_bar = True
                    # Obtém posição da barra
                    x_pos = patch.get_x() + patch.get_width() / 2  # Centro da barra
                    y_pos = patch.get_y() + patch.get_height() / 2
                    
                    # Encontra a potência correspondente - busca a potência exata mais próxima
                    # Procura na lista de potências disponíveis a mais próxima da posição X
                    closest_power = None
                    min_distance = float('inf')
                    
                    for test_power in self.power_epcs_map.keys():
                        distance = abs(test_power - x_pos)
                        if distance < min_distance:
                            min_distance = distance
                            closest_power = test_power
                    
                    power = closest_power if closest_power is not None else round(x_pos)
                    
                    # Busca EPCs para esta potência
                    epcs = self.power_epcs_map.get(power, [])
                    
                    if epcs:
                        # Formata texto do tooltip incluindo apelidos
                        if len(epcs) == 1:
                            epc_data = epcs[0]
                            epc_text = epc_data['epc']
                            apelido_text = f" ({epc_data['apelido']})" if epc_data.get('apelido') else ""
                            tooltip_text = f"{t('population.power_tooltip').format(power=power)}\n{t('population.epc_tooltip').format(epc=epc_text)}{apelido_text}"
                        else:
                            epcs_text = "\n".join([
                                f"• {epc_data['epc']}" + (f" ({epc_data['apelido']})" if epc_data.get('apelido') else "")
                                for epc_data in epcs
                            ])
                            tooltip_text = f"{t('population.power_tooltip').format(power=power)}\n{t('population.epcs_tooltip').format(count=len(epcs))}\n{epcs_text}"
                        
                        # CORREÇÃO: Posicionamento inteligente baseado na posição do mouse
                        self._position_tooltip_smartly(event, x_pos, y_pos, tooltip_text)
                    else:
                        # CORREÇÃO: Se não há EPCs para esta potência, esconde tooltip
                        if self.tooltip_annotation.get_visible():
                            self.tooltip_annotation.set_visible(False)
                            self.canvas.draw_idle()
                    break
            
            # CORREÇÃO: Se não encontrou nenhuma barra, esconde tooltip
            if not found_bar and self.tooltip_annotation.get_visible():
                self.tooltip_annotation.set_visible(False)
                self.canvas.draw_idle()
                    
        except Exception as e:
            print(f"⚠️ Erro no hover: {e}")

    def _position_tooltip_smartly(self, event, x_pos, y_pos, tooltip_text):
        """Posiciona o tooltip de forma inteligente baseado na posição do mouse"""
        try:
            # Obtém dimensões do canvas
            canvas_width = self.canvas.get_width_height()[0]
            canvas_height = self.canvas.get_width_height()[1]
            
            # Converte coordenadas do evento para pixels do canvas
            x_pixel = event.x
            y_pixel = event.y
            
            # Margens mais conservadoras para evitar cortes
            margin_right = 300   # Margem para borda direita
            margin_top = 150     # Margem para borda superior
            margin_bottom = 150  # Margem para borda inferior
            margin_left = 250    # Margem para borda esquerda
            
            # Calcula posição do tooltip baseada na posição do mouse
            if x_pixel > canvas_width - margin_right:  # Próximo à borda direita
                # Posiciona à esquerda do mouse com margem maior
                xytext = (-150, 20)
                ha = 'right'
            elif x_pixel < margin_left:  # Próximo à borda esquerda
                # Posiciona à direita do mouse com margem maior
                xytext = (150, 20)
                ha = 'left'
            else:  # Posição normal (centro)
                # Posiciona à direita do mouse
                xytext = (20, 20)
                ha = 'left'
            
            # Ajusta posição vertical se necessário
            if y_pixel < margin_top:  # Próximo ao topo
                xytext = (xytext[0], 150)  # Margem maior para baixo
            elif y_pixel > canvas_height - margin_bottom:  # Próximo à parte inferior
                xytext = (xytext[0], -150)  # Margem maior para cima
            
            # CORREÇÃO: Evita tremulação definindo posição fixa
            self.tooltip_annotation.xy = (x_pos, y_pos)
            self.tooltip_annotation.xytext = xytext
            self.tooltip_annotation.set_text(tooltip_text)
            self.tooltip_annotation.set_ha(ha)
            self.tooltip_annotation.set_visible(True)
            
            # CORREÇÃO: Sempre força redesenho para garantir que o tooltip apareça
            self.canvas.draw_idle()
            self._last_tooltip_text = tooltip_text
            
        except Exception as e:
            print(f"⚠️ Erro no posicionamento do tooltip: {e}")
            # Fallback para posicionamento padrão
            try:
                if hasattr(self, 'tooltip_annotation') and self.tooltip_annotation:
                    self.tooltip_annotation.xy = (x_pos, y_pos)
                    self.tooltip_annotation.set_text(tooltip_text)
                    self.tooltip_annotation.set_visible(True)
                    self.canvas.draw_idle()
            except Exception as fallback_error:
                print(f"⚠️ Erro no fallback do tooltip: {fallback_error}")

    def _on_leave(self, event):
        """Handler para quando o mouse sai do gráfico"""
        try:
            if hasattr(self, 'tooltip_annotation') and self.tooltip_annotation and self.tooltip_annotation.get_visible():
                self.tooltip_annotation.set_visible(False)
                self.canvas.draw_idle()
        except Exception as e:
            print(f"⚠️ Erro ao sair do gráfico: {e}")

    def _setup_plot_scales(self):
        """Configura as escalas dos eixos do gráfico"""
        # Eixo X: de 5 dBm até a potência máxima configurada pelo usuário
        try:
            max_power = float(self.power_var.get())
        except (ValueError, AttributeError):
            max_power = self.license_limits.get('max_power', 25)
        
        min_power = self.license_limits.get('min_power', 5)
        
        # Adiciona espaços nas laterais para melhor visualização do gráfico de barras
        x_margin = 2  # Espaço de 2 dBm em cada lado
        x_min = max(0, min_power - x_margin)  # Não pode ser negativo
        x_max = max_power + x_margin
        
        # Define escala do eixo X com margens
        self.ax.set_xlim(x_min, x_max)
        self.ax.set_xlabel(t('population.graph_xlabel'), fontsize=12)
        
        # Eixo Y: de 0 até número de tags registradas e selecionadas
        registered_and_selected_count = self._count_registered_and_selected_tags()
        max_tags = max(registered_and_selected_count, 1)  # Mínimo de 1 para visualização
        
        self.ax.set_ylim(0, max_tags)
        self.ax.set_ylabel(t('population.graph_ylabel'), fontsize=12)
        
        # Configura graduações dos eixos
        # Eixo X: graduação principal de 5 em 5 dBm + subdivisões baseadas no step
        try:
            power_step = float(self.attenuator_var.get())
        except (ValueError, AttributeError):
            power_step = 1.0  # Valor padrão
        
        # Cria lista de ticks principais (5 em 5 dBm)
        main_ticks = list(range(int(min_power), int(max_power) + 1, 5))
        
        # Cria lista de ticks secundários baseados no step de potência
        minor_ticks = []
        current_power = min_power
        while current_power <= max_power:
            # Adiciona tick se não for um tick principal
            if current_power not in main_ticks:
                minor_ticks.append(current_power)
            current_power += power_step
        
        # Configura ticks principais e secundários
        self.ax.set_xticks(main_ticks)  # Ticks principais
        self.ax.set_xticks(minor_ticks, minor=True)  # Ticks secundários
        
        # Configura estilo dos ticks - subdivisões menores
        self.ax.tick_params(axis='x', which='major', length=8, width=1.5)  # Ticks principais maiores
        self.ax.tick_params(axis='x', which='minor', length=4, width=0.8)  # Ticks secundários menores
        
        # Habilita exibição dos valores das subdivisões
        self.ax.tick_params(axis='x', which='minor', labelsize=8)  # Tamanho menor para subdivisões
        self.ax.tick_params(axis='x', which='minor', labelcolor='gray')  # Cor cinza para subdivisões
        
        # Força a exibição dos labels das subdivisões
        self.ax.tick_params(axis='x', which='minor', labelbottom=True)
        
        # Adiciona locator para as subdivisões
        from matplotlib.ticker import MultipleLocator
        self.ax.xaxis.set_minor_locator(MultipleLocator(power_step))
        
        # Cria todos os ticks (principais + secundários) e exibe todos os valores
        all_ticks = sorted(main_ticks + minor_ticks)
        # Formata todos os valores com uma casa decimal para uniformidade
        all_labels = [f"{tick:.1f}" for tick in all_ticks]
        
        # Define apenas ticks principais para evitar sobreposição
        self.ax.set_xticks(main_ticks)
        self.ax.set_xticklabels([f"{tick:.0f}" for tick in main_ticks], fontsize=12)
        
        # Configura apenas ticks principais para evitar sobreposição
        self.ax.tick_params(axis='x', which='major', length=4, width=0.8, colors='black', labelsize=12)
        
        # Log dos valores das divisões para debug
        print(f"📊 Divisões do eixo X:")
        print(f"   Ticks principais (5 em 5 dBm): {main_ticks}")
        print(f"   Ticks secundários (step {power_step} dBm): {minor_ticks}")
        print(f"   Total de divisões: {len(main_ticks)} principais + {len(minor_ticks)} secundárias")
        
        # Graduação do eixo Y: de 1 em 1 se poucas tags, senão proporcional
        if max_tags <= 10:
            self.ax.set_yticks(range(0, max_tags + 1, 1))
        else:
            self.ax.set_yticks(range(0, max_tags + 1, max(1, max_tags // 10)))
        
        # MELHORIA: Adiciona 10 linhas horizontais de grade para melhor aparência
        # APENAS quando não há tags registradas
        registered_tags_count = len(self.database.get_tags()) if hasattr(self, 'database') and self.database else 0
        if registered_tags_count == 0:
            # Usa número de tags selecionadas como limite, mínimo 10
            selected_tags_count = len(self.selected_tags) if hasattr(self, 'selected_tags') else 0
            max_y = max(selected_tags_count, 10)  # Mínimo 10 para visualização
            self.ax.set_ylim(0, max_y)
            # Cria linhas horizontais de grade baseadas no número de tags
            y_ticks = list(range(0, max_y + 1, 1))
            self.ax.set_yticks(y_ticks)
            # CORREÇÃO: Mantém os labels do eixo Y para mostrar a escala
            self.ax.set_yticklabels([str(i) for i in y_ticks])
            # Adiciona linhas de grade horizontais mais visíveis
            self.ax.grid(True, axis='y', alpha=0.4, linestyle='-', linewidth=0.8)
            # Mantém as linhas de grade verticais mais sutis
            self.ax.grid(True, axis='x', alpha=0.2, linestyle='-', linewidth=0.5)
        else:
            # Para casos com dados de teste, usa o comportamento original com rótulos
            self.ax.grid(True, alpha=0.3)

    def _count_registered_and_selected_tags(self):
        """Conta o número de tags que estão tanto registradas quanto selecionadas"""
        if not hasattr(self, 'selected_tags') or not hasattr(self, 'database'):
            return 0
        
        # Pega todas as tags registradas
        registered_tags = set()
        try:
            for tag_data in self.database.get_tags():
                registered_tags.add(tag_data['epc'])
        except:
            pass
        
        # Conta quantas tags selecionadas estão também registradas
        count = 0
        if hasattr(self, 'selected_tags') and self.selected_tags:
            for selected_epc in self.selected_tags:
                if selected_epc in registered_tags:
                    count += 1
        
        return count

    def _validate_frequency(self, event=None):
        """Valida se a frequência está dentro dos limites da licença"""
        try:
            frequency = float(self.frequency_var.get())
            min_freq = self.license_limits.get('min_freq', 800)
            max_freq = self.license_limits.get('max_freq', 1000)
            
            if not (min_freq <= frequency <= max_freq):
                messagebox.showerror(t('population.error_freq_blocked'), t('population.error_freq_blocked'))
                # Reseta para valor padrão dentro dos limites
                self.frequency_var.set(str(min_freq))
                # Salva parâmetros automaticamente
                self._save_test_parameters()
                # Salva estado da interface
                self._save_complete_ui_state()
                return False
            # Salva parâmetros automaticamente
            self._save_test_parameters()
            # Salva estado da interface
            self._save_complete_ui_state()
            return True
        except ValueError:
            messagebox.showerror(t('population.error_freq_blocked'), t('population.error_freq_blocked'))
            self.frequency_var.set("915")  # Valor padrão
            return False

    def _validate_power(self, event=None):
        """Valida se a potência está dentro dos limites da licença"""
        try:
            power = float(self.power_var.get())
            min_power = self.license_limits.get('min_power', 5)
            max_power = self.license_limits.get('max_power', 25)
            
            if not (min_power <= power <= max_power):
                messagebox.showerror(t('population.error_validation'), 
                    t('population.error_power_range').format(min_power=min_power, max_power=max_power, value=power))
                # Reseta para valor padrão dentro dos limites
                self.power_var.set(str(max_power))
                # Atualiza a escala do gráfico com a nova potência máxima
                self.update_plot_scales()
                return False
            
            # Atualiza a escala do gráfico com a nova potência máxima
            self.update_plot_scales()
            # Salva parâmetros automaticamente
            self._save_test_parameters()
            # Salva estado da interface
            self._save_complete_ui_state()
            return True
        except ValueError:
            messagebox.showerror(t('population.error_validation'), t('population.error_power_invalid'))
            self.power_var.set("25")  # Valor padrão
            return False

    def setup_3d_plot(self):
        """Configura o gráfico 3D para posicionamento das tags"""
        try:
            if not MATPLOTLIB_OK:
                tk.Label(self.graph_3d_frame, 
                        text="Matplotlib não disponível\npara gráfico 3D", 
                        font=("Helvetica", 12), 
                        bg='white').pack(expand=True)
                return
            
            # Configura figura 3D
            self.fig_3d = Figure(figsize=(8, 6), dpi=100)
            self.ax_3d = self.fig_3d.add_subplot(111, projection='3d')
            
            # Configuração básica do gráfico 3D
            self.ax_3d.set_title(t('population.graph_3d_title'), fontsize=12)
            self.ax_3d.set_xlabel("X", fontsize=10)
            self.ax_3d.set_ylabel("Y", fontsize=10)
            self.ax_3d.set_zlabel("Z", fontsize=10)
            
            # Configuração da moldura externa igual ao gráfico de barras
            # Para gráficos 3D, configuramos as bordas da caixa
            self.ax_3d.set_box_aspect([1,1,1])  # Proporção 1:1:1 para melhor visualização
            self.ax_3d.grid(True, alpha=0.3)
            
            # Configuração de estilo para ficar mais similar ao gráfico 2D
            self.ax_3d.tick_params(axis='x', labelsize=8)
            self.ax_3d.tick_params(axis='y', labelsize=8)
            self.ax_3d.tick_params(axis='z', labelsize=8)
            
            # Configuração da moldura/borda para ficar mais similar ao gráfico 2D
            self.ax_3d.xaxis.pane.set_edgecolor('black')
            self.ax_3d.yaxis.pane.set_edgecolor('black')
            self.ax_3d.zaxis.pane.set_edgecolor('black')
            self.ax_3d.xaxis.pane.set_linewidth(1.0)
            self.ax_3d.yaxis.pane.set_linewidth(1.0)
            self.ax_3d.zaxis.pane.set_linewidth(1.0)
            
            # Configuração das linhas de grade para ficar mais similar ao 2D
            self.ax_3d.xaxis.pane.set_alpha(0.1)
            self.ax_3d.yaxis.pane.set_alpha(0.1)
            self.ax_3d.zaxis.pane.set_alpha(0.1)
            
            # Configuração inicial dos limites (será ajustada automaticamente)
            self.ax_3d.set_xlim(-5, 5)
            self.ax_3d.set_ylim(-5, 5)
            self.ax_3d.set_zlim(-5, 5)
            
            # Canvas para o gráfico 3D
            self.canvas_3d = FigureCanvasTkAgg(self.fig_3d, self.graph_3d_frame)
            self.canvas_3d.get_tk_widget().pack(fill='both', expand=True, padx=5, pady=5)
            
            # Força o redimensionamento
            self.fig_3d.set_tight_layout(True)
            self.canvas_3d.draw()
            
            # Inicializa lista de tags 3D
            self.tags_3d = []
            
            # Atualiza o gráfico 3D com as tags registradas
            self.update_3d_plot(test_running=False, detected_tags=None)
            
            print("✅ Gráfico 3D configurado com sucesso")
            
        except Exception as e:
            print(f"⚠️ Erro ao configurar gráfico 3D: {e}")
            tk.Label(self.graph_3d_frame, 
                    text=f"Erro ao configurar\nGráfico 3D:\n{str(e)}", 
                    font=("Helvetica", 10), 
                    bg='white', 
                    fg='red').pack(expand=True)

    def update_3d_plot(self, test_running=False, detected_tags=None):
        """Atualiza o gráfico 3D com as tags registradas
        
        Args:
            test_running (bool): Se o teste está em andamento
            detected_tags (list): Lista de EPCs das tags detectadas durante o teste
        """
        print(f"🔍 DEBUG 3D: update_3d_plot chamada - test_running: {test_running}, detected_tags: {detected_tags}")
        print(f"🔍 DEBUG 3D: detected_tags_permanent: {len(self.detected_tags_permanent)} tags")
        try:
            if not hasattr(self, 'ax_3d') or not self.ax_3d:
                return
            
            # Recarrega os apelidos das tags antes de atualizar o gráfico
            self._load_tag_apelidos()
            
            # Debug: verifica se os apelidos foram carregados
            if hasattr(self, 'tag_apelidos'):
                print(f"🔍 Apelidos carregados para gráfico 3D: {len(self.tag_apelidos)} tags")
                for epc, apelido in list(self.tag_apelidos.items())[:3]:  # Mostra apenas os primeiros 3
                    print(f"   EPC: {epc[:20]}... -> Apelido: '{apelido}'")
            else:
                print("⚠️ tag_apelidos não encontrado")
            
            # Limpa o gráfico 3D
            self.ax_3d.clear()
            
            # Reconfigura o gráfico 3D
            self.ax_3d.set_title(t('population.graph_3d_title'), fontsize=12)
            self.ax_3d.set_xlabel("X", fontsize=10)
            self.ax_3d.set_ylabel("Y", fontsize=10)
            self.ax_3d.set_zlabel("Z", fontsize=10)
            
            # Configuração da moldura externa igual ao gráfico de barras
            # Para gráficos 3D, configuramos as bordas da caixa
            self.ax_3d.set_box_aspect([1,1,1])  # Proporção 1:1:1 para melhor visualização
            self.ax_3d.grid(True, alpha=0.3)
            
            # Configuração de estilo para ficar mais similar ao gráfico 2D
            self.ax_3d.tick_params(axis='x', labelsize=8)
            self.ax_3d.tick_params(axis='y', labelsize=8)
            self.ax_3d.tick_params(axis='z', labelsize=8)
            
            # Configuração da moldura/borda para ficar mais similar ao gráfico 2D
            self.ax_3d.xaxis.pane.set_edgecolor('black')
            self.ax_3d.yaxis.pane.set_edgecolor('black')
            self.ax_3d.zaxis.pane.set_edgecolor('black')
            self.ax_3d.xaxis.pane.set_linewidth(1.0)
            self.ax_3d.yaxis.pane.set_linewidth(1.0)
            self.ax_3d.zaxis.pane.set_linewidth(1.0)
            
            # Configuração das linhas de grade para ficar mais similar ao 2D
            self.ax_3d.xaxis.pane.set_alpha(0.1)
            self.ax_3d.yaxis.pane.set_alpha(0.1)
            self.ax_3d.zaxis.pane.set_alpha(0.1)
            
            # REMOVIDO: Verificação prematura que estava bloqueando o gráfico 3D
            # A verificação será feita depois de carregar as tags selecionadas
            
            # Carrega as tags realmente selecionadas/registradas
            selected_tags = []
            if os.path.exists(self.SELECTED_TAGS_FILE):
                try:
                    with open(self.SELECTED_TAGS_FILE, 'r', encoding='utf-8') as f:
                        data = json.load(f)
                        selected_tags = data.get('selected_tags', [])
                        print(f"🔍 Tags selecionadas carregadas do arquivo: {len(selected_tags)}")
                        print(f"🔍 EPCs selecionados: {[epc[:20] + '...' for epc in selected_tags]}")
                except Exception as e:
                    print(f"⚠️ Erro ao carregar tags selecionadas: {e}")
            else:
                print(f"⚠️ Arquivo populacao_selected_tags.json não existe!")
                print(f"🔍 DEBUG: self.selected_tags tem {len(self.selected_tags) if hasattr(self, 'selected_tags') else 'N/A'} tags")
                if hasattr(self, 'selected_tags') and self.selected_tags:
                    print(f"🔍 DEBUG: self.selected_tags: {list(self.selected_tags)[:3]}...")
                    # CORREÇÃO: Se o arquivo não existe mas há tags selecionadas, salva-as
                    print("🔄 Salvando tags selecionadas no arquivo...")
                    self._save_selected_tags_to_file()
                    selected_tags = list(self.selected_tags)
            
            # DEBUG ADICIONAL: Verifica se selected_tags está vazio
            if not selected_tags:
                print(f"⚠️ PROBLEMA: selected_tags está vazio!")
                print(f"🔍 DEBUG: self.selected_tags = {self.selected_tags if hasattr(self, 'selected_tags') else 'NÃO EXISTE'}")
                print(f"🔍 DEBUG: Arquivo existe? {os.path.exists(self.SELECTED_TAGS_FILE)}")
                if hasattr(self, 'selected_tags') and self.selected_tags:
                    print("🔄 Tentando salvar tags selecionadas novamente...")
                    self._save_selected_tags_to_file()
                    selected_tags = list(self.selected_tags)
                    print(f"🔍 Após salvar: selected_tags = {len(selected_tags)} tags")
            
            print(f"🔍 Tags selecionadas para gráfico 3D: {len(selected_tags)}")
            
            # Obtém apenas as tags SELECIONADAS para o gráfico 3D
            registered_tags = []
            if hasattr(self, 'database') and self.database:
                all_tags = self.database.get_tags()
                print(f"🔍 Total de tags no banco de dados: {len(all_tags)}")
                
                if selected_tags:
                    # Debug: compara tags selecionadas vs tags no banco
                    print(f"🔍 DEBUG - Comparando tags:")
                    print(f"   Tags selecionadas no JSON: {len(selected_tags)}")
                    print(f"   Tags no banco de dados: {len(all_tags)}")
                    
                    # Lista todas as tags no banco
                    db_epcs = [tag.get('epc', '') for tag in all_tags]
                    print(f"   EPCs no banco: {[epc[:20] + '...' for epc in db_epcs]}")
                    print(f"   EPCs selecionados: {[epc[:20] + '...' for epc in selected_tags]}")
                    
                    # Usa apenas as tags que estão selecionadas
                    registered_tags = [tag for tag in all_tags if tag.get('epc', '') in selected_tags]
                    print(f"🔍 Tags selecionadas encontradas: {len(registered_tags)}")
                    
                    # Debug: mostra quais tags foram encontradas
                    tags_com_coords = 0
                    tags_sem_coords = 0
                    for tag in registered_tags:
                        epc = tag.get('epc', '')
                        coords = tag.get('coordinates', '')
                        apelido = tag.get('name', tag.get('apelido', ''))
                        
                        if coords and coords.strip():
                            tags_com_coords += 1
                            print(f"   ✅ Tag COM coordenadas: {epc[:20]}... -> Apelido: '{apelido}' -> Coords: '{coords}'")
                        else:
                            tags_sem_coords += 1
                            print(f"   ❌ Tag SEM coordenadas: {epc[:20]}... -> Apelido: '{apelido}' -> Coords: '{coords}'")
                    
                    print(f"📊 Resumo: {tags_com_coords} tags COM coordenadas, {tags_sem_coords} tags SEM coordenadas")
                    
                    # Debug: mostra quais tags NÃO foram encontradas
                    missing_tags = [epc for epc in selected_tags if epc not in db_epcs]
                    if missing_tags:
                        print(f"   ❌ Tags selecionadas mas NÃO encontradas no banco: {[epc[:20] + '...' for epc in missing_tags]}")
                else:
                    # Se não há tags selecionadas, não mostra nenhuma no gráfico 3D
                    registered_tags = []
                    print(f"🔍 Nenhuma tag selecionada - gráfico 3D vazio")
            else:
                print("⚠️ Database não encontrado ou não inicializado")
            
            print(f"🔍 Tags registradas com filtro: {len(registered_tags)}")
            
            if not registered_tags:
                # Se não há tags, limpa o gráfico 3D completamente
                self.ax_3d.clear()
                self.ax_3d.text(0, 0, 0, "Nenhuma tag selecionada", 
                               fontsize=12, ha='center', va='center')
                self.ax_3d.set_xlim(-5, 5)
                self.ax_3d.set_ylim(-5, 5)
                self.ax_3d.set_zlim(-5, 5)
                self.ax_3d.set_xlabel("X", fontsize=10)
                self.ax_3d.set_ylabel("Y", fontsize=10)
                self.ax_3d.set_zlabel("Z", fontsize=10)
                print(f"🧹 Gráfico 3D limpo - nenhuma tag selecionada")
            else:
                # Lista para armazenar coordenadas para escala automática
                x_coords = []
                y_coords = []
                z_coords = []
                
                # Debug: mostra todas as tags selecionadas
                print(f"🔍 Tags selecionadas: {selected_tags}")
                
                # Processa cada tag registrada
                tags_with_coords = 0
                tags_without_coords = 0
                for tag_data in registered_tags:
                    epc = tag_data.get('epc', '')
                    apelido = tag_data.get('apelido', '')
                    coordinates = tag_data.get('coordinates', '')
                    
                    # Busca alternativa de apelido no dicionário tag_apelidos
                    if not apelido or not apelido.strip():
                        if hasattr(self, 'tag_apelidos') and epc in self.tag_apelidos:
                            apelido = self.tag_apelidos[epc]
                            print(f"🔍 Tag 3D - Apelido encontrado no dicionário: '{apelido}'")
                        else:
                            # Busca usando a função get_tag_apelido
                            apelido_alt = self.get_tag_apelido(epc)
                            if apelido_alt:
                                apelido = apelido_alt
                                print(f"🔍 Tag 3D - Apelido encontrado via get_tag_apelido: '{apelido}'")
                    
                    # Debug: mostra os dados da tag
                    print(f"🔍 Tag 3D - EPC: {epc[:20]}..., Apelido: '{apelido}', Coordenadas: '{coordinates}'")
                    
                    # Se não tem coordenadas, ignora
                    if not coordinates or not coordinates.strip():
                        tags_without_coords += 1
                        print(f"⚠️ Tag {epc[:20]}... SEM coordenadas - ignorada")
                        
                        # Debug específico para tag 127
                        if epc == '000000000000000588937127':
                            print(f"   🎯 TAG 127 SEM COORDENADAS: EPC={epc}, Coords='{coordinates}', Apelido='{apelido}'")
                        continue
                    
                    tags_with_coords += 1
                    
                    try:
                        # Parse das coordenadas (formato: "X,Y,Z")
                        coords_parts = coordinates.strip().split(',')
                        if len(coords_parts) != 3:
                            continue
                        
                        x = float(coords_parts[0].strip())
                        y = float(coords_parts[1].strip())
                        z = float(coords_parts[2].strip())
                        
                        # Adiciona às listas para escala
                        x_coords.append(x)
                        y_coords.append(y)
                        z_coords.append(z)
                        
                        # Determina o texto a exibir
                        display_text = apelido if apelido and apelido.strip() else "?"
                        print(f"🔍 Tag 3D - Display text: '{display_text}' (apelido: '{apelido}')")
                        
                        # Debug: mostra estado das tags detectadas
                        print(f"🔍 DEBUG COR: Tag {epc[:20]}... - detected_tags_permanent: {epc in self.detected_tags_permanent}")
                        print(f"🔍 DEBUG COR: detected_tags_permanent tem {len(self.detected_tags_permanent)} tags")
                        print(f"🔍 DEBUG COR: test_running: {test_running}, detected_tags: {detected_tags}")
                        
                        # Determina a cor baseada no estado permanente de detecção
                        if epc in self.detected_tags_permanent:
                            # Tag detectada permanentemente - VERDE
                            tag_color = 'green'
                            print(f"🟢 Tag {epc[:20]}... DETECTADA PERMANENTEMENTE - cor VERDE")
                        elif test_running and detected_tags and epc in detected_tags:
                            # Tag detectada durante o teste atual - VERDE
                            tag_color = 'green'
                            print(f"🟢 Tag {epc[:20]}... DETECTADA NO TESTE ATUAL - cor VERDE")
                        else:
                            # Tag não detectada - VERMELHO
                            tag_color = 'red'
                            print(f"🔴 Tag {epc[:20]}... NÃO DETECTADA - cor VERMELHO")
                        
                        # Plota a tag como texto 3D com cor dinâmica
                        text_obj = self.ax_3d.text(x, y, z, f"┌─●─┐\n{display_text}", 
                                                 fontsize=8, ha='center', va='center',
                                                 color=tag_color, weight='bold')
                        
                        # Adiciona tooltip com informações da tag
                        self._add_tag_tooltip(text_obj, epc, display_text, x, y, z)
                        
                        # Debug específico para tag 127
                        if epc == '000000000000000588937127':
                            print(f"   🎯 TAG 127 PLOTADA: EPC={epc}, Posição=({x},{y},{z}), Display='{display_text}'")
                        
                    except (ValueError, IndexError) as e:
                        print(f"⚠️ Erro ao processar coordenadas da tag {epc}: {e}")
                        continue
                
                # Configura escala automática se há coordenadas válidas
                if x_coords and y_coords and z_coords:
                    # Adiciona margem de 20% em cada direção
                    x_margin = (max(x_coords) - min(x_coords)) * 0.2
                    y_margin = (max(y_coords) - min(y_coords)) * 0.2
                    z_margin = (max(z_coords) - min(z_coords)) * 0.2
                    
                    x_min = min(x_coords) - x_margin
                    x_max = max(x_coords) + x_margin
                    y_min = min(y_coords) - y_margin
                    y_max = max(y_coords) + y_margin
                    z_min = min(z_coords) - z_margin
                    z_max = max(z_coords) + z_margin
                    
                    self.ax_3d.set_xlim(x_min, x_max)
                    self.ax_3d.set_ylim(y_min, y_max)
                    self.ax_3d.set_zlim(z_min, z_max)
                else:
                    # Se não há coordenadas válidas, usa escala padrão
                    self.ax_3d.set_xlim(-5, 5)
                    self.ax_3d.set_ylim(-5, 5)
                    self.ax_3d.set_zlim(-5, 5)
            
            # Redesenha o gráfico 3D
            self.canvas_3d.draw()
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar gráfico 3D: {e}")
    
    def _add_tag_tooltip(self, text_obj, epc, display_text, x, y, z):
        """Adiciona tooltip a uma tag no gráfico 3D"""
        try:
            # Busca informações da tag no banco de dados
            tag_info = None
            if hasattr(self, 'database') and self.database:
                all_tags = self.database.get_tags()
                for tag in all_tags:
                    if tag.get('epc') == epc:
                        tag_info = tag
                        break
            
            # Busca a primeira potência de detecção nos dados do teste
            first_power = None
            if hasattr(self, 'test_data') and self.test_data:
                for data_point in self.test_data:
                    if epc in data_point.get('detected_epcs', []):
                        first_power = data_point.get('power')
                        break
            
            # Cria texto do tooltip
            tooltip_text = f"EPC: {epc[:20]}...\n"
            tooltip_text += f"Apelido: {display_text}\n"
            tooltip_text += f"Posição: ({x:.1f}, {y:.1f}, {z:.1f})\n"
            
            if first_power is not None:
                tooltip_text += f"1ª Detecção: {first_power} dBm"
            else:
                tooltip_text += "1ª Detecção: Não detectada"
            
            # Adiciona evento de hover
            def on_hover(event):
                if event.inaxes == self.ax_3d:
                    # Verifica se o mouse está próximo da tag
                    mouse_x, mouse_y = event.xdata, event.ydata
                    if mouse_x is not None and mouse_y is not None:
                        # Calcula distância 2D (aproximada)
                        dist = ((mouse_x - x) ** 2 + (mouse_y - y) ** 2) ** 0.5
                        if dist < 1.0:  # Tolerância de proximidade
                            # Mostra tooltip
                            self._show_3d_tooltip(event, tooltip_text)
                        else:
                            self._hide_3d_tooltip()
                    else:
                        self._hide_3d_tooltip()
                else:
                    self._hide_3d_tooltip()
            
            # Conecta evento de hover
            self.canvas_3d.mpl_connect('motion_notify_event', on_hover)
            
        except Exception as e:
            print(f"⚠️ Erro ao adicionar tooltip para tag {epc}: {e}")
    
    def _show_3d_tooltip(self, event, text):
        """Mostra tooltip no gráfico 3D"""
        try:
            if not hasattr(self, '_tooltip_3d'):
                # Cria tooltip se não existir
                self._tooltip_3d = self.ax_3d.annotate(text, 
                                                     xy=(event.xdata, event.ydata),
                                                     xytext=(10, 10),
                                                     textcoords='offset points',
                                                     bbox=dict(boxstyle='round,pad=0.5', 
                                                              facecolor='yellow', 
                                                              alpha=0.8),
                                                     fontsize=8)
            else:
                # Atualiza posição e texto
                self._tooltip_3d.xy = (event.xdata, event.ydata)
                self._tooltip_3d.set_text(text)
            
            self._tooltip_3d.set_visible(True)
            self.canvas_3d.draw_idle()
            
        except Exception as e:
            print(f"⚠️ Erro ao mostrar tooltip 3D: {e}")
    
    def _hide_3d_tooltip(self):
        """Esconde tooltip do gráfico 3D"""
        try:
            if hasattr(self, '_tooltip_3d'):
                self._tooltip_3d.set_visible(False)
                self.canvas_3d.draw_idle()
        except Exception as e:
            print(f"⚠️ Erro ao esconder tooltip 3D: {e}")
            
            # Print de resumo do gráfico 3D
            print("✅ Gráfico 3D atualizado")
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar gráfico 3D: {e}")
    
    def _save_detected_tags(self):
        """Salva as tags detectadas permanentemente"""
        try:
            os.makedirs(self.PERSISTENCE_DIR, exist_ok=True)
            detected_data = {
                'detected_tags': list(self.detected_tags_permanent),
                'last_updated': datetime.now().isoformat()
            }
            with open(self.DETECTED_TAGS_FILE, 'w', encoding='utf-8') as f:
                json.dump(detected_data, f, indent=2, ensure_ascii=False)
            print(f"💾 Tags detectadas salvas: {len(self.detected_tags_permanent)} tags")
            print(f"🔍 EPCs salvos: {[epc[:20] + '...' for epc in list(self.detected_tags_permanent)[:5]]}")
            print(f"📁 Arquivo salvo em: {self.DETECTED_TAGS_FILE}")
        except Exception as e:
            print(f"⚠️ Erro ao salvar tags detectadas: {e}")
            import traceback
            traceback.print_exc()
    
    def _load_detected_tags(self):
        """Carrega as tags detectadas permanentemente"""
        try:
            print(f"🔍 Tentando carregar tags detectadas de: {self.DETECTED_TAGS_FILE}")
            if os.path.exists(self.DETECTED_TAGS_FILE):
                with open(self.DETECTED_TAGS_FILE, 'r', encoding='utf-8') as f:
                    detected_data = json.load(f)
                    self.detected_tags_permanent = set(detected_data.get('detected_tags', []))
                    print(f"📂 Tags detectadas carregadas: {len(self.detected_tags_permanent)} tags")
                    print(f"🔍 Tags detectadas: {[epc[:20] + '...' for epc in list(self.detected_tags_permanent)[:5]]}")
                    print(f"📅 Última atualização: {detected_data.get('last_updated', 'N/A')}")
            else:
                print("📂 Nenhum arquivo de tags detectadas encontrado")
                self.detected_tags_permanent = set()
        except Exception as e:
            print(f"⚠️ Erro ao carregar tags detectadas: {e}")
            import traceback
            traceback.print_exc()
            self.detected_tags_permanent = set()
    
    def _update_3d_with_persistent_colors(self):
        """Atualiza o gráfico 3D com as cores persistentes das tags detectadas"""
        try:
            print("🔄 Atualizando gráfico 3D com cores persistentes...")
            self.update_3d_plot(test_running=False, detected_tags=None)
            print("✅ Gráfico 3D atualizado com cores persistentes")
        except Exception as e:
            print(f"⚠️ Erro ao atualizar gráfico 3D com cores persistentes: {e}")
    
    def _activate_persistence_after_import(self):
        """Ativa a persistência após importar um teste"""
        try:
            print("🔄 Ativando persistência após importação...")
            print(f"🔍 DEBUG: detected_tags_permanent tem {len(self.detected_tags_permanent)} tags")
            print(f"🔍 DEBUG: EPCs detectados: {[epc[:20] + '...' for epc in list(self.detected_tags_permanent)[:5]]}")
            
            # Verifica se há tags detectadas
            if not self.detected_tags_permanent:
                print("⚠️ Nenhuma tag detectada permanente encontrada - pulando ativação")
                return
            
            # Atualiza lista de tags
            print("🔄 Atualizando lista de tags...")
            self.update_tags_list()
            self.update_tags_count()
            
            # Aguarda um pouco para garantir que a lista foi atualizada
            self.after(500, self._finalize_persistence_activation)
            
        except Exception as e:
            print(f"⚠️ Erro ao ativar persistência após importação: {e}")
            import traceback
            traceback.print_exc()
    
    def _finalize_persistence_activation(self):
        """Finaliza a ativação da persistência"""
        try:
            print("🔄 Finalizando ativação da persistência...")
            
            # Atualiza gráfico 3D
            print("🔄 Atualizando gráfico 3D...")
            self.update_3d_plot(test_running=False, detected_tags=None)
            
            # Força atualização da interface
            self.update_idletasks()
            
            # Força redesenho do gráfico 3D
            if hasattr(self, 'canvas_3d'):
                self.canvas_3d.draw()
                print("🔄 Gráfico 3D redesenhado")
            
            print("✅ Persistência ativada após importação")
        except Exception as e:
            print(f"⚠️ Erro ao finalizar ativação da persistência: {e}")
            import traceback
            traceback.print_exc()
    
    def _save_selected_tags_to_file(self):
        """Salva as tags selecionadas no arquivo para persistência"""
        try:
            if hasattr(self, 'selected_tags') and self.selected_tags:
                data = {
                    "selected_tags": list(self.selected_tags),
                    "last_saved": datetime.now().isoformat()
                }
                with open(self.SELECTED_TAGS_FILE, 'w', encoding='utf-8') as f:
                    json.dump(data, f, indent=2, ensure_ascii=False)
                print(f"💾 Tags selecionadas salvas: {len(self.selected_tags)} tags")
            else:
                print("⚠️ Nenhuma tag selecionada para salvar")
        except Exception as e:
            print(f"⚠️ Erro ao salvar tags selecionadas: {e}")

    def _load_table_column_widths(self, default_widths):
        """Carrega larguras das colunas da tabela salvas anteriormente"""
        try:
            if os.path.exists(self.TABLE_STATE_FILE):
                with open(self.TABLE_STATE_FILE, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    column_widths = data.get('column_widths', {})
                    print(f"📏 Larguras das colunas carregadas: {column_widths}")
                    return column_widths
        except Exception as e:
            print(f"⚠️ Erro ao carregar larguras das colunas: {e}")
        
        return default_widths

    def _save_table_column_widths(self):
        """Salva larguras atuais das colunas da tabela"""
        try:
            if hasattr(self, 'tree'):
                column_widths = {}
                for col in self.tree['columns']:
                    column_widths[col] = self.tree.column(col, 'width')
                
                # Carrega dados existentes ou cria novo
                table_state = {}
                if os.path.exists(self.TABLE_STATE_FILE):
                    with open(self.TABLE_STATE_FILE, 'r', encoding='utf-8') as f:
                        table_state = json.load(f)
                
                table_state['column_widths'] = column_widths
                table_state['last_saved'] = datetime.now().isoformat()
                
                with open(self.TABLE_STATE_FILE, 'w', encoding='utf-8') as f:
                    json.dump(table_state, f, indent=2, ensure_ascii=False)
                
                print(f"💾 Larguras das colunas salvas: {column_widths}")
        except Exception as e:
            print(f"⚠️ Erro ao salvar larguras das colunas: {e}")

    def _on_column_resize(self, event):
        """Chamado quando uma coluna é redimensionada"""
        try:
            # Pequeno delay para garantir que o redimensionamento foi concluído
            self.after(100, self._save_table_column_widths)
        except Exception as e:
            print(f"⚠️ Erro ao processar redimensionamento: {e}")

    def _activate_persistence_immediately(self):
        """Ativa a persistência imediatamente após importação"""
        try:
            print("🔄 Ativando persistência imediatamente...")
            print(f"🔍 DEBUG: detected_tags_permanent tem {len(self.detected_tags_permanent)} tags")
            print(f"🔍 DEBUG: selected_tags tem {len(self.selected_tags) if hasattr(self, 'selected_tags') else 'N/A'} tags")
            if hasattr(self, 'selected_tags') and self.selected_tags:
                print(f"🔍 DEBUG: selected_tags: {list(self.selected_tags)[:3]}...")
            
            # Atualiza lista de tags
            print("🔄 Atualizando lista de tags...")
            self.update_tags_list()
            self.update_tags_count()
            
            # Atualiza gráfico 3D
            print("🔄 Atualizando gráfico 3D...")
            self.update_3d_plot(test_running=False, detected_tags=None)
            
            # Força atualização da interface
            self.update_idletasks()
            
            # Força redesenho do gráfico 3D
            if hasattr(self, 'canvas_3d'):
                self.canvas_3d.draw()
                print("🔄 Gráfico 3D redesenhado")
            
            print("✅ Persistência ativada imediatamente")
        except Exception as e:
            print(f"⚠️ Erro ao ativar persistência imediatamente: {e}")
            import traceback
            traceback.print_exc()
    
    def update_plot_scales(self):
        """Atualiza as escalas do gráfico quando o número de tags muda"""
        if not MATPLOTLIB_OK or not hasattr(self, 'ax'):
            return
        
        try:
            # Atualiza escala do eixo Y baseado no número atual de tags registradas e selecionadas
            registered_and_selected_count = self._count_registered_and_selected_tags()
            max_tags = max(registered_and_selected_count, 1)  # Mínimo de 1 para visualização
            
            # MELHORIA: Aplica a mesma lógica de grade melhorada
            # APENAS quando não há tags registradas
            registered_tags_count = len(self.database.get_tags()) if hasattr(self, 'database') and self.database else 0
            if registered_tags_count == 0:
                # Usa número de tags selecionadas como limite, mínimo 10
                selected_tags_count = len(self.selected_tags) if hasattr(self, 'selected_tags') else 0
                max_y = max(selected_tags_count, 10)  # Mínimo 10 para visualização
                self.ax.set_ylim(0, max_y)
                # Cria linhas horizontais de grade baseadas no número de tags
                y_ticks = list(range(0, max_y + 1, 1))
                self.ax.set_yticks(y_ticks)
                # CORREÇÃO: Mantém os labels do eixo Y para mostrar a escala
                self.ax.set_yticklabels([str(i) for i in y_ticks])
                # Adiciona linhas de grade horizontais mais visíveis
                self.ax.grid(True, axis='y', alpha=0.4, linestyle='-', linewidth=0.8)
                # Mantém as linhas de grade verticais mais sutis
                self.ax.grid(True, axis='x', alpha=0.2, linestyle='-', linewidth=0.5)
            else:
                # Para casos com dados de teste, usa o comportamento original com rótulos
                self.ax.set_ylim(0, max_tags)
                # Graduação do eixo Y: de 1 em 1 se poucas tags, senão proporcional
                if max_tags <= 10:
                    self.ax.set_yticks(range(0, max_tags + 1, 1))
                else:
                    self.ax.set_yticks(range(0, max_tags + 1, max(1, max_tags // 10)))
                self.ax.grid(True, alpha=0.3)
                
                # Mantém as margens do eixo X baseado na potência configurada
                try:
                    max_power = float(self.power_var.get())
                except (ValueError, AttributeError):
                    max_power = self.license_limits.get('max_power', 25)
                
                min_power = self.license_limits.get('min_power', 5)
                x_margin = 2  # Espaço de 2 dBm em cada lado
                x_min = max(0, min_power - x_margin)
                x_max = max_power + x_margin
                self.ax.set_xlim(x_min, x_max)
                
                # Redesenha o gráfico
                self.canvas.draw()
        except Exception as e:
            print(f"⚠️ Erro ao atualizar escalas do gráfico: {e}")

    def setup_data_table(self):
        """Configura a tabela de dados"""
        # Frame para título com ícone
        title_frame = tk.Frame(self.table_frame, bg='white')
        title_frame.pack(pady=(5, 0))
        
        # Ícone de lupa clicável para mostrar imagem de Risco
        zoom_label = tk.Label(title_frame, text="🔍", font=("Helvetica", 12), 
                             cursor="hand2", fg="#0066cc", bg='white')
        zoom_label.pack(side='left', padx=(0, 5))
        zoom_label.bind('<Button-1>', self._show_risk_image_enlarged)
        
        # Título da tabela
        title_label = tk.Label(title_frame, text=t('population.test_data_title'), 
                              font=("Helvetica", 12), bg='white', fg='#2c3e50')
        title_label.pack(side='left')
        
        # Frame para a tabela
        table_container = tk.Frame(self.table_frame, bg='white')
        table_container.pack(fill='both', expand=True, padx=10, pady=5)
        
        # Treeview para exibir dados - altura reduzida
        columns = ('EPC', 'Apelido', 'Comentario', 'Coordenadas', 'Potencia (dBm)', 'RSSI (dBm)', 'Margem (dBm)', 'Frequencia', 'Data / Hora')
        self.tree = ttk.Treeview(table_container, columns=columns, show='headings', height=8)
        
        # Mapeamento de colunas para chaves de tradução
        self.column_translations = {
            'EPC': 'population.table_column_epc',
            'Apelido': 'population.table_column_nickname',
            'Comentario': 'population.table_column_comment',
            'Coordenadas': 'population.table_column_coordinates',
            'Potencia (dBm)': 'population.table_column_power',
            'RSSI (dBm)': 'population.table_column_rssi',
            'Margem (dBm)': 'population.table_column_margin',
            'Frequencia': 'population.table_column_frequency',
            'Data / Hora': 'population.table_column_datetime'
        }
        
        # Configura cabeçalhos e larguras das colunas
        default_column_widths = {
            'EPC': 200,  # Aumentado para acomodar EPC completo de 24 dígitos
            'Apelido': 100,  # Nova coluna para apelido da tag
            'Comentario': 120,
            'Coordenadas': 120,  # Nova coluna para coordenadas
            'Potencia (dBm)': 100,
            'RSSI (dBm)': 100,
            'Margem (dBm)': 100,  # Nova coluna para margem
            'Frequencia': 100,
            'Data / Hora': 120
        }
        
        # Carrega larguras salvas ou usa padrões
        column_widths = self._load_table_column_widths(default_column_widths)
        
        for col in columns:
            col_text = t(self.column_translations.get(col, col))
            self.tree.heading(col, text=f'{col_text} ↕', command=lambda c=col: self.sort_treeview(c))
            # CORREÇÃO: Adiciona stretch=True para a coluna EPC permitir expansão
            stretch = col == 'EPC'
            self.tree.column(col, width=column_widths.get(col, 100), anchor='center', stretch=stretch)
        
        # Binding para salvar larguras quando alteradas
        self.tree.bind('<ButtonRelease-1>', self._on_column_resize)
        
        # Scrollbar
        scrollbar = ttk.Scrollbar(table_container, orient='vertical', command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        # Binding para edição da coluna Comentário
        self.tree.bind('<Double-1>', self.on_tree_double_click)
        
        # Layout
        self.tree.pack(side='left', fill='both', expand=True)
        scrollbar.pack(side='right', fill='y')

        # Estilo para destacar linhas de tags não lidas (fundo rosa claro)
        try:
            self.tree.tag_configure('not_read', background='#ffe6f0')
        except Exception:
            pass


    def start_test(self):
        """Inicia o teste de população"""
        if self.test_in_progress:
            messagebox.showwarning(t('population.warning_test_in_progress'), t('population.warning_test_in_progress_msg'))
            return
        
        # Valida descrição obrigatória
        description = self.description_var.get().strip()
        if not description:
            messagebox.showerror(t('population.error_description_required'), t('population.error_description_required_msg'), parent=self)
            return
        
        # PROTEÇÃO DE TEMPERATURA: Verifica antes de iniciar
        temp = self.get_temperature()
        if temp is not None and temp >= TEMPERATURE_LIMIT:
            messagebox.showerror(t('population.error_safety_stop'), 
                               t('population.error_safety_stop_temp').format(temp=f"{temp:.1f}", limit=TEMPERATURE_LIMIT), 
                               parent=self)
            return
        
        # PROTEÇÃO DE POTÊNCIA REFLETIDA: Verifica antes de iniciar
        print(f"🔍 Population: Verificando potência refletida...")
        reflected_power = self.get_reflected_power()
        print(f"🔍 Population: Potência Refletida lida = {reflected_power} dBm (Limite: {REFLECTED_POWER_LIMIT} dBm)")
        if reflected_power is not None and reflected_power > REFLECTED_POWER_LIMIT:
            messagebox.showerror(t('population.error_reflected_power_high'), 
                               t('population.error_reflected_power_msg').format(power=f"{reflected_power:.1f}", limit=REFLECTED_POWER_LIMIT), 
                               parent=self)
            print(f"❌ Population: TESTE BLOQUEADO - Potência Refletida muito alta!")
            return
        else:
            print(f"✅ Population: Potência Refletida OK - Teste permitido")
        
        # Valida parâmetros
        try:
            frequency = float(self.frequency_var.get())
            power = float(self.power_var.get())
            
            if not (800 <= frequency <= 1000):
                messagebox.showerror(t('population.error_freq_blocked'), t('population.error_freq_blocked'))
                return
            
            if not (5 <= power <= 25):
                messagebox.showerror(t('population.error'), t('population.error_power_range'))
                return
                
        except ValueError:
            messagebox.showerror(t('population.error'), t('population.error_invalid_parameters'))
            return
        
        # Inicia teste em thread separada
        self.test_in_progress = True
        self.global_test_cancelled = False
        self.test_data = []
        
        self.start_button.config(state='disabled')
        self.stop_button.config(state='normal')
        print("🚀 Teste iniciado")
        
        # Notifica app_shell sobre teste ativo
        if self.app_shell:
            self.app_shell.set_test_running(True, self.module_name)
        
        # Inicia thread do teste
        self.active_test_thread = threading.Thread(target=self._run_test, daemon=True)
        self.active_test_thread.start()

    def stop_test(self):
        """Para o teste de população"""
        if not self.test_in_progress:
            return
        
        print("⏹️ Parando teste...")
        
        # CORREÇÃO: Marca cancelamento global
        self.global_test_cancelled = True
        
        # CORREÇÃO: Usa port_manager centralizado para fechar comunicação COM
        try:
            if hasattr(self, 'port_manager') and self.port_manager:
                self.port_manager.close_port(self.module_name)
                print("✅ Population: Comunicação COM fechada via port_manager ao abortar teste")
            elif HARDWARE_AVAILABLE and rfid_sdk:
                # Fallback: usa método direto se port_manager não estiver disponível
                with self.com_port_lock:
                    try:
                        rfid_sdk.UHF_RFID_Close(self.com_port)
                        print("✅ Population: Comunicação COM fechada ao abortar teste (fallback)")
                    except Exception as e:
                        print(f"⚠️ Population: Erro ao fechar COM ao abortar: {e}")
        except Exception as e:
            print(f"⚠️ Population: Erro geral ao fechar comunicação: {e}")
        
        # CORREÇÃO: Aguarda thread terminar com timeout
        if hasattr(self, 'active_test_thread') and self.active_test_thread and self.active_test_thread.is_alive():
            print("⏹️ Aguardando thread de teste terminar...")
            self.active_test_thread.join(timeout=2.0)
            if self.active_test_thread.is_alive():
                print("⚠️ Population: Thread não terminou naturalmente - forçando")
                self.active_test_thread.daemon = True
        
        print("✅ Population: Teste abortado com sucesso")

    def start_continuous_search(self):
        """Inicia o teste de população com potência crescente"""
        # Bloqueio em modo browser
        if not self.license_limits.get('is_licensed', False):
            messagebox.showwarning(t('population.warning_browser_mode'), t('population.warning_browser_mode_msg'))
            return
        if self.continuous_search_running:
            messagebox.showwarning(t('population.warning_test_in_progress'), t('population.warning_test_in_progress_msg'))
            return
        
        # Valida descrição obrigatória
        description = self.description_var.get().strip()
        if not description:
            messagebox.showerror(t('population.error_description_required'), t('population.error_description_required_msg'), parent=self)
            return
        
        # PROTEÇÃO DE TEMPERATURA: Verifica antes de iniciar
        temp = self.get_temperature()
        if temp is not None and temp >= TEMPERATURE_LIMIT:
            messagebox.showerror(t('population.error_safety_stop'), 
                               t('population.error_safety_stop_temp').format(temp=f"{temp:.1f}", limit=TEMPERATURE_LIMIT), 
                               parent=self)
            return
        
        # PROTEÇÃO DE POTÊNCIA REFLETIDA: Verifica antes de iniciar
        print(f"🔍 Population (Busca Contínua): Verificando potência refletida...")
        reflected_power = self.get_reflected_power()
        print(f"🔍 Population (Busca Contínua): Potência Refletida lida = {reflected_power} dBm (Limite: {REFLECTED_POWER_LIMIT} dBm)")
        if reflected_power is not None and reflected_power > REFLECTED_POWER_LIMIT:
            messagebox.showerror(t('population.error_reflected_power_high'), 
                               t('population.error_reflected_power_msg').format(power=f"{reflected_power:.1f}", limit=REFLECTED_POWER_LIMIT), 
                               parent=self)
            print(f"❌ Population (Busca Contínua): TESTE BLOQUEADO - Potência Refletida muito alta!")
            return
        else:
            print(f"✅ Population (Busca Contínua): Potência Refletida OK - Teste permitido")
            
        # Valida parâmetros antes de iniciar
        print("🔍 Validando parâmetros...")
        freq_valid = self._validate_frequency()
        power_valid = self._validate_power()
        print(f"🔍 Frequência válida: {freq_valid}, Potência válida: {power_valid}")
        if not freq_valid or not power_valid:
            print("❌ Parâmetros inválidos - teste não iniciado")
            return
            
        # Verifica se há tags selecionadas
        print(f"🔍 Verificando tags selecionadas: {len(self.selected_tags) if hasattr(self, 'selected_tags') else 'N/A'} tags")
        if not hasattr(self, 'selected_tags') or not self.selected_tags:
            messagebox.showwarning(t('population.warning_no_tags_selected'), t('population.warning_no_tags_selected_msg'))
            return
        
        # CORREÇÃO: Removida verificação duplicada de hardware (causava 2 bips)
        # A verificação será feita dentro do worker thread
        
        # Inicia teste
        self.continuous_search_running = True
        self.continuous_search_cancelled = False
        self.test_data.clear()
        self.cumulative_tags_detected = 0  # Contador acumulativo
        self.first_detection_registry.clear()  # Limpa registro de primeira detecção
        self.power_epcs_map.clear()  # Limpa mapeamento de EPCs por potência
        
        # Atualiza interface
        self.start_button.config(state='disabled')
        self.stop_button.config(state='normal')
        if hasattr(self, 'clear_test_button') and self.clear_test_button:
            self.clear_test_button.config(state='disabled')
        
        # Limpa tabela de dados e gráfico
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # Limpa apenas os dados do gráfico, mantendo as configurações
        self.ax.clear()
        
        # Reconfigura o gráfico mantendo as linhas de grade
        self.ax.set_title(t('population.graph_title'), fontsize=14)
        self.ax.set_xlabel(t('population.graph_xlabel'), fontsize=12)
        self.ax.set_ylabel(t('population.graph_ylabel'), fontsize=12)
        # MELHORIA: Aplica grade melhorada baseada no número de tags selecionadas
        selected_tags_count = len(self.selected_tags) if hasattr(self, 'selected_tags') else 0
        max_y = max(selected_tags_count, 10)  # Mínimo 10 para visualização
        self.ax.set_ylim(0, max_y)
        y_ticks = list(range(0, max_y + 1, 1))
        self.ax.set_yticks(y_ticks)
        # CORREÇÃO: Mantém os labels do eixo Y para mostrar a escala
        self.ax.set_yticklabels([str(i) for i in y_ticks])
        # Adiciona linhas de grade horizontais mais visíveis
        self.ax.grid(True, axis='y', alpha=0.4, linestyle='-', linewidth=0.8)
        # Mantém as linhas de grade verticais mais sutis
        self.ax.grid(True, axis='x', alpha=0.2, linestyle='-', linewidth=0.5)
        
        # Recria a linha de progresso após limpar o gráfico
        self.progress_line, = self.ax.plot([5, 5], [0, 0], color='red', linewidth=6, alpha=1.0, label='Progresso da Potência', zorder=15)
        print("🔴 Linha de progresso recriada após clear")
        
        # Reconfigura as escalas
        self._setup_plot_scales()
        self.canvas.draw()
        
        # CORREÇÃO: Reconfigura tooltip após limpar gráfico
        self.after(100, self._setup_bar_tooltip)
        print("🔄 Tooltip reconfigurado após limpar gráfico")
        
        # Inicia thread do teste
        self.continuous_search_thread = threading.Thread(target=self._power_test_worker)
        self.continuous_search_thread.daemon = True
        self.continuous_search_thread.start()
        
        print("🔍 Teste de população iniciado - potência crescente de 5dBm até máxima")

    def _power_test_worker(self):
        """Worker thread para teste de população com potência crescente (via UHFRFID.dll direto)."""
        try:
            # Configurações do teste
            frequency = float(self.frequency_var.get())
            if not self._is_frequency_allowed_by_license(frequency):
                self.after(0, self._test_finished, False, t('population.error_freq_blocked'))
                return

            max_power = self._clamp_power_to_license(float(self.power_var.get()))
            power_step = float(self.attenuator_var.get())
            min_power = 5.0  # Sempre começa em 5 dBm
            
            print(f"📊 Iniciando teste: Frequência={frequency}MHz, Potência={min_power}→{max_power}dBm, Step={power_step}dBm")

            # --- ABERTURA DA PORTA SERIAL VIA COMPORTMANAGER / DLL DIRETA ---
            port_opened_via_manager = False
            try:
                if hasattr(self, 'port_manager') and self.port_manager:
                    if not self.port_manager.open_port(self.module_name):
                        print("❌ Population: Falha ao abrir porta via port_manager")
                        self.after(0, self._test_finished, False, "Falha ao conectar hardware")
                        return
                    port_opened_via_manager = True
                    print("✅ Population: Porta aberta via port_manager para teste de população")
                else:
                    # Fallback direto via DLL
                    with self.com_port_lock:
                        if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) != 0:
                            print("❌ Population: Falha ao abrir porta via UHF_RFID_Open (fallback)")
                            self.after(0, self._test_finished, False, "Falha ao conectar hardware")
                            return
                    print("✅ Population: Porta aberta via UHF_RFID_Open (fallback)")

                # Configura frequência diretamente via DLL
                if not self._set_frequency_direct(frequency):
                    print(f"⚠️ Population: Falha ao configurar frequência via DLL: {frequency} MHz")

                # Loop principal do teste
                current_power = min_power
                while current_power <= max_power and not self.continuous_search_cancelled:
                    print(f"🔋 Testando potência: {current_power} dBm")
                    
                    # Configura potência via DLL
                    power_to_set = self._clamp_power_to_license(current_power)
                    self._set_tx_power(power_to_set)
                    # Pausa para hardware processar
                    time.sleep(0.1)
                    
                    # PROTEÇÃO DE POTÊNCIA REFLETIDA: Verifica DURANTE o teste
                    print(f"🔍 Population (Durante Teste): Verificando potência refletida em {current_power} dBm...")
                    reflected_power = self.get_reflected_power()
                    if reflected_power is not None:
                        print(f"📊 Population (Durante Teste): Potência Refletida = {reflected_power:.1f} dBm (Limite: {REFLECTED_POWER_LIMIT} dBm)")
                        if reflected_power > REFLECTED_POWER_LIMIT:
                            error_msg = t('population.error_reflected_power_during_test').format(
                                    power=f"{reflected_power:.1f}", 
                                    limit=REFLECTED_POWER_LIMIT)
                            print(f"❌ Population: TESTE INTERROMPIDO!")
                            
                            # Mostra mensagem de erro para o usuário
                            self.after(0, lambda: messagebox.showerror(t('population.error_reflected_power_high'), 
                                                                       error_msg, 
                                                                       parent=self))
                            self.after(0, self._test_finished, False, "Potência Refletida excedida")
                            return
                        else:
                            print(f"✅ Population (Durante Teste): Potência Refletida OK em {current_power} dBm")
                    else:
                        print(f"⚠️ Population (Durante Teste): Não foi possível ler Potência Refletida em {current_power} dBm")
                    
                    # Faz inventário com filtro por software (apenas tags selecionadas)
                    detected_tags = self._perform_filtered_inventory()
                    
                    # Calcula RSSI médio de TODAS as tags detectadas (não apenas as filtradas)
                    all_tags = self._scan_for_tags_internal()
                    rssi_avg = self._calculate_avg_rssi(all_tags)
                    
                    # Filtra apenas tags detectadas pela primeira vez em TODO o teste
                    first_detection_tags = []
                    for tag in detected_tags:
                        tag_epc = tag.epc if hasattr(tag, 'epc') else (tag.get('epc', 'N/A') if isinstance(tag, dict) else 'N/A')
                        
                        # Verifica se é primeira detecção desta tag em TODO o teste
                        if tag_epc not in self.first_detection_registry:
                            # Primeira detecção desta tag em TODO o teste
                            first_detection_tags.append(tag)
                            self.first_detection_registry[tag_epc] = current_power  # Registra a potência da primeira detecção
                            print(f"🆕 Primeira detecção: {tag_epc} em {current_power} dBm")
                        else:
                            print(f"⏭️ Tag já detectada: {tag_epc} (primeira vez em {self.first_detection_registry[tag_epc]} dBm) - ignorando em {current_power} dBm")
                    
                    # DEBUG: Mostra todas as tags detectadas nesta potência (não apenas primeiras detecções)
                    print(f"🔍 DEBUG POTÊNCIA {current_power} dBm:")
                    print(f"   📊 Tags detectadas nesta potência: {len(detected_tags)}")
                    print(f"   🆕 Primeiras detecções: {len(first_detection_tags)}")
                    print(f"   📈 Total acumulado: {self.cumulative_tags_detected}")
                    for tag in detected_tags:
                        tag_epc = tag.epc if hasattr(tag, 'epc') else (tag.get('epc', 'N/A') if isinstance(tag, dict) else 'N/A')
                        print(f"      - {tag_epc[:20]}...")
                    
                    # Atualiza contador acumulativo apenas com primeiras detecções
                    self.cumulative_tags_detected += len(first_detection_tags)
                    
                    # Armazena EPCs e apelidos para esta potência (para tooltip)
                    if first_detection_tags:
                        epcs_with_apelidos = []
                        for tag in first_detection_tags:
                            epc = tag.epc if hasattr(tag, 'epc') else (tag.get('epc', 'N/A') if isinstance(tag, dict) else 'N/A')
                            # Busca o apelido da tag registrada
                            apelido = self.get_tag_apelido(epc)
                            epcs_with_apelidos.append({'epc': epc, 'apelido': apelido})
                        self.power_epcs_map[current_power] = epcs_with_apelidos
                    
                    # Cria ponto de dados
                    data_point = {
                        'timestamp': datetime.now(),
                        'power': current_power,
                        'frequency': frequency,
                        'tags_detected': len(first_detection_tags),  # Apenas primeiras detecções
                        'cumulative_tags': self.cumulative_tags_detected,
                        'rssi_avg': rssi_avg,
                        'detected_epcs': [tag.epc if hasattr(tag, 'epc') else (tag.get('epc', 'N/A') if isinstance(tag, dict) else 'N/A') for tag in first_detection_tags],
                        'all_detected_epcs': [tag.epc if hasattr(tag, 'epc') else (tag.get('epc', 'N/A') if isinstance(tag, dict) else 'N/A') for tag in detected_tags],  # TODAS as tags detectadas
                        'comment': '',  # Comentário vazio por padrão
                        'apelido': '',  # Apelido vazio por padrão (ponto geral)
                        'skip_table': True  # Não inserir linha agregada; já inserimos por tag
                    }
                    
                    # Adiciona uma linha para cada tag detectada pela primeira vez
                    for tag in first_detection_tags:
                        # Obtém o EPC da tag
                        tag_epc = tag.epc if hasattr(tag, 'epc') else (tag.get('epc', 'N/A') if isinstance(tag, dict) else 'N/A')
                        
                        # Busca o apelido da tag registrada
                        tag_apelido = self.get_tag_apelido(tag_epc)
                        
                        # Cria ponto de dados individual para cada tag
                        individual_data_point = {
                            'timestamp': datetime.now(),
                            'power': current_power,
                            'frequency': frequency,
                            'tags_detected': 1,  # Apenas esta tag
                            'cumulative_tags': self.cumulative_tags_detected,
                            'rssi_avg': tag.rssi if hasattr(tag, 'rssi') else (tag.get('rssi', 0) if isinstance(tag, dict) else 0),
                            'detected_epcs': [tag_epc],
                            'comment': '',  # Comentário vazio por padrão
                            'apelido': tag_apelido  # Apelido da tag registrada
                        }
                        
                        # Adiciona aos dados do teste
                        self.test_data.append(individual_data_point)
                        
                        # Atualiza a tabela com uma linha única por tag
                        try:
                            self.after(0, self._update_table, individual_data_point)
                        except Exception:
                            pass
                    
                    # Atualiza interface sempre (mesmo quando não há tags detectadas)
                    self.after(0, self._update_test_display, data_point)
                    
                    # Incrementa potência
                    current_power += power_step
                    
                    # Pequena pausa entre medições
                    time.sleep(0.5)
            
                # Finaliza teste
                if not self.continuous_search_cancelled:
                    print("✅ Teste concluído com sucesso")
                    self.after(0, self._test_finished, True, "Teste concluído com sucesso")
                else:
                    print("⏹️ Teste interrompido pelo usuário")
                    self.after(0, self._test_finished, False, "Teste interrompido")

            finally:
                # Fecha porta aberta para o teste
                try:
                    if port_opened_via_manager and hasattr(self, 'port_manager') and self.port_manager:
                        self.port_manager.close_port(self.module_name)
                        print("✅ Population: Porta fechada via port_manager ao finalizar teste de população")
                    elif not port_opened_via_manager:
                        with self.com_port_lock:
                            try:
                                rfid_sdk.UHF_RFID_Close(self.com_port)
                                print("✅ Population: Porta fechada via UHF_RFID_Close (fallback)")
                            except Exception:
                                pass
                except Exception as e_close:
                    print(f"⚠️ Population: Erro ao fechar porta após teste: {e_close}")
                
        except Exception as e:
            print(f"❌ Erro no teste: {e}")
            self.after(0, self._test_finished, False, f"Erro: {str(e)}")

    def _perform_filtered_inventory(self):
        """Executa inventário filtrando apenas tags selecionadas (via UHFRFID.dll)."""
        try:
            print(f"🔍 Executando inventário...")
            print(f"🏷️ Tags selecionadas para filtro: {len(self.selected_tags)}")
            
            # Faz inventário normal usando a DLL diretamente
            all_tags = self._scan_for_tags_internal()
            print(f"📡 Inventário retornou: {len(all_tags)} tags")
            
            # Filtra apenas tags selecionadas.
            # IMPORTANTE: no módulo População, muitas vezes os EPCs do banco têm sufixos
            # (ex.: '...7117') enquanto o EPC físico lido pode ser apenas a parte base
            # (ex.: '...58893'). Para não perder detecções, consideramos match por
            # igualdade ou prefixo/sufixo.
            filtered_tags = []
            selected = list(self.selected_tags) if hasattr(self, 'selected_tags') else []
            for tag in all_tags:
                epc_value = None
                if hasattr(tag, 'epc'):
                    epc_value = str(tag.epc)
                elif isinstance(tag, dict) and 'epc' in tag:
                    epc_value = str(tag.get('epc'))

                if not epc_value:
                    continue

                matched = False
                for sel in selected:
                    sel_str = str(sel)
                    if (
                        epc_value == sel_str
                        or epc_value.startswith(sel_str)
                        or sel_str.startswith(epc_value)
                    ):
                        matched = True
                        break

                if matched:
                    filtered_tags.append(tag)
                    print(f"✅ Tag selecionada encontrada: {epc_value}")
            
            print(f"🏷️ Resultado: {len(all_tags)} tags totais, {len(filtered_tags)} tags selecionadas")
            return filtered_tags
            
        except Exception as e:
            print(f"⚠️ Erro no inventário: {e}")
            import traceback
            traceback.print_exc()
            return []

    def _calculate_avg_rssi(self, tags):
        """Calcula RSSI médio das tags detectadas"""
        if not tags:
            return 0.0
        
        rssi_sum = 0.0
        count = 0
        
        for tag in tags:
            if hasattr(tag, 'rssi'):
                rssi_sum += tag.rssi
                count += 1
            elif isinstance(tag, dict) and 'rssi' in tag:
                rssi_sum += tag['rssi']
                count += 1
        
        return rssi_sum / count if count > 0 else 0.0

    def _update_test_display(self, data_point):
        """Atualiza interface em tempo real durante o teste"""
        try:
            print(f"🔍 DEBUG _update_test_display: data_point recebido: {data_point}")
            
            # Atualiza tabela: evita linha agregada quando houver várias tags; linhas por tag são inseridas separadamente
            try:
                if data_point.get('skip_table'):
                    pass
                else:
                    detected_epcs = data_point.get('detected_epcs', [])
                    if isinstance(detected_epcs, (list, tuple)) and len(detected_epcs) <= 1:
                        self._update_table(data_point)
            except Exception:
                if not data_point.get('skip_table'):
                    self._update_table(data_point)
            
            # Atualiza gráfico
            self._update_plot(data_point)
            
            # Adiciona tags detectadas ao conjunto permanente
            all_detected_epcs = data_point.get('all_detected_epcs', [])
            print(f"🔍 DEBUG PERSISTÊNCIA: all_detected_epcs recebidos: {all_detected_epcs}")
            print(f"🔍 DEBUG PERSISTÊNCIA: detected_tags_permanent antes: {len(self.detected_tags_permanent)} tags")
            
            # CORREÇÃO: Adiciona TODAS as tags detectadas nesta potência ao conjunto permanente
            # Usa os dados já processados, não faz nova chamada de inventário
            for epc in all_detected_epcs:
                if epc not in self.detected_tags_permanent:
                    self.detected_tags_permanent.add(epc)
                    print(f"🟢 Tag {epc[:20]}... adicionada ao conjunto permanente de detectadas")
                else:
                    print(f"🔄 Tag {epc[:20]}... já estava no conjunto permanente")
            
            print(f"🔍 DEBUG PERSISTÊNCIA: detected_tags_permanent depois: {len(self.detected_tags_permanent)} tags")
            print(f"🔍 DEBUG PERSISTÊNCIA: EPCs no conjunto: {[epc[:20] + '...' for epc in list(self.detected_tags_permanent)[:5]]}")
            
            # Salva tags detectadas permanentemente
            self._save_detected_tags()
            
            # Atualiza gráfico 3D com tags detectadas
            self.update_3d_plot(test_running=True, detected_tags=all_detected_epcs)
            
            # Salva estado da interface após atualização
            self._save_complete_ui_state()
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar interface: {e}")

    def _update_plot(self, data_point):
        """Atualiza gráfico em tempo real"""
        try:
            # Adiciona barra vertical no gráfico
            power = data_point['power']
            cumulative_tags = data_point['cumulative_tags']
            
            # Ajusta largura da barra baseada no step de potência
            try:
                power_step = float(self.attenuator_var.get())
                if power_step == 0.5:
                    bar_width = 0.3  # Largura reduzida para step 0.5 dBm
                else:
                    bar_width = 0.6  # Largura reduzida para step 1.0 dBm
            except (ValueError, AttributeError):
                bar_width = 0.6  # Largura padrão reduzida se não conseguir obter o step
            
            # Desenha barra vertical - usa cumulative_tags como no arquivo antigo
            self.ax.bar(power, cumulative_tags, width=bar_width, alpha=0.8, color='lightblue')
            
            # Atualiza linha de progresso em tempo real (de 5 dBm até potência atual)
            min_power = self.license_limits.get('min_power', 5)
            self.progress_line.set_data([min_power, power], [0, 0])
            print(f"🔴 Linha de progresso atualizada: {min_power} dBm → {power} dBm")
            
            # Garante que as linhas de grade permaneçam visíveis
            self.ax.grid(True, alpha=0.3)
            
            # Atualiza gráfico
            self.canvas.draw()
            
            # Salva estado do gráfico após cada atualização
            self._save_graph_state()
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar gráfico: {e}")

    def stop_continuous_search(self):
        """Para o inventário contínuo"""
        if not self.continuous_search_running:
            return
            
        self.continuous_search_cancelled = True
        self.continuous_search_running = False
        
        # Para inventário contínuo se estiver usando hardware controller global
        if hasattr(self, 'hardware_controller') and self.hardware_controller:
            self.hardware_controller.stop_continuous_inventory()
            print("✅ Inventário contínuo parado via hardware controller")
        else:
            # CORREÇÃO: Usa port_manager centralizado para fechar porta COM
            if hasattr(self, 'port_manager') and self.port_manager:
                self.port_manager.close_port(self.module_name)
                print("✅ Porta COM fechada via port_manager após inventário contínuo")
            else:
                # Fallback: fecha porta COM diretamente
                with self.com_port_lock:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    print("✅ Porta COM fechada após inventário contínuo (fallback)")
        
        # Atualiza interface
        self.start_button.config(state='normal')
        self.stop_button.config(state='disabled')
        if hasattr(self, 'clear_test_button') and self.clear_test_button:
            self.clear_test_button.config(state='normal')
        
        print(f"⏹️ Busca contínua parada - {len(self.live_tags)} tags únicas detectadas")

    def _run_test(self):
        """Executa o teste de população em thread separada"""
        try:
            # Parâmetros do teste
            frequency = float(self.frequency_var.get())
            # Validação de frequência com faixas/exclusões
            if not self._validate_frequency_against_license(frequency):
                self.after(0, self._test_finished, False, t('population.error_freq_blocked'))
                return
            power = float(self.power_var.get())
            description = self.description_var.get()
            
            # Inicializa dados
            self.start_time = datetime.now()
            self.test_data = []
            
            print(f"🚀 Iniciando teste de população - Frequência: {frequency}MHz, Potência: {power}dBm")
            
            # Conecta ao reader
            if not HARDWARE_AVAILABLE or not rfid_sdk:
                print("⚠️ Hardware não disponível - simulando teste")
                self._simulate_test()
                return
            
            # CORREÇÃO: Usa port_manager centralizado para gerenciar comunicação COM
            if hasattr(self, 'port_manager') and self.port_manager:
                if not self.port_manager.open_port(self.module_name):
                    print("❌ Falha ao conectar ao reader via port_manager")
                    self._test_finished(False, "Falha na conexão com o reader")
                    return
                
                try:
                    # Configura potência
                    self._set_tx_power(power)
                    
                    # Executa teste
                    self._execute_population_test(frequency, power, description)
                    
                finally:
                    self.port_manager.close_port(self.module_name)
            else:
                # Fallback: usa método direto se port_manager não estiver disponível
                with self.com_port_lock:
                    if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) != 0:
                        print("❌ Falha ao conectar ao reader (fallback)")
                        self._test_finished(False, "Falha na conexão com o reader")
                        return
                    
                    try:
                        # Configura potência
                        self._set_tx_power(power)
                        
                        # Executa teste
                        self._execute_population_test(frequency, power, description)
                        
                    finally:
                        rfid_sdk.UHF_RFID_Close(self.com_port)
            
            self._test_finished(True, "Teste concluído com sucesso")
            
        except Exception as e:
            print(f"❌ Erro durante o teste: {e}")
            traceback.print_exc()
            self._test_finished(False, f"Erro: {str(e)}")

    def _simulate_test(self):
        """Simula um teste quando hardware não está disponível"""
        import random
        
        start_time = time.time()
        duration = 60  # Duração fixa de 60 segundos para simulação
        while time.time() - start_time < duration and not self.global_test_cancelled:
            # Simula detecção de tags
            tags_detected = random.randint(0, 10)
            rssi_avg = random.uniform(-60, -30)
            
            timestamp = datetime.now()
            elapsed_time = time.time() - start_time
            
            data_point = {
                'timestamp': timestamp,
                'elapsed_time': elapsed_time,
                'tags_detected': tags_detected,
                'cumulative_tags': tags_detected,  # Adiciona cumulative_tags para o gráfico
                'rssi_avg': rssi_avg,
                'frequency': float(self.frequency_var.get()),
                'power': float(self.power_var.get())
            }
            
            self.test_data.append(data_point)
            
            # Atualiza interface
            self.after(0, self._update_display, data_point)
            
            time.sleep(1)  # Atualiza a cada segundo

    def _execute_population_test(self, frequency, power, description):
        """Executa o teste real de população"""
        start_time = time.time()
        duration = 60  # Duração fixa de 60 segundos
        
        while time.time() - start_time < duration and not self.global_test_cancelled:
            # Executa inventário
            tags_detected, rssi_avg = self._perform_inventory()
            
            timestamp = datetime.now()
            elapsed_time = time.time() - start_time
            
            data_point = {
                'timestamp': timestamp,
                'elapsed_time': elapsed_time,
                'tags_detected': tags_detected,
                'cumulative_tags': tags_detected,  # Adiciona cumulative_tags para o gráfico
                'rssi_avg': rssi_avg,
                'frequency': frequency,
                'power': power
            }
            
            self.test_data.append(data_point)
            
            # Atualiza interface
            self.after(0, self._update_display, data_point)
            
            time.sleep(1)  # Atualiza a cada segundo

    def _perform_inventory(self):
        """Executa inventário de tags usando a mesma lógica bruta do módulo RSSI/Power."""
        try:
            tags = self._scan_for_tags_internal()
            if not tags:
                print("📡 Population _perform_inventory: 0 tags retornadas pelo inventário bruto")
                return 0, 0.0

            rssi_values = [info.rssi for info in tags]
            avg_rssi = sum(rssi_values) / len(rssi_values)
            print(f"📡 Population _perform_inventory: {len(tags)} tags únicas, RSSI médio={avg_rssi:.1f} dBm")
            return len(tags), avg_rssi

        except Exception as e:
            print(f"⚠️ Population: Erro no inventário: {e}")
            return 0, 0.0

    def _scan_for_tags_internal(self, duration_sec=0.5):
        """Realiza inventário bruto via DLL e retorna lista de TagInfo."""
        tags_found = {}
        try:
            if not HARDWARE_AVAILABLE:
                print("⚠️ Population: _scan_for_tags_internal chamado sem hardware disponível")
                return []

            start_time = time.time()
            while time.time() - start_time < duration_sec and not self.global_test_cancelled:
                status = None
                raw_bytes = b""

                # Preferencialmente usa o COMPortManager
                if hasattr(self, "port_manager") and self.port_manager:
                    result = self.port_manager.send_command(self.module_name, RFID_CMD_INV_TAG, bytes([0x00, 0x64]), output_buffer_size=256)
                    if result is None:
                        status = None
                    else:
                        status, raw_bytes, out_len_val = result
                else:
                    if not rfid_sdk:
                        break
                    out_buf = ctypes.create_string_buffer(256)
                    out_len = ctypes.c_uint(0)
                    status = rfid_sdk.UHF_RFID_Set(
                        RFID_CMD_INV_TAG,
                        bytes([0x00, 0x64]),
                        2,
                        out_buf,
                        ctypes.byref(out_len),
                    )
                    raw_bytes = out_buf.raw[:out_len.value]

                if status == 0 and raw_bytes and len(raw_bytes) > 5:
                    try:
                        epc = raw_bytes[2:-3].hex().upper()
                        rssi_bytes = raw_bytes[-3:-1]
                        rssi = struct.unpack('>h', rssi_bytes)[0] / 10.0
                        if -100 <= rssi <= 0 and (epc not in tags_found or rssi > tags_found[epc].rssi):
                            tags_found[epc] = TagInfo(epc=epc, rssi=rssi)
                    except (struct.error, IndexError):
                        pass

                time.sleep(0.01)

        except Exception as e:
            print(f"⚠️ Population: Erro em _scan_for_tags_internal: {e}")

        return list(tags_found.values())

    def _set_tx_power(self, power):
        """Configura potência de transmissão"""
        try:
            # Garante que a potência respeita os limites da licença
            power = self._clamp_power_to_license(float(power))
            # Converte potência para valor do comando (mesmo formato dos outros módulos)
            power_val = int(power * 100)  # Converte para centésimos
            power_data = bytes([0, 0]) + power_val.to_bytes(2, 'big') * 2

            # Preferencialmente usa o COMPortManager (mesmo caminho dos outros módulos)
            status = None
            if hasattr(self, "port_manager") and self.port_manager:
                result = self.port_manager.send_command(self.module_name, RFID_CMD_SET_TXPOWER, power_data, output_buffer_size=32)
                if result is not None:
                    status, _, _ = result
            else:
                # Fallback direto via DLL (mantido para compatibilidade)
                out_buf = ctypes.create_string_buffer(32)
                out_len = ctypes.c_uint(0)
                status = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, out_buf, ctypes.byref(out_len))

            if status == 0:
                print(f"✅ Potência configurada para {power} dBm (respeitando licença)")
            else:
                print(f"⚠️ Falha ao configurar potência - status: {status}")
                
        except Exception as e:
            print(f"⚠️ Erro ao configurar potência: {e}")

    def _set_frequency_direct(self, freq_mhz: float) -> bool:
        """Configura a frequência diretamente via DLL (mesma lógica dos outros módulos)."""
        try:
            freq_data = (1).to_bytes(1, 'big') + int(freq_mhz * 1000).to_bytes(3, 'big')
            status = None

            # Preferencialmente usa o COMPortManager (mesmo caminho dos outros módulos)
            if hasattr(self, "port_manager") and self.port_manager:
                result = self.port_manager.send_command(self.module_name, RFID_CMD_SET_FREQ_TABLE, freq_data, output_buffer_size=32)
                if result is not None:
                    status, _, _ = result
            else:
                # Fallback direto via DLL
                out_buf = ctypes.create_string_buffer(32)
                out_len = ctypes.c_uint(0)
                status = rfid_sdk.UHF_RFID_Set(
                    RFID_CMD_SET_FREQ_TABLE,
                    ctypes.c_char_p(freq_data),
                    4,
                    out_buf,
                    ctypes.byref(out_len),
                )
            if status == 0:
                print(f"✅ Population: Frequência configurada via _set_frequency_direct: {freq_mhz} MHz")
                return True
            else:
                print(f"⚠️ Population: Falha em _set_frequency_direct (status={status}) para {freq_mhz} MHz")
                return False
        except Exception as e:
            print(f"⚠️ Population: Erro em _set_frequency_direct: {e}")
            return False

    def _update_display(self, data_point):
        """Atualiza a exibição com novo dado"""
        try:
            # Atualiza gráfico de barras (módulo população)
            self._update_plot(data_point)
            
            # Atualiza tabela
            self._update_table(data_point)
            
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar display: {e}")

    def _update_table(self, data_point):
        """Atualiza a tabela de dados"""
        try:
            # Prepara EPCs detectados para exibição de forma segura
            detected_epcs = data_point.get('detected_epcs', [])
            if isinstance(detected_epcs, (list, tuple)) and detected_epcs:
                # Mostra EPC completo como no módulo Threshold
                epc_display = ', '.join(str(epc) for epc in detected_epcs)  # Mostra todos os EPCs completos
            else:
                # Não adiciona registros quando não há tags detectadas
                return
            
            # Adiciona nova linha com dados acumulativos
            comment = data_point.get('comment', '')  # Carrega comentário salvo ou vazio
            apelido = data_point.get('apelido', '')  # Carrega apelido salvo ou vazio
            
            # CORREÇÃO: Se não há apelido nos dados, busca no sistema de apelidos
            if not apelido and detected_epcs:
                # Pega o apelido da primeira tag detectada
                first_epc = detected_epcs[0]
                apelido = self.get_tag_apelido(first_epc)
                print(f"🔍 DEBUG _update_table: Apelido não encontrado nos dados, buscando para EPC {first_epc[:20]}... -> '{apelido}'")
            
            # DEBUG: Verifica se o apelido está sendo processado corretamente
            if apelido:
                print(f"🔍 DEBUG _update_table: EPCs={detected_epcs}, Apelido='{apelido}'")
            else:
                print(f"⚠️ DEBUG _update_table: Nenhum apelido encontrado para EPCs={detected_epcs}")
            
            # Busca coordenadas das tags detectadas
            coordinates = ''
            detected_epcs = data_point.get('detected_epcs', [])
            if detected_epcs:
                # Pega as coordenadas da primeira tag detectada (ou concatena se múltiplas)
                for epc in detected_epcs:
                    tag_coords = self.get_tag_coordinates(epc)
                    if tag_coords:
                        coordinates = tag_coords
                        break  # Usa apenas a primeira tag com coordenadas
            
            # Calcula margem (potência máxima - potência atual)
            try:
                max_power = float(self.power_var.get()) if hasattr(self, 'power_var') else 25.0
                current_power = float(data_point.get('power', 0))
                margin = max_power - current_power
            except (ValueError, AttributeError):
                margin = 0.0
            
            # Se a tag não foi lida, exibe '-' para Potencia, RSSI, Margem e Data/Hora
            if comment == '(NAO LIDA)':
                potencia_str = '-'
                rssi_str = '-'
                margem_str = '-'
                data_hora_str = '-'
            else:
                potencia_str = f"{data_point.get('power', 0):.1f}"
                rssi_str = f"{data_point.get('rssi_avg', 0):.1f}"
                margem_str = f"{margin:.1f}"
                # Formata data/hora conforme idioma
                if isinstance(data_point.get('timestamp'), datetime):
                    translator = get_translator()
                    if translator.get_language() == 'en':
                        data_hora_str = data_point.get('timestamp').strftime('%m/%d/%y %H:%M:%S')
                    else:
                        data_hora_str = data_point.get('timestamp').strftime('%d/%m/%Y %H:%M:%S')
                else:
                    data_hora_str = str(data_point.get('timestamp', ''))
            
            values = (
                str(epc_display),  # EPC (garante que seja string para manter zeros à esquerda)
                apelido,  # Apelido (carrega apelido salvo ou vazio)
                comment,  # Comentario (carrega comentário salvo ou vazio)
                coordinates,  # Coordenadas (carrega coordenadas salvas ou vazio)
                potencia_str,  # Potencia (dBm)
                rssi_str,  # RSSI (dBm)
                margem_str,  # Margem (dBm)
                f"{data_point.get('frequency', 915):.0f}",  # Frequencia
                data_hora_str  # Data / Hora
            )
            
            
            # CORREÇÃO: Aplica tag visual para tags reprovadas (igualdade visual)
            if comment == '(NAO LIDA)':
                self.tree.insert('', 'end', values=values, tags=('not_read',))
            else:
                self.tree.insert('', 'end', values=values)
            
            # Mantém apenas as últimas 100 linhas
            items = self.tree.get_children()
            if len(items) > 100:
                self.tree.delete(items[0])
            
            # Scroll para a última linha
            self.tree.see(self.tree.get_children()[-1])
            
            # Salva dados de teste automaticamente (apenas os últimos 10 pontos para não sobrecarregar)
            if len(self.test_data) % 10 == 0:  # Salva a cada 10 pontos
                self._save_test_data()
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar tabela: {e}")

    def _update_table_from_test_data(self):
        """Atualiza a tabela com todos os dados de teste (usado após importação)"""
        try:
            # Limpa a tabela
            for item in self.tree.get_children():
                self.tree.delete(item)
            
            # Reconstrói a tabela com todos os dados de teste
            for data_point in self.test_data:
                self._update_table(data_point)
            
            print(f"✅ Tabela atualizada com {len(self.test_data)} pontos de dados")
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar tabela a partir dos dados de teste: {e}")

    def sort_treeview(self, column):
        """
        Ordena a tabela de dados do teste pela coluna especificada
        """
        try:
            # Obtém todos os itens da tabela
            items = [(self.tree.set(item, column), item) for item in self.tree.get_children('')]
            
            # Verifica se é a mesma coluna para alternar direção
            if self.current_sort_column == column:
                self.current_sort_reverse = not self.current_sort_reverse
            else:
                self.current_sort_column = column
                self.current_sort_reverse = False
            
            # Determina o tipo de ordenação baseado na coluna
            if column == "EPC":
                # Ordenação alfabética para EPC
                items.sort(key=lambda x: x[0].lower(), reverse=self.current_sort_reverse)
            elif column == "Comentario":
                # Ordenação alfabética para comentário
                items.sort(key=lambda x: x[0].lower(), reverse=self.current_sort_reverse)
            elif column == "Potencia (dBm)":
                # Ordenação numérica decimal para potência (trata '-' como valor muito baixo)
                items.sort(key=lambda x: float(x[0]) if x[0] not in ["", "-"] else -999, reverse=self.current_sort_reverse)
            elif column == "RSSI (dBm)":
                # Ordenação numérica decimal para RSSI (trata '-' como valor muito baixo)
                items.sort(key=lambda x: float(x[0]) if x[0] not in ["", "-"] else -999, reverse=self.current_sort_reverse)
            elif column == "Margem (dBm)":
                # Ordenação numérica decimal para Margem (trata '-' como valor muito baixo)
                items.sort(key=lambda x: float(x[0]) if x[0] not in ["", "-"] else -999, reverse=self.current_sort_reverse)
            elif column == "Frequencia":
                # Ordenação numérica para frequência
                items.sort(key=lambda x: float(x[0]) if x[0] != "" else 0, reverse=self.current_sort_reverse)
            elif column == "Data / Hora":
                # Ordenação de data/hora
                items.sort(key=lambda x: x[0], reverse=self.current_sort_reverse)
            else:
                # Ordenação alfabética para outras colunas
                items.sort(key=lambda x: x[0].lower(), reverse=self.current_sort_reverse)
            
            # Reorganiza os itens na tabela
            for index, (val, item) in enumerate(items):
                self.tree.move(item, '', index)
            
            # Atualiza o símbolo de ordenação no cabeçalho
            self.update_sort_indicator(column)
            
            # Salva estado da tabela após ordenação
            self._save_table_state()
            
        except Exception as e:
            print(f"❌ Erro na ordenação: {e}")
    
    def update_sort_indicator(self, column):
        """
        Atualiza os símbolos de ordenação nos cabeçalhos
        """
        try:
            # Remove símbolos de todas as colunas
            columns = ['EPC', 'Apelido', 'Comentario', 'Coordenadas', 'Potencia (dBm)', 'RSSI (dBm)', 'Frequencia', 'Data / Hora']
            for col in columns:
                current_text = self.tree.heading(col)['text']
                # Remove símbolos existentes
                if " ↑" in current_text:
                    current_text = current_text.replace(" ↑", "")
                elif " ↓" in current_text:
                    current_text = current_text.replace(" ↓", "")
                # Adiciona símbolo neutro
                if not current_text.endswith(" ↕"):
                    current_text += " ↕"
                self.tree.heading(col, text=current_text)
            
            # Adiciona símbolo de direção na coluna atual
            current_text = self.tree.heading(column)['text']
            current_text = current_text.replace(" ↕", "")
            if self.current_sort_reverse:
                current_text += " ↓"
            else:
                current_text += " ↑"
            self.tree.heading(column, text=current_text)
            
        except Exception as e:
            print(f"❌ Erro ao atualizar indicador de ordenação: {e}")

    def on_tree_double_click(self, event):
        """Manipula o duplo-clique para edição in-line das colunas Apelido e Comentário."""
        
        # Destrói qualquer widget de edição que já exista
        if hasattr(self, '_edit_entry') and self._edit_entry.winfo_exists():
            self._edit_entry.destroy()

        region = self.tree.identify("region", event.x, event.y)
        if region != "cell":
            return

        column_id = self.tree.identify_column(event.x)
        # Permite edição APENAS da coluna Comentario (#3)
        if column_id != '#3':
            return
            
        item_id = self.tree.identify_row(event.y)
        if not item_id:
            return

        # Obtém as coordenadas da célula para posicionar o widget de edição
        x, y, width, height = self.tree.bbox(item_id, column_id)

        # Obtém o valor atual da coluna Comentario
        current_value = self.tree.set(item_id, 'Comentario')
        self._editing_column = 'Comentario'

        # Cria e posiciona o widget de edição
        self._edit_var = tk.StringVar(value=current_value)
        self._edit_entry = ttk.Entry(self.tree, textvariable=self._edit_var)
        self._edit_entry.place(x=x, y=y, width=width, height=height)
        self._edit_entry.focus_set()
        self._edit_entry.selection_range(0, 'end')

        # Associa eventos para salvar ou cancelar a edição
        self._edit_entry.bind("<Return>", lambda e: self.save_edited_cell(item_id))
        self._edit_entry.bind("<FocusOut>", lambda e: self.save_edited_cell(item_id))
        self._edit_entry.bind("<Escape>", lambda e: self.cancel_edit_cell())

    def save_edited_cell(self, item_id):
        """Salva a célula que foi editada (apelido, comentário ou coordenadas)."""
        if not (hasattr(self, '_edit_entry') and self._edit_entry.winfo_exists()):
            return
            
        try:
            new_value = self._edit_var.get().strip()
            editing_column = getattr(self, '_editing_column', 'Comentario')  # Fallback para Comentario
            
            # Destrói o widget de edição mas mantém a informação da coluna
            if hasattr(self, '_edit_entry') and self._edit_entry.winfo_exists():
                self._edit_entry.destroy()
                delattr(self, '_edit_entry')

            # Atualiza na interface
            self.tree.set(item_id, editing_column, new_value)
            
            # Atualiza nos dados de teste
            epc = self.tree.set(item_id, 'EPC')
            potencia = self.tree.set(item_id, 'Potencia (dBm)')
            rssi = self.tree.set(item_id, 'RSSI (dBm)')
            frequencia = self.tree.set(item_id, 'Frequencia')
            data_hora = self.tree.set(item_id, 'Data / Hora')
            
            # Encontra o item correspondente nos dados de teste
            if hasattr(self, 'test_data') and isinstance(self.test_data, (list, tuple)):
                print(f"🔍 Procurando data_point com: EPC={epc}, Power={potencia}, RSSI={rssi}, Freq={frequencia}, Data={data_hora}")
                for i, data_point in enumerate(self.test_data):
                    # Debug: mostra os valores do data_point atual
                    dp_epcs = data_point.get('detected_epcs', [])
                    dp_power = f"{data_point.get('power', 0):.1f}"
                    dp_rssi = f"{data_point.get('rssi_avg', 0):.1f}"
                    dp_freq = f"{data_point.get('frequency', 0):.0f}"
                    # Formata data conforme idioma
                    if hasattr(data_point.get('timestamp', ''), 'strftime'):
                        translator = get_translator()
                        if translator.get_language() == 'en':
                            dp_time = data_point.get('timestamp', '').strftime('%m/%d/%y %H:%M:%S')
                        else:
                            dp_time = data_point.get('timestamp', '').strftime('%d/%m/%Y %H:%M:%S')
                    else:
                        dp_time = str(data_point.get('timestamp', ''))
                    
                    print(f"🔍 Data_point {i}: EPCs={dp_epcs}, Power={dp_power}, RSSI={dp_rssi}, Freq={dp_freq}, Data={dp_time}")
                    
                    # Compara data formatada conforme idioma
                    translator = get_translator()
                    if translator.get_language() == 'en':
                        formatted_timestamp = data_point['timestamp'].strftime('%m/%d/%y %H:%M:%S')
                    else:
                        formatted_timestamp = data_point['timestamp'].strftime('%d/%m/%Y %H:%M:%S')
                    
                    if (data_point.get('detected_epcs', []) and 
                        epc in data_point['detected_epcs'] and
                        f"{data_point['power']:.1f}" == potencia and
                        f"{data_point['rssi_avg']:.1f}" == rssi and
                        f"{data_point['frequency']:.0f}" == frequencia and
                        formatted_timestamp == data_hora):
                        
                        # Atualiza o campo correspondente nos dados
                        if editing_column == 'Apelido':
                            if 'apelido' not in data_point:
                                data_point['apelido'] = ""
                            data_point['apelido'] = new_value
                            print(f"✅ Apelido atualizado: '{new_value}' no data_point {i}")
                            
                            # Sincroniza com os apelidos das tags registradas
                            if data_point.get('detected_epcs'):
                                for epc in data_point['detected_epcs']:
                                    if not hasattr(self, 'tag_apelidos'):
                                        self.tag_apelidos = {}
                                    self.tag_apelidos[epc] = new_value
                                self._save_tag_apelidos()
                                print(f"✅ Apelido sincronizado com tags registradas")
                        elif editing_column == 'Comentario':
                            if 'comment' not in data_point:
                                data_point['comment'] = ""
                            data_point['comment'] = new_value
                            print(f"✅ Comentário atualizado: '{new_value}' no data_point {i}")
                        elif editing_column == 'Coordenadas':
                            if 'coordinates' not in data_point:
                                data_point['coordinates'] = ""
                            data_point['coordinates'] = new_value
                            print(f"✅ Coordenadas atualizadas: '{new_value}' no data_point {i}")
                        
                        # Salva os dados atualizados
                        self._save_test_data()
                        
                        # Atualiza o power_epcs_map para o tooltip do gráfico em tempo real
                        self._update_power_epcs_map_for_tooltip()
                        break
            
            # Limpa o atributo _editing_column após salvar
            if hasattr(self, '_editing_column'):
                delattr(self, '_editing_column')
            
        except Exception as e:
            editing_column = getattr(self, '_editing_column', 'campo')
            print(f"❌ Erro ao salvar {editing_column.lower()} editado: {e}")
            # Limpa o atributo mesmo em caso de erro
            if hasattr(self, '_editing_column'):
                delattr(self, '_editing_column')

    def _update_power_epcs_map_for_tooltip(self):
        """Atualiza o power_epcs_map em tempo real para o tooltip do gráfico"""
        try:
            # Reconstrói o mapa de EPCs e apelidos por potência
            self.power_epcs_map.clear()
            for data_point in self.test_data:
                power = data_point.get('power')
                epcs = data_point.get('detected_epcs')
                apelido = data_point.get('apelido', '')
                if power is not None and epcs:
                    # Garante que a chave exista antes de adicionar
                    if power not in self.power_epcs_map:
                        self.power_epcs_map[power] = []
                    # Adiciona EPCs com apelidos, evitando duplicatas
                    for epc in epcs:
                        # Verifica se o EPC já existe na lista
                        epc_exists = any(item['epc'] == epc for item in self.power_epcs_map[power])
                        if not epc_exists:
                            self.power_epcs_map[power].append({'epc': epc, 'apelido': apelido})
                        else:
                            # Se o EPC já existe, atualiza o apelido
                            for item in self.power_epcs_map[power]:
                                if item['epc'] == epc:
                                    item['apelido'] = apelido
                                    break
            
            print("✅ Power_epcs_map atualizado em tempo real para tooltip")
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar power_epcs_map: {e}")

    def cancel_edit_cell(self, event=None):
        """Cancela o processo de edição e destrói o widget."""
        if hasattr(self, '_edit_entry') and self._edit_entry.winfo_exists():
            self._edit_entry.destroy()
            delattr(self, '_edit_entry')
        if hasattr(self, '_editing_column'):
            delattr(self, '_editing_column')


    def on_tags_tree_double_click(self, event):
        """Manipula duplo clique na lista de tags registradas para editar apelido."""
        print(f"🖱️ Duplo clique detectado na lista de tags registradas")
        item_id = self.tags_tree.identify_row(event.y)
        print(f"🖱️ Item ID identificado: {item_id}")
        if not item_id:
            print("⚠️ Nenhum item identificado no duplo clique")
            return
        
        # Obtém o EPC da tag
        epc = self.tags_tree.set(item_id, 'tag')
        print(f"🖱️ EPC obtido: {epc}")
        
        # Obtém o apelido atual (se existir)
        current_apelido = self.get_tag_apelido(epc)
        print(f"🖱️ Apelido atual: '{current_apelido}'")
        
        # Abre popup para editar apelido
        print(f"🖱️ Abrindo popup para editar apelido")
        self.edit_tag_apelido_popup(epc, current_apelido)

    def edit_tag_apelido_inline(self, item_id, epc, current_apelido):
        """Cria edição inline para o apelido na lista de tags registradas."""
        print(f"✏️ Iniciando edição inline - EPC: {epc}, Apelido atual: '{current_apelido}'")
        
        # Cancela qualquer edição anterior
        self.cancel_edit_cell()
        
        # Obtém a posição da célula
        bbox = self.tags_tree.bbox(item_id, 'apelido')
        if not bbox:
            print("⚠️ Não foi possível obter a posição da célula")
            return
        
        x, y, width, height = bbox
        
        # Cria o campo de entrada
        self._edit_entry = tk.Entry(self.tags_tree, font=('Arial', 9))
        self._edit_entry.place(x=x, y=y, width=width, height=height)
        self._edit_entry.insert(0, current_apelido)
        self._edit_entry.select_range(0, tk.END)
        self._edit_entry.focus()
        
        # Armazena informações da edição
        self._editing_item_id = item_id
        self._editing_epc = epc
        
        # Bind eventos
        self._edit_entry.bind('<Return>', self.save_tag_apelido_inline)
        self._edit_entry.bind('<Escape>', self.cancel_tag_apelido_inline)
        self._edit_entry.bind('<FocusOut>', self.save_tag_apelido_inline)

    def save_tag_apelido_inline(self, event=None):
        """Salva o apelido editado inline."""
        if not hasattr(self, '_edit_entry') or not self._edit_entry.winfo_exists():
            return
        
        new_apelido = self._edit_entry.get().strip()
        epc = getattr(self, '_editing_epc', '')
        
        print(f"💾 Salvando apelido inline - EPC: {epc}, Novo apelido: '{new_apelido}'")
        
        # CORREÇÃO: Valida o apelido (máximo 4 caracteres alfanuméricos)
        if len(new_apelido) > 4:
            new_apelido = new_apelido[:4]
            print(f"⚠️ Apelido truncado para 4 caracteres: '{new_apelido}'")
        
        # Remove apenas caracteres perigosos, mantendo caracteres especiais comuns
        import re
        # Permite letras, números, espaços e caracteres especiais comuns: # & % @ - _ . , ! ? ( )
        new_apelido = re.sub(r'[^a-zA-Z0-9\s#&%@\-_.,!?()]', '', new_apelido)
        
        # Salva o apelido usando a função com validação
        self.save_tag_info(epc, new_apelido, "")
        
        # Limpa a edição
        self.cancel_edit_cell()

    def cancel_tag_apelido_inline(self, event=None):
        """Cancela a edição inline do apelido."""
        print("❌ Cancelando edição inline do apelido")
        self.cancel_edit_cell()

    def get_tag_apelido(self, epc):
        """Obtém o apelido de uma tag pelo EPC."""
        print(f"🔍 Buscando apelido para EPC: {epc}")
        
        # PRIMEIRO: Verifica se há um apelido salvo no sistema de apelidos das tags registradas
        if hasattr(self, 'tag_apelidos') and epc in self.tag_apelidos:
            apelido = self.tag_apelidos[epc]
            print(f"🔍 Apelido encontrado em tag_apelidos: '{apelido}'")
            return apelido
        
        # SEGUNDO: Verifica se a tag está nos dados de teste (para casos especiais)
        if hasattr(self, 'test_data') and isinstance(self.test_data, (list, tuple)):
            for data_point in self.test_data:
                if data_point.get('detected_epcs') and epc in data_point['detected_epcs']:
                    apelido = data_point.get('apelido', '')
                    if apelido:  # Só retorna se o apelido não estiver vazio
                        print(f"🔍 Apelido encontrado nos dados de teste: '{apelido}'")
                        return apelido
        
        print(f"🔍 Nenhum apelido encontrado para EPC: {epc}")
        return ''
    
    def get_tag_coordinates(self, epc):
        """Obtém as coordenadas de uma tag pelo EPC do banco de dados."""
        try:
            tag_data = self.database.get_tag_by_epc(epc)
            if tag_data and 'coordinates' in tag_data:
                coordinates = tag_data.get('coordinates', '')
                print(f"🔍 Coordenadas encontradas para EPC {epc}: '{coordinates}'")
                return coordinates
            else:
                print(f"🔍 Nenhuma coordenada encontrada para EPC {epc}")
                return ''
        except Exception as e:
            print(f"⚠️ Erro ao buscar coordenadas para EPC {epc}: {e}")
            return ''

    def edit_tag_apelido_popup(self, epc, current_apelido):
        """Abre popup para editar apelido e coordenadas de uma tag."""
        print(f"🖼️ Criando popup para editar apelido e coordenadas - EPC: {epc}, Apelido atual: '{current_apelido}'")
        
        # Obtém as coordenadas atuais
        current_coordinates = self.get_tag_coordinates(epc)
        print(f"🖼️ Coordenadas atuais: '{current_coordinates}'")
        
        # Cria janela popup
        popup = tk.Toplevel(self.master)
        popup.title(t('population.edit_tag'))
        popup.geometry("400x220")
        popup.resizable(False, False)
        popup.transient(self.master)
        popup.grab_set()
        
        # Centraliza a janela
        popup.update_idletasks()
        x = (popup.winfo_screenwidth() // 2) - (400 // 2)
        y = (popup.winfo_screenheight() // 2) - (220 // 2)
        popup.geometry(f"400x220+{x}+{y}")
        
        # Frame principal
        main_frame = tk.Frame(popup, padx=20, pady=20)
        main_frame.pack(fill='both', expand=True)
        
        # Label do EPC
        epc_label = tk.Label(main_frame, text=f"EPC: {epc}", font=("Helvetica", 10))
        epc_label.pack(pady=(0, 10))
        
        # Frame para entrada do apelido
        apelido_frame = tk.Frame(main_frame)
        apelido_frame.pack(fill='x', pady=(0, 10))
        
        # Label e entrada do apelido
        apelido_label = tk.Label(apelido_frame, text=t('population.nickname_label'), font=("Helvetica", 9))
        apelido_label.pack(anchor='w')
        
        apelido_var = tk.StringVar(value=current_apelido)
        apelido_entry = tk.Entry(apelido_frame, textvariable=apelido_var, font=("Helvetica", 10), width=40)
        apelido_entry.pack(fill='x', pady=(5, 0))
        
        # Frame para entrada das coordenadas
        coords_frame = tk.Frame(main_frame)
        coords_frame.pack(fill='x', pady=(0, 15))
        
        # Label e entrada das coordenadas
        coords_label = tk.Label(coords_frame, text=t('population.coordinates_label'), font=("Helvetica", 9))
        coords_label.pack(anchor='w')
        
        coords_var = tk.StringVar(value=current_coordinates)
        coords_entry = tk.Entry(coords_frame, textvariable=coords_var, font=("Helvetica", 10), width=40)
        coords_entry.pack(fill='x', pady=(5, 0))
        coords_entry.focus()
        coords_entry.select_range(0, tk.END)
        
        # Frame para botões
        button_frame = tk.Frame(main_frame)
        button_frame.pack(fill='x')
        
        def save_apelido():
            new_apelido = apelido_var.get().strip()
            new_coordinates = coords_var.get().strip()
            
            # CORREÇÃO: Validação: máximo 4 caracteres alfanuméricos
            if len(new_apelido) > 4:
                tk.messagebox.showerror(t('population.error'), t('population.error_nickname_max_length'))
                return
            
            # Validação: permite caracteres especiais comuns
            import re
            if new_apelido and not re.match(r'^[a-zA-Z0-9\s#&%@\-_.,!?()]*$', new_apelido):
                tk.messagebox.showerror(t('population.error'), t('population.error_nickname_invalid_chars'))
                return
            
            # Validação das coordenadas (formato X,Y,Z)
            if new_coordinates:
                coords_parts = new_coordinates.split(',')
                if len(coords_parts) != 3:
                    tk.messagebox.showerror(t('population.error'), t('population.error_coordinates_format'))
                    return
                try:
                    for part in coords_parts:
                        float(part.strip())
                except ValueError:
                    tk.messagebox.showerror(t('population.error'), t('population.error_coordinates_numeric'))
                    return
            
            # Salva o apelido e as coordenadas usando a função com validação
            self.save_tag_info(epc, new_apelido, new_coordinates)
            
            print(f"✅ Tag atualizada - EPC: {epc}, Apelido: '{new_apelido}', Coordenadas: '{new_coordinates}'")
            
            # Fecha o popup
            popup.destroy()
        
        def cancel_edit():
            popup.destroy()
        
        # Botões
        save_button = tk.Button(button_frame, text="Salvar", command=save_apelido, 
                               bg='#2ecc71', fg='white', font=("Helvetica", 9), width=10)
        save_button.pack(side='right', padx=(5, 0))
        
        cancel_button = tk.Button(button_frame, text="Cancelar", command=cancel_edit,
                                 bg='#95a5a6', fg='white', font=("Helvetica", 9), width=10)
        cancel_button.pack(side='right')
        
        # Bind Enter para salvar
        apelido_entry.bind('<Return>', lambda e: save_apelido())
        apelido_entry.bind('<Escape>', lambda e: cancel_edit())

    def save_tag_apelido(self, epc, apelido):
        """Salva o apelido de uma tag e sincroniza com todos os pontos."""
        # Inicializa o dicionário de apelidos se não existir
        if not hasattr(self, 'tag_apelidos'):
            self.tag_apelidos = {}
        
        # Salva o apelido
        self.tag_apelidos[epc] = apelido
        
        # Sincroniza com os dados de teste (se a tag estiver presente)
        if hasattr(self, 'test_data') and isinstance(self.test_data, (list, tuple)):
            updated = False
            for data_point in self.test_data:
                if data_point.get('detected_epcs') and epc in data_point['detected_epcs']:
                    data_point['apelido'] = apelido
                    updated = True
            
            if updated:
                # Salva os dados atualizados
                self._save_test_data()
                # Atualiza a tabela visual para refletir o novo apelido
                self._update_table_from_test_data()
                print(f"✅ Tabela atualizada com novo apelido: '{apelido}' para EPC {epc[:20]}...")
                # Atualiza o tooltip do gráfico
                self._update_power_epcs_map_for_tooltip()
                print(f"✅ Apelido '{apelido}' sincronizado para tag {epc}")
        
        # Salva os apelidos das tags registradas
        self._save_tag_apelidos()
        
        # Atualiza a lista "Registered Tags" se a tag estiver presente
        self._update_registered_tags_apelido(epc, apelido)
        
        # Atualiza o gráfico 3D após salvar apelido
        self.update_3d_plot(test_running=False, detected_tags=None)
        
        print(f"✅ Apelido '{apelido}' salvo para tag {epc}")

    def _save_tag_apelidos(self):
        """Salva os apelidos das tags registradas em arquivo JSON."""
        try:
            if hasattr(self, 'tag_apelidos'):
                with open(os.path.join(self.PERSISTENCE_DIR, 'tag_apelidos.json'), 'w', encoding='utf-8') as f:
                    json.dump(self.tag_apelidos, f, indent=2, ensure_ascii=False)
                print(f"💾 Apelidos das tags salvos: {len(self.tag_apelidos)} tags")
        except Exception as e:
            print(f"⚠️ Erro ao salvar apelidos das tags: {e}")

    def _update_registered_tags_apelido(self, epc, apelido):
        """Atualiza o apelido na lista Registered Tags."""
        try:
            if hasattr(self, 'tags_tree'):
                # Procura o item na lista com o EPC correspondente
                for item in self.tags_tree.get_children():
                    item_epc = self.tags_tree.set(item, 'tag')
                    if item_epc == epc:
                        # Atualiza o apelido na coluna
                        self.tags_tree.set(item, 'apelido', apelido)
                        print(f"✅ Apelido atualizado na lista Registered Tags: {epc} -> '{apelido}'")
                        break
        except Exception as e:
            print(f"⚠️ Erro ao atualizar apelido na lista Registered Tags: {e}")

    def _load_tag_apelidos(self):
        """Carrega os apelidos das tags registradas do arquivo JSON."""
        try:
            target_apelidos_file = os.path.join(self.PERSISTENCE_DIR, 'tag_apelidos.json')
            legacy_apelidos_file = os.path.join("data", "tag_apelidos.json")
            
            if os.path.exists(target_apelidos_file) or os.path.exists(legacy_apelidos_file):
                src_file = target_apelidos_file if os.path.exists(target_apelidos_file) else legacy_apelidos_file
                with open(src_file, 'r', encoding='utf-8') as f:
                    self.tag_apelidos = json.load(f)
                
                # Migra para o caminho absoluto para garantir consistência
                if src_file != target_apelidos_file:
                    try:
                        os.makedirs(self.PERSISTENCE_DIR, exist_ok=True)
                        with open(target_apelidos_file, 'w', encoding='utf-8') as wf:
                            json.dump(self.tag_apelidos, wf, indent=2, ensure_ascii=False)
                        print(f"🔄 Migration: tag_apelidos carregados de legacy e movidos para: {target_apelidos_file}")
                    except Exception as e:
                        print(f"⚠️ Migration: falha ao mover tag_apelidos para o caminho absoluto: {e}")
                
                print(f"📂 Apelidos das tags carregados: {len(self.tag_apelidos)} tags")
            else:
                self.tag_apelidos = {}
        except Exception as e:
            print(f"⚠️ Erro ao carregar apelidos das tags: {e}")
            self.tag_apelidos = {}


    def hide_tags_tooltip(self):
        """Esconde o tooltip das tags."""
        if hasattr(self, 'tags_tooltip') and self.tags_tooltip.winfo_exists():
            self.tags_tooltip.destroy()
    
    def save_tag_info(self, epc, apelido, coordinates):
        """Salva as informações editadas da tag (apelido e coordenadas)."""
        try:
            print(f"🔍 DEBUG: Salvando tag {epc} com apelido '{apelido}' e coordenadas '{coordinates}'")
            
            # Valida duplicatas antes de salvar
            if apelido and apelido.strip():
                print(f"🔍 DEBUG: Validando apelido '{apelido.strip()}'")
                
                # Usa SOMENTE as tags atualmente visíveis na lista de "Tags registradas"
                # (self.tags_data é preenchido por update_tags_list)
                visible_tags = getattr(self, "tags_data", [])
                print(f"🔍 DEBUG: Encontradas {len(visible_tags)} tags visíveis na lista")
                
                for tag in visible_tags:
                    existing_name = str(tag.get('apelido', '') or tag.get('name', '')).strip()
                    existing_epc = tag.get('epc')
                    print(f"🔍 DEBUG: Tag visível {existing_epc} tem apelido '{existing_name}'")
                    
                    if (
                        existing_name
                        and existing_name.lower() == apelido.strip().lower()
                        and existing_epc != epc
                    ):
                        print(
                            f"❌ DEBUG: Apelido duplicado encontrado na lista ativa! "
                            f"'{apelido.strip()}' já existe em {existing_epc}"
                        )
                        messagebox.showwarning(
                            t("population.warning_duplicate_nickname"),
                            t("population.warning_duplicate_nickname_msg").format(
                                nickname=apelido.strip()
                            ),
                        )
                        return
                
                print(f"✅ DEBUG: Apelido '{apelido.strip()}' é único entre as tags ativas")
            
            if coordinates and coordinates.strip():
                # Verifica se as coordenadas já existem (exceto para a própria tag)
                existing_tags = self.database.get_all_tags()
                for tag in existing_tags:
                    if (tag.get('coordinates', '').strip().lower() == coordinates.strip().lower() and 
                        tag.get('epc') != epc):
                        messagebox.showwarning(t('population.warning_duplicate_coordinates'), 
                            t('population.warning_duplicate_coordinates_msg').format(coordinates=coordinates.strip()))
                        return
            
            # Salva o apelido se fornecido
            if apelido and apelido.strip():
                self.save_tag_apelido(epc, apelido.strip())
                print(f"✅ Apelido salvo para EPC {epc}: '{apelido.strip()}'")
            else:
                # Remove apelido se vazio
                if hasattr(self, 'tag_apelidos') and epc in self.tag_apelidos:
                    del self.tag_apelidos[epc]
                print(f"✅ Apelido removido para EPC {epc}")
            
            # Salva as coordenadas no banco de dados
            if coordinates and coordinates.strip():
                # Atualiza as coordenadas no banco de dados
                tag_data = self.database.get_tag_by_epc(epc)
                if tag_data:
                    # Atualiza as coordenadas existentes
                    tag_data['coordinates'] = coordinates.strip()
                    # Salva no banco
                    self.database._save_data()
                    print(f"✅ Coordenadas salvas para EPC {epc}: '{coordinates.strip()}'")
                else:
                    # Se a tag não existe no banco, adiciona
                    self.database.add_tag(epc, apelido.strip() if apelido else "", 0.0, coordinates.strip())
                    print(f"✅ Tag adicionada ao banco com coordenadas: EPC {epc}")
            else:
                # Remove coordenadas se vazio
                tag_data = self.database.get_tag_by_epc(epc)
                if tag_data and 'coordinates' in tag_data:
                    del tag_data['coordinates']
                    self.database._save_data()
                    print(f"✅ Coordenadas removidas para EPC {epc}")
            
            # Atualiza a lista de tags
            self.update_tags_list()
            
            # Fecha o popup
            self.hide_tags_tooltip()
            
            # Mostra mensagem de sucesso
            messagebox.showinfo(t('population.success'), t('population.success_tag_saved').format(epc=epc[:8]))
            
        except Exception as e:
            print(f"⚠️ Erro ao salvar informações da tag {epc}: {e}")
            messagebox.showerror(t('population.error'), t('population.error_saving_tag').format(error=str(e)))
            delattr(self, 'tags_tooltip')
    
    def save_tag_coordinates(self, epc, coordinates):
        """Salva as coordenadas de uma tag no banco de dados."""
        try:
            if coordinates and coordinates.strip():
                # Atualiza as coordenadas no banco de dados
                tag_data = self.database.get_tag_by_epc(epc)
                if tag_data:
                    # Atualiza as coordenadas existentes
                    tag_data['coordinates'] = coordinates.strip()
                    # Salva no banco
                    self.database._save_data()
                    print(f"✅ Coordenadas salvas para EPC {epc}: '{coordinates.strip()}'")
                else:
                    print(f"⚠️ Tag não encontrada no banco para EPC {epc}")
            else:
                # Remove coordenadas se vazio
                tag_data = self.database.get_tag_by_epc(epc)
                if tag_data and 'coordinates' in tag_data:
                    del tag_data['coordinates']
                    self.database._save_data()
                    print(f"✅ Coordenadas removidas para EPC {epc}")
            
            # Atualiza o gráfico 3D após salvar coordenadas
            self.update_3d_plot(test_running=False, detected_tags=None)
        except Exception as e:
            print(f"⚠️ Erro ao salvar coordenadas para EPC {epc}: {e}")
            raise e

    def _test_finished(self, success, message):
        """Finaliza o teste"""
        self.end_time = datetime.now()
        self.test_in_progress = False
        self.continuous_search_running = False
        
        # CORREÇÃO: Usa port_manager centralizado para fechar comunicação COM
        try:
            if hasattr(self, 'port_manager') and self.port_manager:
                self.port_manager.close_port(self.module_name)
                print("✅ Population: Comunicação COM fechada via port_manager ao finalizar teste")
            elif HARDWARE_AVAILABLE and rfid_sdk:
                # Fallback: usa método direto se port_manager não estiver disponível
                with self.com_port_lock:
                    try:
                        rfid_sdk.UHF_RFID_Close(self.com_port)
                        print("✅ Population: Comunicação COM fechada ao finalizar teste (fallback)")
                    except Exception as e:
                        print(f"⚠️ Population: Erro ao fechar COM ao finalizar: {e}")
        except Exception as e:
            print(f"⚠️ Population: Erro geral ao fechar comunicação ao finalizar: {e}")
        
        # Salva teste no histórico se bem-sucedido
        if success:
            self.after(0, self._save_test_to_history)
        
        # Atualiza interface
        self.after(0, self._finish_test_ui, success, message)
        
        # Notifica app_shell
        if self.app_shell:
            self.app_shell.set_test_running(False, None)
        
        # Salva dados persistidos
        self._save_persisted_data()
        
        # CORREÇÃO: Reseta flag de cancelamento após finalizar
        self.global_test_cancelled = False
        
        # CORREÇÃO: Reconfigura tooltip após finalizar teste para restaurar interatividade
        self.after(200, self._setup_bar_tooltip)
        print("🔄 Tooltip reconfigurado após finalizar teste (método _test_finished)")
        
        print(f"🏁 Teste finalizado: {message}")

    def _save_test_to_history(self):
        """Salva teste no histórico"""
        try:
            if self.test_data and self.start_time and self.end_time:
                test_record = {
                    'date_time': self.start_time.isoformat(),
                    'description': self.description_var.get() or f"Teste Population {self.start_time.strftime('%d/%m/%Y %H:%M')}",
                    'frequency': float(self.frequency_var.get()),
                    'power': float(self.power_var.get()),
                    'distance': float(self.distance_var.get()),
                    'attenuator': float(self.attenuator_var.get()),
                    'test_data': self.test_data,
                    'module': 'População'
                }
                self.database.save_test_result(test_record)
                print("✅ Teste salvo no histórico")
        except Exception as e:
            print(f"⚠️ Erro ao salvar teste no histórico: {e}")

    def _finish_test_ui(self, success, message):
        """Atualiza interface após fim do teste"""
        self.start_button.config(state='normal')
        self.stop_button.config(state='disabled')
        if hasattr(self, 'clear_test_button') and self.clear_test_button:
            self.clear_test_button.config(state='normal')
        
        if success:
            print("✅ Teste concluído com sucesso")
        else:
            print(f"❌ Erro no teste: {message}")
            # CORREÇÃO: Só mostra popup se não foi cancelamento normal
            if not self.global_test_cancelled and t('population.error_freq_blocked') in message:
                try:
                    # Usa exatamente a mensagem exigida
                    messagebox.showerror(t('population.error_freq_blocked'), t('population.error_freq_blocked'))
                except Exception:
                    pass
            elif self.global_test_cancelled:
                print("ℹ️ Teste cancelado pelo usuário - popup suprimido")
        
        # CORREÇÃO: Reconfigura tooltip após finalizar teste para restaurar interatividade
        self.after(100, self._setup_bar_tooltip)
        print("🔄 Tooltip reconfigurado após finalizar teste")

        # Adiciona à tabela as tags selecionadas que NÃO foram lidas
        try:
            self._append_not_read_tags_to_table()
        except Exception as e:
            print(f"⚠️ Erro ao adicionar tags não lidas na tabela: {e}")

        # Salva reprovadas (não lidas) também no fechamento do teste
        try:
            # Garante persistência no disco das reprovadas atuais
            if hasattr(self, 'not_read_tags_permanent') and self.not_read_tags_permanent:
                os.makedirs(self.PERSISTENCE_DIR, exist_ok=True)
                with open(self.NOT_READ_TAGS_FILE, 'w', encoding='utf-8') as f:
                    json.dump({'not_read_tags': list(self.not_read_tags_permanent), 'last_updated': datetime.now().isoformat()}, f, indent=2, ensure_ascii=False)
        except Exception as _e_save2:
            print(f"⚠️ Erro ao persistir reprovadas no fim do teste: {_e_save2}")


    def _append_not_read_tags_to_table(self):
        """Inclui na tabela da UI as tags selecionadas que não foram lidas no teste."""
        try:
            # Coleta tags selecionadas
            if hasattr(self, 'selected_tags') and self.selected_tags:
                selected = set(self.selected_tags)
            else:
                selected = set()
            
            # Coleta tags efetivamente detectadas durante o teste
            detected = set()
            try:
                if hasattr(self, 'detected_tags_permanent') and self.detected_tags_permanent:
                    detected = set(self.detected_tags_permanent)
            except Exception:
                detected = set()
            
            not_read = [epc for epc in selected if epc not in detected]
            if not not_read:
                return
            
            # Frequência atual (se disponível)
            try:
                freq_val = float(self.frequency_var.get()) if hasattr(self, 'frequency_var') else None
            except Exception:
                freq_val = None
            
            for epc in not_read:
                apelido = self.get_tag_apelido(epc) if hasattr(self, 'get_tag_apelido') else ''
                coords = self.get_tag_coordinates(epc) if hasattr(self, 'get_tag_coordinates') else ''
                
                # CORREÇÃO: Adiciona tag reprovada ao test_data (igualdade com tags aprovadas)
                not_read_data_point = {
                    'timestamp': datetime.now(),
                    'power': float(self.power_var.get()) if hasattr(self, 'power_var') else 0.0,
                    'frequency': freq_val if isinstance(freq_val, (int, float)) else 865.0,
                    'tags_detected': 0,  # Nenhuma tag detectada (reprovada)
                    'cumulative_tags': len(self.test_data) + 1,  # Próximo número sequencial
                    'rssi_avg': 0.0,  # RSSI zero para tags não lidas
                    'detected_epcs': [epc],  # EPC da tag reprovada
                    'comment': '(NAO LIDA)',  # Comentário indicando reprovação
                    'apelido': apelido  # Apelido da tag reprovada
                }
                
                # Adiciona aos dados do teste (IGUALDADE com tags aprovadas)
                self.test_data.append(not_read_data_point)
                
                # Atualiza a tabela usando o mesmo método das tags aprovadas
                try:
                    self._update_table(not_read_data_point)
                except Exception as e:
                    print(f"⚠️ Erro ao atualizar tabela para tag reprovada {epc}: {e}")
                    
                print(f"✅ Tag reprovada {epc} adicionada ao test_data com igualdade")

            # CORREÇÃO: Tags reprovadas agora são salvas em test_data (igualdade total)
            # Não precisa mais de persistência separada - são tratadas igual às aprovadas
            print(f"✅ {len(not_read)} tags reprovadas adicionadas ao test_data com igualdade total")
        except Exception as e:
            print(f"⚠️ Falha em _append_not_read_tags_to_table: {e}")


    def export_data(self):
        """Exporta dados do teste"""
        if not self.test_data:
            messagebox.showwarning(t('population.warning_no_data_export'), t('population.warning_no_data_export_msg'))
            return
        
        try:
            # Solicita local do arquivo
            filename = filedialog.asksaveasfilename(
                defaultextension=".csv",
                filetypes=[("CSV files", "*.csv"), ("Excel files", "*.xlsx"), ("All files", "*.*")],
                title=t('population.save_test_data')
            )
            
            if not filename:
                return
            
            # Cria DataFrame
            df = pd.DataFrame(self.test_data)
            
            # Salva arquivo
            if filename.endswith('.csv'):
                df.to_csv(filename, index=False)
            elif filename.endswith('.xlsx'):
                df.to_excel(filename, index=False)
            else:
                df.to_csv(filename, index=False)
            
            messagebox.showinfo(t('population.success'), t('population.success_exported').format(filename=filename))
            
        except Exception as e:
            messagebox.showerror(t('population.error'), t('population.error_export_failed').format(error=str(e)))

    def _generate_complete_report(self):
        """Gera relatório completo com todos os dados do módulo"""
        try:
            # Dados básicos do teste
            test_data = []
            if hasattr(self, 'test_data') and isinstance(self.test_data, (list, tuple)):
                for data_point in self.test_data:
                    # Converte timestamp para string de forma segura
                    timestamp_str = ""
                    if 'timestamp' in data_point:
                        if isinstance(data_point['timestamp'], datetime):
                            timestamp_str = data_point['timestamp'].isoformat()
                        else:
                            timestamp_str = str(data_point['timestamp'])
                    else:
                        timestamp_str = datetime.now().isoformat()
                    
                    # Processa tags_detected de forma segura
                    tags_detected = data_point.get('tags_detected', [])
                    if isinstance(tags_detected, (list, tuple)):
                        tags_detected_list = list(tags_detected)
                    else:
                        tags_detected_list = [tags_detected] if tags_detected is not None else []
                    
                    # Processa detected_epcs de forma segura
                    detected_epcs = data_point.get('detected_epcs', [])
                    if isinstance(detected_epcs, (list, tuple)):
                        detected_epcs_list = list(detected_epcs)
                    else:
                        detected_epcs_list = [detected_epcs] if detected_epcs is not None else []
                    
                    test_data.append({
                        'power': float(data_point.get('power', 0)),
                        'cumulative_tags': int(data_point.get('cumulative_tags', 0)),
                        'timestamp': timestamp_str,
                        'tags_detected': tags_detected_list,
                        'rssi_avg': float(data_point.get('rssi_avg', 0)),
                        'frequency': float(data_point.get('frequency', 915)),
                        'detected_epcs': detected_epcs_list,
                        'comment': str(data_point.get('comment', '')),  # CORREÇÃO: Inclui comentário
                        'apelido': str(data_point.get('apelido', ''))   # CORREÇÃO: Inclui apelido
                    })
            
            # Dados das tags selecionadas
            if hasattr(self, 'selected_tags') and isinstance(self.selected_tags, (set, list, tuple)):
                selected_tags_data = list(self.selected_tags)
            else:
                selected_tags_data = []
            
            # Dados das tags registradas
            registered_tags_data = []
            if hasattr(self, 'database') and self.database:
                try:
                    all_tags = self.database.get_tags()
                    for tag in all_tags:
                        # Adiciona o apelido se existir
                        tag_with_apelido = tag.copy()
                        if hasattr(self, 'get_tag_apelido'):
                            apelido = self.get_tag_apelido(tag.get('epc', ''))
                            if apelido:
                                tag_with_apelido['apelido'] = apelido
                        registered_tags_data.append(tag_with_apelido)
                        # Log para debug
                        epc = tag.get('epc', '')
                        coords = tag.get('coordinates', '')
                        print(f"💾 Salvando tag: {epc[:20]}... -> Coords: '{coords}'")
                    print(f"📂 Incluindo {len(registered_tags_data)} tags registradas no relatório")
                except Exception as e:
                    print(f"⚠️ Erro ao obter tags registradas: {e}")
                    registered_tags_data = []
            
            # Estado do gráfico
            graph_state = {}
            if hasattr(self, 'ax') and MATPLOTLIB_OK:
                try:
                    # Dados das linhas
                    graph_lines = []
                    try:
                        lines = self.ax.get_lines()
                        if hasattr(lines, '__iter__'):
                            for line in lines:
                                x_data = line.get_xdata()
                                y_data = line.get_ydata()
                                if len(x_data) > 0 and len(y_data) > 0:
                                    x_list = x_data.tolist() if hasattr(x_data, 'tolist') else list(x_data)
                                    y_list = y_data.tolist() if hasattr(y_data, 'tolist') else list(y_data)
                                    graph_lines.append({
                                        'x': x_list,
                                        'y': y_list,
                                        'color': line.get_color(),
                                        'linewidth': line.get_linewidth(),
                                        'label': line.get_label()
                                    })
                    except Exception as e:
                        print(f"⚠️ Erro ao salvar linhas do gráfico: {e}")
                    
                    # Dados das barras
                    graph_bars = []
                    try:
                        patches = self.ax.patches
                        if hasattr(patches, '__iter__'):
                            for patch in patches:
                                face_color = patch.get_facecolor()
                                if hasattr(face_color, '__iter__') and len(face_color) >= 3:
                                    color_str = f"#{int(face_color[0]*255):02x}{int(face_color[1]*255):02x}{int(face_color[2]*255):02x}"
                                else:
                                    color_str = str(face_color)
                        
                                graph_bars.append({
                                    'x': float(patch.get_x()),
                                    'y': float(patch.get_y()),
                                    'width': float(patch.get_width()),
                                    'height': float(patch.get_height()),
                                    'color': color_str
                                })
                    except Exception as e:
                        print(f"⚠️ Erro ao salvar barras do gráfico: {e}")
                    
                    graph_state = {
                        'xlim': self.ax.get_xlim(),
                        'ylim': self.ax.get_ylim(),
                        'title': self.ax.get_title(),
                        'xlabel': self.ax.get_xlabel(),
                        'ylabel': self.ax.get_ylabel(),
                        'lines': graph_lines,
                        'bars': graph_bars
                    }
                except Exception as e:
                    print(f"⚠️ Erro ao capturar estado do gráfico: {e}")
                    graph_state = {'error': str(e)}
            
            # Estado da tabela
            table_data = []
            try:
                for item in self.tree.get_children():
                    values = self.tree.item(item)['values']
                    table_data.append(values)
            except Exception as e:
                print(f"⚠️ Erro ao capturar dados da tabela: {e}")
                table_data = []
            
            # Relatório completo
            report = {
                'metadata': {
                    'module': 'Population RFID',
                    'version': '1.0',
                    'generated_at': datetime.now().isoformat(),
                    'total_test_points': len(self.test_data) if hasattr(self, 'test_data') and isinstance(self.test_data, (list, tuple)) else 0,
                    'total_selected_tags': len(self.selected_tags) if hasattr(self, 'selected_tags') and isinstance(self.selected_tags, (set, list, tuple)) else 0,
                    'hardware_connected': hasattr(self, 'hardware_controller') and self.hardware_controller is not None
                },
                'test_parameters': {
                    'frequency': self.frequency_var.get(),
                    'power': self.power_var.get(),
                    'distance': self.distance_var.get(),
                    'attenuator': self.attenuator_var.get(),
                    'description': self.description_var.get(),
                    'selected_action': self.action_var.get()
                },
                'test_data': test_data,
                'selected_tags': selected_tags_data,
                'registered_tags': registered_tags_data,
                'detected_tags_permanent': list(self.detected_tags_permanent),
                'table_data': table_data,
                'graph_state': graph_state,
                'ui_state': {
                    'test_running': getattr(self, 'test_running', False),
                    'auto_save_enabled': getattr(self, 'auto_save_enabled', True),
                    'last_auto_save': getattr(self, 'last_auto_save', None).isoformat() if getattr(self, 'last_auto_save', None) and hasattr(getattr(self, 'last_auto_save', None), 'isoformat') else None
                }
            }
            
            print(f"📊 Relatório gerado: {len(test_data) if isinstance(test_data, (list, tuple)) else 0} pontos, {len(selected_tags_data) if isinstance(selected_tags_data, (list, tuple)) else 0} tags selecionadas, {len(registered_tags_data) if isinstance(registered_tags_data, (list, tuple)) else 0} tags registradas, {len(table_data) if isinstance(table_data, (list, tuple)) else 0} linhas da tabela")
            print(f"🔍 Debug: Dados das tags registradas no relatório: {registered_tags_data}")
            return report
            
        except Exception as e:
            print(f"❌ Erro ao gerar relatório: {e}")
            return {
                'error': str(e),
                'metadata': {
                    'module': 'Population RFID',
                    'generated_at': datetime.now().isoformat(),
                    'error': True
                }
            }

    def save_test(self):
        """Salva relatório completo com todos os dados"""
        try:
            # Gera nome padrão com prefixo 'Populacao' para padronizar com os demais módulos
            default_filename = f"Populacao_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
            
            # Solicita local do arquivo (padrão: Downloads)
            import os
            downloads_path = os.path.join(os.path.expanduser("~"), "Downloads")
            
            filename = filedialog.asksaveasfilename(
                defaultextension=".json",
                filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
                title=t('population.save_complete_report'),
                initialdir=downloads_path,
                initialfile=default_filename
            )
            
            if not filename:
                return
            
            print(f"📄 Gerando relatório completo: {filename}")
            
            # Gera relatório completo
            report = self._generate_complete_report()
            if 'error' in report:
                messagebox.showerror(t('population.error'), t('population.error_report_generation_failed').format(error=report['error']))
                return
            
            report['metadata']['test_name'] = default_filename.replace('.json', '')
            
            # Salva arquivo JSON
            try:
                with open(filename, 'w', encoding='utf-8') as f:
                    json.dump(report, f, indent=2, ensure_ascii=False, default=str)
            except Exception as e:
                messagebox.showerror(t('population.error'), t('population.error_save_file_failed').format(error=str(e)))
                return
            
            tags_count = len(self.selected_tags) if hasattr(self, 'selected_tags') and isinstance(self.selected_tags, (set, list, tuple)) else 0
            messagebox.showinfo(t('population.success'), 
                t('population.success_test_saved').format(
                    filename=filename,
                    points=len(self.test_data),
                    tags=tags_count
                ))
            
            print(f"✅ Relatório salvo: {filename}")
                
        except Exception as e:
            messagebox.showerror(t('population.error'), t('population.error_save_test_failed').format(error=str(e)))

    def import_test(self):
        """Importa relatório completo de teste"""
        try:
            filename = filedialog.askopenfilename(
                filetypes=[("JSON files", "*.json"), ("All files", "*.*")],
                title=t('population.import_complete_report')
            )
            if not filename: return
            
            print(f"📄 Importando relatório: {filename}")
            with open(filename, 'r', encoding='utf-8') as f:
                report = json.load(f)
            
            self.test_data.clear()
            self.live_tags.clear()
            self.cumulative_tags_detected = 0
            self.first_detection_registry.clear()
            self.power_epcs_map.clear()
            for item in self.tree.get_children(): self.tree.delete(item)
            self._clear_persisted_data()
            
            if 'test_parameters' in report:
                params = report['test_parameters']
                self.frequency_var.set(params.get('frequency', '915'))
                self.power_var.set(params.get('power', '15'))
                self.distance_var.set(params.get('distance', '1.0'))
                self.attenuator_var.set(params.get('attenuator', '1.0'))
                self.description_var.set(params.get('description', ''))
            
            if 'test_data' in report and report['test_data']:
                print(f"📊 Importando {len(report['test_data'])} pontos de dados de teste...")
                
                for data_point in report['test_data']:
                    try:
                        timestamp = datetime.fromisoformat(data_point['timestamp']) if 'timestamp' in data_point and data_point['timestamp'] else datetime.now()
                    except:
                        timestamp = datetime.now()
                    
                    power = float(data_point.get('power', 0))
                    detected_epcs = data_point.get('detected_epcs', [])
                    
                    # Reconstrói mapeamento de EPCs e apelidos por potência para tooltip
                    if detected_epcs:
                        epcs_with_apelidos = []
                        for epc in detected_epcs:
                            # Busca o apelido correspondente nos dados de teste
                            apelido = data_point.get('apelido', '')
                            epcs_with_apelidos.append({'epc': epc, 'apelido': apelido})
                        self.power_epcs_map[power] = epcs_with_apelidos
                    
                    # Adiciona o ponto de dados ao test_data
                    test_point = {
                        'power': power, 
                        'cumulative_tags': int(data_point.get('cumulative_tags', 0)),
                        'timestamp': timestamp, 
                        'tags_detected': data_point.get('tags_detected', []),
                        'rssi_avg': float(data_point.get('rssi_avg', 0)), 
                        'frequency': float(data_point.get('frequency', 915)),
                        'detected_epcs': detected_epcs, 
                        'comment': str(data_point.get('comment', '')),
                        'apelido': str(data_point.get('apelido', ''))
                    }
                    self.test_data.append(test_point)
                    
                    # Atualiza a tabela para cada ponto
                    self._update_table(test_point)
                
                print(f"✅ {len(self.test_data)} pontos de dados importados e tabela atualizada")
            
            if 'selected_tags' in report:
                self.selected_tags = set(report['selected_tags'])
                print(f"🏷️ Importando {len(self.selected_tags)} tags selecionadas")
                print(f"🔍 DEBUG: selected_tags após importação: {list(self.selected_tags)[:3]}...")
                
                # CORREÇÃO: Salva as tags selecionadas no arquivo para que o gráfico 3D funcione
                self._save_selected_tags_to_file()
                
                self.update_tags_list()
            
            # Importa tags registradas se existirem no relatório
            if 'registered_tags' in report and report['registered_tags']:
                print(f"📂 Importando {len(report['registered_tags'])} tags registradas...")
                print(f"🔍 Debug: Dados das tags registradas no arquivo: {report['registered_tags']}")
                imported_tags_count = 0
                for tag_data in report['registered_tags']:
                    try:
                        # Extrai os dados da tag
                        epc = tag_data.get('epc', '')
                        name = tag_data.get('name', '')
                        initial_rssi = tag_data.get('initial_rssi', 0.0)
                        coordinates = tag_data.get('coordinates', '')  # CORREÇÃO: Pega coordenadas
                        
                        # Adiciona a tag ao banco de dados COM coordenadas
                        if self.database.add_tag(epc, name, initial_rssi, coordinates):
                            imported_tags_count += 1
                            print(f"✅ Tag importada: {epc[:20]}... -> Coords: '{coordinates}'")
                            # Se a tag tem apelido, salva no sistema de apelidos
                            if 'apelido' in tag_data and tag_data['apelido']:
                                self.save_tag_apelido(epc, tag_data['apelido'])
                    except Exception as e:
                        print(f"⚠️ Erro ao importar tag {tag_data.get('epc', 'desconhecida')}: {e}")
                
                print(f"✅ {imported_tags_count} tags registradas importadas")
                # Atualiza a lista de tags registradas
                self.update_tags_list()
                
                # SINCRONIZAÇÃO: Atualiza os apelidos nos dados de teste importados
                print("🔄 Sincronizando apelidos nos dados de teste...")
                apelidos_sincronizados = 0
                for data_point in self.test_data:
                    if 'detected_epcs' in data_point:
                        for epc in data_point['detected_epcs']:
                            # Busca o apelido da tag registrada
                            apelido = self.get_tag_apelido(epc)
                            if apelido:
                                # Atualiza o apelido no ponto de dados
                                data_point['apelido'] = apelido
                                apelidos_sincronizados += 1
                                print(f"✅ Apelido '{apelido}' sincronizado para EPC {epc}")
                            else:
                                print(f"⚠️ Nenhum apelido encontrado para EPC {epc}")
                
                print(f"🔄 {apelidos_sincronizados} apelidos sincronizados nos dados de teste")
                
                # DEBUG: Verifica os apelidos nos dados de teste antes de atualizar a tabela
                print("🔍 Debug - Apelidos nos dados de teste:")
                for i, data_point in enumerate(self.test_data[:3]):  # Mostra apenas os primeiros 3
                    print(f"  Ponto {i}: EPCs={data_point.get('detected_epcs', [])}, Apelido='{data_point.get('apelido', '')}'")
                
                # Atualiza a tabela com os apelidos sincronizados
                self._update_table_from_test_data()
            else:
                print(f"⚠️ Nenhuma tag registrada encontrada no relatório. Seção 'registered_tags': {report.get('registered_tags', 'NÃO EXISTE')}")
            
            # Importa tags detectadas permanentemente se existirem no relatório
            if 'detected_tags_permanent' in report and report['detected_tags_permanent']:
                self.detected_tags_permanent = set(report['detected_tags_permanent'])
                print(f"🟢 Importando {len(self.detected_tags_permanent)} tags detectadas permanentemente")
                print(f"🔍 Tags detectadas: {[epc[:20] + '...' for epc in list(self.detected_tags_permanent)[:5]]}")
                # Salva as tags detectadas
                self._save_detected_tags()
            else:
                print("⚠️ Nenhuma tag detectada permanentemente encontrada no relatório")
            
            # Sempre ativa persistência após importação (mesmo se não houver tags detectadas)
            print("🔄 Ativando persistência após importação...")
            self._activate_persistence_immediately()
            
            # LÓGICA DE RESTAURAÇÃO DO GRÁFICO CORRIGIDA
            graph_restored_bar_count = 0
            if 'graph_state' in report:
                with open(self.GRAPH_STATE_FILE, 'w', encoding='utf-8') as f:
                    json.dump(report['graph_state'], f, indent=2, ensure_ascii=False, default=str)
                if MATPLOTLIB_OK and hasattr(self, 'ax'):
                    graph_restored_bar_count = self._restore_graph_state()
                    if graph_restored_bar_count:
                        print(f"✅ Estado do gráfico restaurado com sucesso a partir do snapshot. barras={graph_restored_bar_count}")

            if self.test_data and MATPLOTLIB_OK and hasattr(self, 'ax'):
                # Se snapshot estiver incompleto, reconstruímos para manter todas as barras.
                expected_bars = 0
                try:
                    if isinstance(self.test_data, (list, tuple)):
                        expected_bars = sum(
                            1
                            for dp in self.test_data
                            if isinstance(dp, dict)
                            and 'power' in dp
                            and 'cumulative_tags' in dp
                        )
                except Exception:
                    expected_bars = 0

                if expected_bars and graph_restored_bar_count < expected_bars:
                    print(
                        f"⚠️ Snapshot do gráfico incompleto (restored={graph_restored_bar_count}, expected={expected_bars}). Reconstruindo..."
                    )
                    self._reconstruct_graph_from_test_data()
                elif not graph_restored_bar_count:
                    print("⚠️ Snapshot do gráfico vazio/falhou. Reconstruindo a partir dos dados de teste...")
                    self._reconstruct_graph_from_test_data()

            self.update_tags_count()
            self._save_complete_ui_state()
            
            test_name = report.get('metadata', {}).get('test_name', 'Sem nome')
            messagebox.showinfo(t('population.success'), t('population.success_test_imported').format(test_name=test_name))
            
        except Exception as e:
            messagebox.showerror(t('population.error'), t('population.error_import_failed').format(error=str(e)))
            import traceback
            traceback.print_exc()

    def clear_test(self):
        """Limpa todos os dados do teste atual"""
        try:
            # Confirma limpeza
            if messagebox.askyesno(t('population.confirm_clear'), 
                                 t('population.confirm_clear_msg')):
                
                # Limpa dados do teste
                self.test_data.clear()
                self.live_tags.clear()
                self.cumulative_tags_detected = 0
                self.first_detection_registry.clear()
                self.power_epcs_map.clear()
                
                # Tags detectadas permanentemente serão limpas na seção do gráfico 3D
                
                # Limpa tabela
                for item in self.tree.get_children():
                    self.tree.delete(item)
                
                # Limpa gráfico
                if hasattr(self, 'ax') and self.ax:
                    self.ax.clear()
                    self.ax.set_title(t('population.graph_title'), fontsize=14)
                    self.ax.set_xlabel(t('population.graph_xlabel'), fontsize=12)
                    self.ax.set_ylabel(t('population.graph_ylabel'), fontsize=12)
                    # MELHORIA: Aplica grade melhorada baseada no número de tags selecionadas
                    selected_tags_count = len(self.selected_tags) if hasattr(self, 'selected_tags') else 0
                    max_y = max(selected_tags_count, 10)  # Mínimo 10 para visualização
                    self.ax.set_ylim(0, max_y)
                    y_ticks = list(range(0, max_y + 1, 1))
                    self.ax.set_yticks(y_ticks)
                    # CORREÇÃO: Mantém os labels do eixo Y para mostrar a escala
                    self.ax.set_yticklabels([str(i) for i in y_ticks])
                    # Adiciona linhas de grade horizontais mais visíveis
                    self.ax.grid(True, axis='y', alpha=0.4, linestyle='-', linewidth=0.8)
                    # Mantém as linhas de grade verticais mais sutis
                    self.ax.grid(True, axis='x', alpha=0.2, linestyle='-', linewidth=0.5)
                    
                    # Recria a linha de progresso após limpar o gráfico
                    self.progress_line, = self.ax.plot([5, 5], [0, 0], color='red', linewidth=6, alpha=1.0, label=t('population.progress_label'), zorder=15)
                    print("🔴 Linha de progresso recriada após clear")
                    
                    # CORREÇÃO: Usa a configuração padrão de ticks baseada no step de frequência
                    self._setup_plot_scales()
                    self.canvas.draw()
                    
                    # CORREÇÃO: Reconfigura tooltip após limpar gráfico
                    self.after(100, self._setup_bar_tooltip)
                    print("🔄 Tooltip reconfigurado após limpar gráfico (método clear_data)")
                
                # Limpa campos de entrada
                self.description_var.set("")
                
                # Limpa status do teste no gráfico 3D - mantém tags registradas mas todas vermelhas
                if hasattr(self, 'ax_3d') and self.ax_3d:
                    # Limpa tags detectadas permanentemente para resetar cores
                    self.detected_tags_permanent.clear()
                    self._save_detected_tags()
                    print("🧹 Tags detectadas permanentemente limpas - cores resetadas para vermelho")
                    
                    # Atualiza o gráfico 3D com todas as tags em vermelho
                    self.update_3d_plot(test_running=False, detected_tags=None)
                    print("🔴 Gráfico 3D atualizado - todas as tags em vermelho")
                
                # Atualiza contador de tags
                self.update_tags_count()
                
                # Limpa arquivos de dados persistentes
                try:
                    # Limpa dados das tags selecionadas
                    if os.path.exists(self.SELECTED_TAGS_FILE):
                        with open(self.SELECTED_TAGS_FILE, 'w', encoding='utf-8') as f:
                            json.dump({"selected_tags": [], "last_saved": ""}, f)
                    
                    # REMOVIDO: Não limpa apelidos das tags - eles devem ser preservados
                    # Os apelidos das tags registradas devem permanecer após limpar teste
                    
                    print("🧹 Arquivos de dados persistentes limpos (apelidos preservados)")
                    
                    # CORREÇÃO: Mantém seleção das tags - não desmarca tags selecionadas
                    # self.selected_tags.clear()  # REMOVIDO: Não desmarca tags selecionadas
                    # self.update_tags_list()     # REMOVIDO: Não atualiza lista para manter seleção
                    # self.update_tags_count()    # REMOVIDO: Não atualiza contador
                    print("🧹 Lista de tags mantida - seleção preservada")
                    
                except Exception as e:
                    print(f"⚠️ Erro ao limpar arquivos persistentes: {e}")
                
                # Salva estado limpo
                self._save_complete_ui_state()
                
                messagebox.showinfo(t('population.success'), t('population.success_test_cleared'))
                print("🧹 Teste limpo - todos os dados removidos")
                
        except Exception as e:
            messagebox.showerror(t('population.error'), t('population.error_clear_failed').format(error=str(e)))
            print(f"❌ Erro ao limpar teste: {e}")

    def on_action_selected(self, event):
        """Callback para seleção de ação"""
        try:
            action = self.action_var.get()

            # Mapeia usando os mesmos textos traduzidos usados na criação do dropdown,
            # para funcionar corretamente em PT e EN.
            action_map = {
                t('population.register_new_tags'): self.register_new_tags,
                t('population.import_tag_json'): self.import_tags_from_file,
                t('population.select_all_tags'): self.select_all_tags,
                t('population.deselect_all_tags'): self.deselect_all_tags,
                t('population.erase_selected_tags'): self.erase_selected_tags,
                t('population.save_selected_tags'): self.save_selected_tags,
            }

            handler = action_map.get(action)
            if handler:
                handler()
            else:
                print(f"⚠️ Population: ação de dropdown não reconhecida: {action!r}")
        finally:
            # Reset dropdown para o texto padrão traduzido
            self.action_var.set(t('population.select_an_action'))

    def register_new_tags(self):
        """Abre popup para registrar novas tags"""
        try:
            # Usa a mesma lógica do threshold - verifica se app_shell tem hardware
            if self.app_shell and hasattr(self.app_shell, 'hardware_available'):
                hardware_ok = self.app_shell.hardware_available
            else:
                # Fallback: verifica se a DLL está disponível (mesmo com erro de função)
                hardware_ok = rfid_sdk is not None
            
            if not hardware_ok:
                messagebox.showerror(t('population.error'), t('population.error_hardware_not_detected'))
                return
            popup = RegisterTagsPopup(self, self.database, self.perform_tag_scan, self.license_limits)
            self.wait_window(popup)
            if popup.result:
                self.update_tags_list()
                # Atualiza o gráfico 3D após registrar novas tags
                self.update_3d_plot(test_running=False, detected_tags=None)
        except Exception as e:
            messagebox.showerror(t('population.error'), t('population.error_register_tags_failed').format(error=str(e)))

    def perform_tag_scan(self, power_dbm):
        """Executa scan de tags com potência especificada"""
        # CORREÇÃO: Usa método direto como módulo Threshold (que funciona)
        with self.com_port_lock:
            if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) == 0:
                try:
                    # Respeita licença: potência clamp
                    power_dbm = self._clamp_power_to_license(float(power_dbm))
                    
                    # Configura potência
                    power_val = int(power_dbm * 100)
                    power_data = bytes([0, 0]) + power_val.to_bytes(2, 'big') * 2
                    out_buf = ctypes.create_string_buffer(32)
                    out_len = ctypes.c_uint(0)
                    
                    status = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, out_buf, ctypes.byref(out_len))
                    
                    if status == 0:
                        print(f"✅ Potência configurada para scan: {power_dbm} dBm")
                    else:
                        print(f"⚠️ Falha ao configurar potência para scan - status: {status}")
                        return []

                    # Executa scan usando caminho direto (sem port_manager),
                    # alinhado ao comportamento do módulo Threshold.
                    found_tags = self._scan_for_tags_direct_for_registration(duration_sec=2.0)
                    return found_tags
                finally:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
            else:
                raise IOError("Não foi possível abrir a porta COM para o scan.")

    def _scan_for_tags_direct_for_registration(self, duration_sec=2.0):
        """
        Executa inventário direto via DLL para o fluxo de registro de tags.

        Este caminho é propositalmente idêntico ao usado no módulo Threshold:
        - Usa RFID_CMD_INV_TAG com janela de 100 ms (0x00, 0x64).
        - Não passa pelo port_manager (porta já está aberta em perform_tag_scan).
        """
        tags_found = {}
        start_time = time.time()

        while time.time() - start_time < duration_sec:
            out_buf = ctypes.create_string_buffer(256)
            out_len = ctypes.c_uint(0)
            status = rfid_sdk.UHF_RFID_Set(
                RFID_CMD_INV_TAG,
                bytes([0x00, 0x64]),
                2,
                out_buf,
                ctypes.byref(out_len),
            )

            if status == 0 and out_len.value >= 16:
                try:
                    epc = out_buf.raw[2:14].hex().upper()
                    rssi_bytes = out_buf.raw[out_len.value - 3 : out_len.value - 1]
                    rssi_raw = struct.unpack(">h", rssi_bytes)[0]
                    rssi = float(int(rssi_raw)) / 10.0

                    if -100 <= rssi <= 0 and (
                        epc not in tags_found or rssi > tags_found[epc].rssi
                    ):
                        tags_found[epc] = TagInfo(epc=epc, rssi=rssi)
                except (ValueError, IndexError):
                    pass

            # Pequeno delay para não saturar a DLL
            time.sleep(0.01)

        return list(tags_found.values())

    def _continuous_search_worker(self):
        """Worker thread para inventário contínuo otimizado"""
        print("🔍 Thread de busca contínua iniciada")
        cycle_count = 0
        last_update_time = time.time()
        last_safety_check_time = time.time()
        update_interval = 0.5  # Atualiza interface a cada 500ms
        safety_check_interval = 5.0  # Verifica potência refletida a cada 5 segundos
        start_time = time.time()
        
        try:
            while not self.continuous_search_cancelled:
                cycle_count += 1
                current_cycle_time = time.time()
                
                # Log detalhado para debug
                if cycle_count % 10 == 0:
                    elapsed = current_cycle_time - start_time
                    print(f"🔍 Ciclo {cycle_count}: {elapsed:.1f}s decorridos, "
                          f"cancelled={self.continuous_search_cancelled}, "
                          f"running={self.continuous_search_running}")
                
                # PROTEÇÃO DE POTÊNCIA REFLETIDA: Verifica periodicamente durante a busca
                current_time = time.time()
                if current_time - last_safety_check_time >= safety_check_interval:
                    print(f"🔍 Population (Busca Contínua - Ciclo {cycle_count}): Verificando potência refletida...")
                    reflected_power = self.get_reflected_power()
                    if reflected_power is not None:
                        print(f"📊 Population (Busca Contínua): Potência Refletida = {reflected_power:.1f} dBm (Limite: {REFLECTED_POWER_LIMIT} dBm)")
                        if reflected_power > REFLECTED_POWER_LIMIT:
                            error_msg = t('population.error_reflected_power_during_test').format(
                                power=f"{reflected_power:.1f}", 
                                limit=REFLECTED_POWER_LIMIT)
                            print(f"❌ Population: BUSCA CONTÍNUA INTERROMPIDA!")
                            self.continuous_search_cancelled = True
                            self.after(0, lambda: messagebox.showerror(t('population.error_reflected_power_high'), error_msg, parent=self))
                            break
                        else:
                            print(f"✅ Population (Busca Contínua): Potência Refletida OK")
                    last_safety_check_time = current_time
                
                # Executa inventário
                tags_detected = self._perform_continuous_inventory()
                
                # Atualiza interface periodicamente para melhor performance
                current_time = time.time()
                if current_time - last_update_time >= update_interval:
                    self.after(0, self._update_data_table)
                    last_update_time = current_time
                    
                    # Log de progresso
                    if cycle_count % 20 == 0:
                        print(f"🔍 Busca contínua: {cycle_count} ciclos, {len(self.live_tags)} tags únicas, "
                              f"Taxa: {self.inventory_stats['scan_rate']:.1f} tags/scan")
                
                # Pausa otimizada - menor delay para inventário mais contínuo
                time.sleep(0.05)  # 50ms entre inventários para maior continuidade
                
        except Exception as e:
            print(f"⚠️ Erro na busca contínua: {e}")
            import traceback
            traceback.print_exc()
        finally:
            self.continuous_search_running = False
            total_time = time.time() - start_time
            print(f"⏹️ Thread de busca contínua finalizada após {cycle_count} ciclos em {total_time:.1f}s")

    def _perform_continuous_inventory(self):
        """Executa um ciclo de inventário contínuo otimizado"""
        try:
            # Usa hardware controller global se disponível
            if hasattr(self, 'hardware_controller') and self.hardware_controller:
                # Usa o hardware controller global
                tags_detected = self.hardware_controller.scan_for_tags()
                if tags_detected:
                    # Atualiza estatísticas
                    self.inventory_stats['total_scans'] += 1
                    self.inventory_stats['last_scan_time'] = datetime.now()
                    
                    # Processa tags detectadas
                    tags_in_scan = 0
                    for tag in tags_detected:
                        epc = tag.epc
                        rssi = tag.rssi
                        
                        if -100 <= rssi <= 0:
                            # Atualiza ou adiciona tag
                            current_time = datetime.now().strftime("%H:%M:%S")
                            if epc in self.live_tags:
                                # Atualiza tag existente
                                self.live_tags[epc]['rssi'] = rssi
                                self.live_tags[epc]['last_seen'] = current_time
                                self.live_tags[epc]['count'] += 1
                            else:
                                # Adiciona nova tag
                                self.live_tags[epc] = {
                                    'rssi': rssi,
                                    'last_seen': current_time,
                                    'count': 1,
                                    'first_seen': current_time
                                }
                                self.inventory_stats['tags_found'] += 1
                            
                            self.search_count += 1
                            tags_in_scan += 1
                    
                    # Atualiza taxa de scan
                    if tags_in_scan > 0:
                        self.inventory_stats['scan_rate'] = tags_in_scan
                    
                    return True
                else:
                    # Atualiza estatísticas mesmo sem tags
                    self.inventory_stats['total_scans'] += 1
                    self.inventory_stats['last_scan_time'] = datetime.now()
                    return False
            else:
                # Fallback: usa método direto
                # Buffer maior para múltiplas tags
                out_buf = ctypes.create_string_buffer(2048)
                out_len = ctypes.c_uint(0)
                
                # Inventário contínuo otimizado - 200ms para melhor detecção
                inv_tag_input_data = bytes([0x01, 0xC8])  # 200ms de inventário contínuo
                status = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, 
                                             ctypes.c_char_p(inv_tag_input_data), 2, 
                                             out_buf, ctypes.byref(out_len))
                
                # Atualiza estatísticas
                self.inventory_stats['total_scans'] += 1
                self.inventory_stats['last_scan_time'] = datetime.now()
                
                if status == 0 and out_len.value >= 16:
                    try:
                        # Processa resposta
                        data = out_buf.raw
                        offset = 2  # Pula cabeçalho
                        tags_in_scan = 0
                        
                        # Processa todas as tags na resposta
                        while offset + 12 < out_len.value:
                            # Extrai EPC (12 bytes)
                            epc = data[offset:offset+12].hex().upper()
                            
                            # Extrai RSSI (2 bytes após EPC)
                            if offset + 14 < out_len.value:
                                rssi_bytes = data[offset+12:offset+14]
                                rssi_raw = struct.unpack('>h', rssi_bytes)[0]
                                rssi = float(int(rssi_raw)) / 10.0
                                
                                if -100 <= rssi <= 0:
                                    # Atualiza ou adiciona tag
                                    current_time = datetime.now().strftime("%H:%M:%S")
                                    if epc in self.live_tags:
                                        # Atualiza tag existente
                                        self.live_tags[epc]['rssi'] = rssi
                                        self.live_tags[epc]['last_seen'] = current_time
                                        self.live_tags[epc]['count'] += 1
                                    else:
                                        # Adiciona nova tag
                                        self.live_tags[epc] = {
                                            'rssi': rssi,
                                            'last_seen': current_time,
                                            'count': 1,
                                            'first_seen': current_time
                                        }
                                        self.inventory_stats['tags_found'] += 1
                                    
                                    self.search_count += 1
                                    tags_in_scan += 1
                                
                            offset += 14  # Próxima tag (EPC + RSSI)
                        
                        # Atualiza taxa de scan
                        if tags_in_scan > 0:
                            self.inventory_stats['scan_rate'] = tags_in_scan
                        
                        return True
                        
                    except (ValueError, IndexError):
                        pass
                
                return False
                
        except Exception as e:
            print(f"⚠️ Erro no inventário contínuo: {e}")
            return False

    def _update_data_table(self):
        """Atualiza a tabela de dados com as tags detectadas"""
        try:
            # Limpa tabela
            for item in self.tree.get_children():
                self.tree.delete(item)
            
            # Adiciona tags detectadas
            if hasattr(self, 'live_tags') and isinstance(self.live_tags, dict):
                for epc, data in self.live_tags.items():
                    self.tree.insert('', 'end', values=(
                        data['last_seen'],
                        epc,
                        f"{data['rssi']:.1f}",
                        data['count']
                ))
            
        except Exception as e:
            print(f"⚠️ Erro ao atualizar tabela: {e}")



    def import_tags_from_file(self):
        """
        Abre uma janela para selecionar um arquivo .json e importa as tags
        para o banco de dados.
        O formato do arquivo esperado é JSON com estrutura de tags.
        """
        filepath = filedialog.askopenfilename(
            title=t('population.select_json_file'),
            filetypes=(("JSON files", "*.json"), ("Todos os arquivos", "*.*"))
        )

        if not filepath:  # Usuário cancelou a seleção
            return

        imported_count = 0
        skipped_count = 0
        error_count = 0
        tags_to_import = []

        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
                
                # Verifica se é uma lista de tags ou um objeto com tags
                if isinstance(data, list):
                    tags_data = data
                elif isinstance(data, dict) and 'tags' in data:
                    tags_data = data['tags']
                else:
                    messagebox.showerror(t('population.error'), t('population.error_invalid_json'))
                    return
                
                for tag_data in tags_data:
                    if not isinstance(tag_data, dict):
                        print(f"Erro: Tag deve ser um objeto JSON: {tag_data}")
                        error_count += 1
                        continue
                    
                    epc = tag_data.get('epc', '').strip()
                    apelido = tag_data.get('apelido', tag_data.get('name', '')).strip()
                    coordinates = tag_data.get('coordinates', '')
                    
                    if not epc or not apelido:
                        print(f"Erro: Tag deve ter 'epc' e 'apelido' (ou 'name'): {tag_data}")
                        error_count += 1
                        continue
                    
                    # Verifica se a tag já existe
                    if self.database.get_tag_by_epc(epc):
                        skipped_count += 1
                    else:
                        tags_to_import.append((epc, apelido, coordinates))

        except Exception as e:
            messagebox.showerror(t('population.error_reading_file'), t('population.error_reading_file_msg').format(error=str(e)), parent=self)
            return

        # Importa as tags para o banco de dados
        for epc, apelido, coordinates in tags_to_import:
            try:
                # Adiciona a tag ao banco de dados
                success = self.database.add_tag(epc, apelido, 0.0, coordinates)  # RSSI inicial 0.0, coordenadas
                if success:
                    imported_count += 1
                    # Salva o apelido no sistema de apelidos
                    if apelido:  # Só salva se o apelido não estiver vazio
                        self.tag_apelidos[epc] = apelido
                        self._save_tag_apelidos()
                else:
                    skipped_count += 1
            except Exception as e:
                print(f"Erro ao importar tag {epc}: {e}")
                error_count += 1

        # Log do resultado da importação (sem janela)
        print(f"✅ Importação concluída: {imported_count} tags importadas, {skipped_count} ignoradas, {error_count} erros")
        
        # Verifica quantas tags têm coordenadas
        tags_with_coords = sum(1 for _, _, coords in tags_to_import if coords and coords.strip())
        tags_without_coords = imported_count - tags_with_coords
        
        if tags_without_coords > 0:
            print(f"⚠️ AVISO: {tags_without_coords} tags importadas SEM coordenadas - não aparecerão no gráfico 3D!")
            print(f"   Para aparecer no gráfico 3D, as tags precisam ter coordenadas no formato: X,Y,Z")
            messagebox.showwarning(
                "Tags sem coordenadas", 
                f"{tags_without_coords} de {imported_count} tags importadas NÃO têm coordenadas.\n\n"
                f"Essas tags foram importadas, mas NÃO aparecerão no gráfico 3D.\n\n"
                f"Para aparecerem no gráfico 3D, edite as tags e adicione coordenadas no formato: X,Y,Z",
                parent=self
            )
        
        # Seleciona automaticamente as tags importadas
        if imported_count > 0:
            for epc, apelido, coordinates in tags_to_import:
                if self.database.get_tag_by_epc(epc):  # Verifica se a tag foi realmente importada
                    self.selected_tags.add(epc)
            print(f"🏷️ {len([epc for epc, _, _ in tags_to_import if self.database.get_tag_by_epc(epc)])} tags importadas foram automaticamente selecionadas")
        
        # Atualiza a lista de tags
        self.update_tags_list()
        # Atualiza a escala do gráfico baseada no número de tags selecionadas
        self.update_plot_scales()
        # Salva seleção automaticamente
        self._save_selected_tags()
        # Atualiza o gráfico 3D com as novas tags selecionadas
        print(f"🔄 Atualizando gráfico 3D após importação de {imported_count} tags")
        self.update_3d_plot(test_running=False, detected_tags=None)
        # Salva estado da interface
        self._save_complete_ui_state()

    def select_all_tags(self):
        """Seleciona todas as tags"""
        self.selected_tags = set()
        if hasattr(self, 'tags_data') and isinstance(self.tags_data, (list, tuple)):
            for tag_data in self.tags_data:
                self.selected_tags.add(tag_data['epc'])
        self.update_tags_list()
        # Atualiza a escala do gráfico baseada no número de tags selecionadas
        self.update_plot_scales()
        # Salva seleção automaticamente PRIMEIRO
        self._save_selected_tags()
        # Atualiza o gráfico 3D em tempo real DEPOIS de salvar
        print(f"🔄 Atualizando gráfico 3D após SELECT ALL ({len(self.selected_tags)} tags)")
        self.update_3d_plot(test_running=False, detected_tags=None)
        # Salva estado da interface
        self._save_complete_ui_state()

    def deselect_all_tags(self):
        """Deseleciona todas as tags"""
        self.selected_tags.clear()
        self.update_tags_list()
        # Atualiza a escala do gráfico baseada no número de tags selecionadas
        self.update_plot_scales()
        # Salva seleção automaticamente PRIMEIRO
        self._save_selected_tags()
        # Atualiza o gráfico 3D em tempo real DEPOIS de salvar
        print(f"🔄 Atualizando gráfico 3D após DESELECT ALL ({len(self.selected_tags)} tags)")
        self.update_3d_plot(test_running=False, detected_tags=None)
        # Salva estado da interface
        self._save_complete_ui_state()

    def erase_selected_tags(self):
        """Remove tags selecionadas"""
        if not self.selected_tags:
            print("⚠️ Nenhuma tag selecionada para deletar")
            return
        
        try:
            deleted_count = 0
            for epc in self.selected_tags:
                if self.database.delete_tag(epc):
                    deleted_count += 1
            
            print(f"✅ {deleted_count} tags deletadas com sucesso")
            self.selected_tags.clear()
            self.update_tags_list()
            # Atualiza a escala do gráfico baseada no número de tags selecionadas
            self.update_plot_scales()
            # Salva seleção automaticamente
            self._save_selected_tags()
            # Atualiza o gráfico 3D após deletar tags
            print(f"🔄 Atualizando gráfico 3D após deletar {deleted_count} tags")
            self.update_3d_plot(test_running=False, detected_tags=None)
            # Salva estado da interface
            self._save_complete_ui_state()
        except Exception as e:
            print(f"❌ Erro ao deletar tags: {e}")

    def save_selected_tags(self):
        """Salva tags selecionadas em arquivo JSON"""
        if not self.selected_tags:
            print("⚠️ Nenhuma tag selecionada para salvar")
            return
        
        try:
            # Busca os dados completos das tags selecionadas
            selected_tags_data = []
            all_tags = self.database.get_tags()
            
            for epc in self.selected_tags:
                # Encontra a tag no banco de dados
                tag_data = None
                for tag in all_tags:
                    if tag.get('epc') == epc:
                        tag_data = tag.copy()  # Cria uma cópia para não modificar o original
                        break
                
                if tag_data:
                    # Busca o apelido da tag
                    apelido = self.get_tag_apelido(epc)
                    tag_data['apelido'] = apelido  # Adiciona o apelido aos dados da tag
                    selected_tags_data.append(tag_data)
            
            if not selected_tags_data:
                print("⚠️ Nenhuma tag válida encontrada para salvar")
                return
            
            # Salva diretamente em arquivo JSON
            filepath = filedialog.asksaveasfilename(
                title=t('population.save_selected_tags'),
                defaultextension=".json",
                filetypes=(("JSON files", "*.json"), ("Todos os arquivos", "*.*"))
            )
            
            if filepath:
                with open(filepath, 'w', encoding='utf-8') as f:
                    json.dump(selected_tags_data, f, indent=2, ensure_ascii=False)
                print(f"✅ {len(selected_tags_data)} tags salvas em: {filepath}")
            
        except Exception as e:
            print(f"❌ Erro ao salvar tags: {e}")

    def update_tags_list(self):
        """Atualiza lista de tags"""
        # Limpa o treeview
        for item in self.tags_tree.get_children():
            self.tags_tree.delete(item)
        
        self.tags_data = []
        # CORREÇÃO: Mostra TODAS as tags (com e sem coordenadas)
        # Tags sem coordenadas podem ter coordenadas adicionadas depois
        tags_from_db = self.database.get_tags()
        
        if not tags_from_db:
            # Insere mensagem quando não há tags
            self.tags_tree.insert('', 'end', values=('', '(Nenhuma tag registrada)'), tags=('empty',))
            return
        
        for tag in tags_from_db:
            epc = tag.get('epc', 'N/A')
            name = tag.get('name', '')
            coordinates = tag.get('coordinates', '')
            
            # Obtém o apelido da tag (se existir) - apenas para armazenamento interno
            apelido = self.get_tag_apelido(epc)
            print(f"🔍 Tag {epc}: apelido='{apelido}'")
            
            # Verifica se a tag está selecionada
            is_selected = epc in self.selected_tags
            checkbox = '☑' if is_selected else '☐'
            
            # CORREÇÃO: Tags sem coordenadas aparecem em VERMELHO
            has_coordinates = coordinates and coordinates.strip()
            tag_style = 'normal' if has_coordinates else 'no_coords'
            
            # Insere no treeview sem apelido (apenas checkbox e EPC)
            item_id = self.tags_tree.insert('', 'end', values=(checkbox, epc), tags=(tag_style,))
            
            # Armazena os dados da tag
            self.tags_data.append({'epc': epc, 'name': name, 'apelido': apelido, 'item_id': item_id})
        
        # Atualiza o contador de tags
        self.update_tags_count()
        
        # Atualiza o gráfico 3D com as cores persistentes
        self.update_3d_plot(test_running=False, detected_tags=None)

    def update_tags_count(self):
        """Atualiza o contador de tags registradas e selecionadas"""
        try:
            # Atualiza contador de tags registradas
            tags_from_db = self.database.get_tags()
            count = len(tags_from_db) if tags_from_db else 0
            
            if count == 0:
                text = t('population.tags_count_registered_none')
                color = "#e74c3c"  # Vermelho
            elif count == 1:
                text = t('population.tags_count_registered_one')
                color = "#27ae60"  # Verde
            else:
                text = t('population.tags_count_registered_many').format(n=count)
                color = "#27ae60"  # Verde
            
            self.tags_count_label.config(text=text, fg=color)
            
            # Atualiza contador de tags selecionadas
            selected_count = len(self.selected_tags)
            if selected_count == 0:
                selected_text = t('population.tags_count_selected_none')
                selected_color = "#e74c3c"  # Vermelho
            elif selected_count == 1:
                selected_text = t('population.tags_count_selected_one')
                selected_color = "#27ae60"  # Verde
            else:
                selected_text = t('population.tags_count_selected_many').format(n=selected_count)
                selected_color = "#27ae60"  # Verde
            
            self.selected_tags_count_label.config(text=selected_text, fg=selected_color)
            
            # Atualiza as escalas do gráfico quando o número de tags muda
            self.update_plot_scales()
        except Exception as e:
            print(f"⚠️ Erro ao atualizar contador de tags: {e}")
            self.tags_count_label.config(text=f"📊 {t('population.error_counting_tags')}", fg="#e74c3c")
            self.selected_tags_count_label.config(text=f"✅ {t('population.error_counting_selection')}", fg="#e74c3c")

    def on_tree_click(self, event):
        """Callback para cliques no treeview de tags"""
        # Só processa cliques simples, não duplos
        if event.type != '4':  # 4 = ButtonPress, não ButtonRelease
            return
            
        region = self.tags_tree.identify("region", event.x, event.y)
        if region == "cell":
            column = self.tags_tree.identify_column(event.x)
            item = self.tags_tree.identify_row(event.y)
            
            if column == '#1' and item:  # Coluna do checkbox
                print(f"🖱️ Clique no checkbox - Item: {item}")
                self.toggle_tag_selection(item)
            elif column == '#2' and item:  # Coluna do EPC - mostra tooltip com apelido
                epc = self.tags_tree.set(item, 'tag')
                apelido = self.get_tag_apelido(epc)
                if apelido:
                    # Mostra tooltip com apelido quando passar o mouse sobre o EPC
                    print(f"🖱️ Clique na coluna EPC - EPC: {epc}, Apelido: '{apelido}'")
                else:
                    print(f"🖱️ Clique na coluna EPC - EPC: {epc} (sem apelido)")

    def toggle_tag_selection(self, item_id):
        """Alterna a seleção de uma tag"""
        try:
            # Encontra os dados da tag
            if hasattr(self, 'tags_data') and isinstance(self.tags_data, (list, tuple)):
                for tag_data in self.tags_data:
                    if tag_data['item_id'] == item_id:
                        epc = tag_data['epc']
                        print(f"🔄 Alternando seleção da tag: {epc}")
                        
                        if epc in self.selected_tags:
                            self.selected_tags.remove(epc)
                            self.tags_tree.set(item_id, 'checkbox', '☐')
                            print(f"❌ Tag {epc} removida da seleção")
                        else:
                            self.selected_tags.add(epc)
                            self.tags_tree.set(item_id, 'checkbox', '☑')
                            print(f"✅ Tag {epc} adicionada à seleção")
                        
                        # Atualiza o contador de tags selecionadas
                        self.update_tags_count()
                        
                        # Atualiza a escala do gráfico baseada no número de tags selecionadas
                        self.update_plot_scales()
                        
                        # Salva seleção automaticamente PRIMEIRO
                        self._save_selected_tags()
                        
                        # Atualiza o gráfico 3D em tempo real DEPOIS de salvar
                        print(f"🔄 Atualizando gráfico 3D após toggle da tag {epc[:20]}...")
                        self.update_3d_plot(test_running=False, detected_tags=None)
                        break
        except Exception as e:
            print(f"⚠️ Erro ao alternar seleção de tag: {e}")


    def _load_previous_state(self):
        """Carrega estado anterior do módulo"""
        try:
            # Carrega tags
            self.update_tags_list()
            
            # Restaura estado da interface após um pequeno delay para garantir que a UI esteja pronta
            self.after(500, self._restore_complete_ui_state)
            
        except Exception as e:
            print(f"⚠️ Erro ao carregar estado anterior: {e}")

    def _restore_complete_ui_state(self):
        """Restaura o estado completo da interface"""
        try:
            # Se há dados de teste, reconstrói a tabela a partir dos dados de teste
            if self.test_data:
                print("🔄 Reconstruindo tabela a partir dos dados de teste...")
                self._update_table_from_test_data()
            else:
                # Restaura estado da tabela apenas se não há dados de teste
                self._restore_table_state()
            
            # Restaura estado do gráfico
            graph_restored_bar_count = self._restore_graph_state()

            # Para evitar snapshots incompletos, sempre reconstrói o gráfico a partir do test_data quando existe.
            if self.test_data:
                print("🔄 Reconstruindo gráfico a partir dos dados de teste (force)...")
                self._reconstruct_graph_from_test_data()
            
            # Restaura estado geral da UI
            self._restore_ui_state()
            
            print("✅ Estado completo da interface restaurado")
            
        except Exception as e:
            print(f"⚠️ Erro ao restaurar estado completo da UI: {e}")

    def _is_graph_state_empty(self):
        """Verifica se o estado do gráfico está vazio"""
        try:
            if os.path.exists(self.GRAPH_STATE_FILE):
                with open(self.GRAPH_STATE_FILE, 'r', encoding='utf-8') as f:
                    graph_state = json.load(f)
                return (not graph_state.get('graph_data') and 
                       not graph_state.get('bar_data'))
            return True
        except:
            return True

    def _reconstruct_graph_from_test_data(self):
        """Reconstrói o gráfico a partir dos dados de teste salvos"""
        try:
            if not self.test_data or not MATPLOTLIB_OK or not hasattr(self, 'ax'):
                print("⚠️ Não é possível reconstruir gráfico: dados insuficientes")
                return
            
            print("🔄 Iniciando reconstrução do gráfico a partir dos dados de teste...")
            
            # Limpa o gráfico
            self.ax.clear()
            
            # Reconfigura o gráfico
            self.ax.set_title(t('population.graph_title'), fontsize=14)
            self.ax.set_xlabel(t('population.graph_xlabel'), fontsize=12)
            self.ax.set_ylabel(t('population.graph_ylabel'), fontsize=12)
            self.ax.grid(True, alpha=0.3)
            
            # Recria a linha de progresso após limpar o gráfico
            self.progress_line, = self.ax.plot([5, 5], [0, 0], color='red', linewidth=6, alpha=1.0, label='Progresso da Potência', zorder=15)
            print("🔴 Linha de progresso recriada após reconstrução")
            
            # Reconstrói as barras a partir dos dados de teste,
            # preenchendo também potências onde não houve novas tags
            # (nesses casos o `cumulative_tags` fica igual ao anterior).
            power_step = 1.0
            try:
                power_step = float(self.attenuator_var.get())
            except Exception:
                power_step = 1.0
            if power_step <= 0:
                power_step = 1.0

            start_power = 5.0  # worker inicia sempre em 5 dBm
            try:
                max_power = self._clamp_power_to_license(float(self.power_var.get()))
            except Exception:
                max_power = None

            # Mapa: power_key -> cumulative_tags
            # power_key é a potência convertida para "passos" (evita erros de float).
            height_by_power_key = {}
            found_any_power = False
            try:
                for data_point in self.test_data:
                    if not isinstance(data_point, dict):
                        continue
                    if 'power' not in data_point or 'cumulative_tags' not in data_point:
                        continue

                    found_any_power = True
                    p = float(data_point.get('power', 0))
                    h = int(data_point.get('cumulative_tags', 0))
                    key = int(round(p / power_step))
                    height_by_power_key[key] = h
            except Exception as e:
                print(f"⚠️ Erro ao processar pontos para reconstrução: {e}")

            if not found_any_power:
                print("⚠️ Nenhum dado de teste válido encontrado para reconstrução")
                self._setup_plot_scales()
            else:
                if max_power is None:
                    # Fallback: usa o máximo que existe nos dados
                    try:
                        max_power = max(
                            float(dp.get('power', 0))
                            for dp in self.test_data
                            if isinstance(dp, dict) and 'power' in dp
                        )
                    except Exception:
                        max_power = start_power

                # Ajusta largura conforme step (mesma lógica do update ao vivo)
                bar_width = 0.3 if abs(power_step - 0.5) < 1e-9 else 0.6

                start_key = int(round(start_power / power_step))
                end_key = int(round(max_power / power_step))
                if end_key < start_key:
                    end_key = start_key

                print(
                    f"🔧 Reconstrução gráfico: start_power={start_power}, max_power={max_power}, "
                    f"power_step={power_step}, start_key={start_key}, end_key={end_key}"
                )

                prev_height = 0
                drawn_bars = 0
                for key in range(start_key, end_key + 1):
                    if key in height_by_power_key:
                        prev_height = height_by_power_key[key]

                    x_position = key * power_step
                    self.ax.bar(
                        x_position,
                        prev_height,
                        width=bar_width,
                        alpha=0.8,
                        color='lightblue',
                    )
                    drawn_bars += 1

                # Configura limites dos eixos de forma segura
                try:
                    min_x = start_key * power_step
                    max_x = end_key * power_step
                    if max_x > min_x:
                        margin = max(1.0, (max_x - min_x) * 0.1)
                        self.ax.set_xlim(min_x - margin, max_x + margin)
                    else:
                        self.ax.set_xlim(min_x - 1, max_x + 1)

                    max_height = max(height_by_power_key.values()) if height_by_power_key else 1
                    self.ax.set_ylim(0, max(max_height + 1, 1))

                    # Usa a configuração padrão de ticks
                    self._setup_plot_scales()

                    # Reconstrói o mapa de EPCs para tooltip (usado para os rótulos)
                    self._reconstruct_power_epcs_map()
                    self.after(200, self._setup_bar_tooltip)
                except Exception as e:
                    print(f"⚠️ Erro ao configurar limites dos eixos: {e}")
                    self.ax.relim()
                    self.ax.autoscale_view()

                print(f"✅ Gráfico reconstruído: {drawn_bars} barras (step={power_step})")
            
            # Redesenha o gráfico
            try:
                self.canvas.draw()
                print("🎨 Gráfico redesenhado")
            except Exception as e:
                print(f"⚠️ Erro ao redesenhar gráfico: {e}")
            
        except Exception as e:
            print(f"❌ Erro ao reconstruir gráfico: {e}")
            import traceback
            traceback.print_exc()

    def _reconstruct_power_epcs_map(self):
        """Reconstrói o mapa de EPCs por potência para o tooltip"""
        try:
            self.power_epcs_map.clear()
            for data_point in self.test_data:
                power = data_point.get('power')
                epcs = data_point.get('detected_epcs', [])
                if power is not None and epcs:
                    # Busca o apelido da tag registrada
                    epcs_with_apelidos = []
                    for epc in epcs:
                        apelido = self.get_tag_apelido(epc)
                        epcs_with_apelidos.append({'epc': epc, 'apelido': apelido})
                    self.power_epcs_map[power] = epcs_with_apelidos
            print(f"✅ Mapa de EPCs reconstruído: {len(self.power_epcs_map)} potências")
        except Exception as e:
            print(f"⚠️ Erro ao reconstruir mapa de EPCs: {e}")

    def _restore_ui_state(self):
        """Restaura o estado geral da interface"""
        try:
            if os.path.exists(self.UI_STATE_FILE):
                with open(self.UI_STATE_FILE, 'r', encoding='utf-8') as f:
                    ui_state = json.load(f)
                
                # Restaura parâmetros se não foram alterados pelo usuário
                if not self.state_changed:
                    self.frequency_var.set(ui_state.get('frequency', '915'))
                    self.power_var.set(ui_state.get('power', '25'))
                    self.distance_var.set(ui_state.get('distance', '1.0'))
                    self.attenuator_var.set(ui_state.get('attenuator', '1.0'))
                    self.description_var.set(ui_state.get('description', ''))
                    self.action_var.set(ui_state.get('action_selected', 'Select an action'))
                
                print(f"🖥️ Estado da UI restaurado: {ui_state.get('test_data_count', 0)} pontos de dados")
                
        except Exception as e:
            print(f"⚠️ Erro ao restaurar estado da UI: {e}")

    def _save_state(self):
        """Salva estado atual do módulo"""
        try:
            # Salva dados do teste atual se houver
            if self.test_data and self.start_time and self.end_time:
                test_record = {
                    'date_time': self.start_time.isoformat(),
                    'description': self.description_var.get() or f"Teste Population {self.start_time.strftime('%d/%m/%Y %H:%M')}",
                    'frequency': float(self.frequency_var.get()),
                    'power': float(self.power_var.get()),
                    'distance': float(self.distance_var.get()),
                    'attenuator': float(self.attenuator_var.get()),
                    'test_data': self.test_data,
                    'module': 'População'
                }
                self.database.save_test_result(test_record)
                self.update_history_list()
        except Exception as e:
            print(f"⚠️ Erro ao salvar estado: {e}")

    def _ensure_persistence_dir(self):
        """Cria o diretório de persistência se não existir"""
        try:
            if not os.path.exists(self.PERSISTENCE_DIR):
                os.makedirs(self.PERSISTENCE_DIR)
                print(f"📁 Diretório de persistência criado: {self.PERSISTENCE_DIR}")
        except Exception as e:
            print(f"⚠️ Erro ao criar diretório de persistência: {e}")

    def _load_persisted_data(self):
        """Carrega dados persistidos do módulo"""
        try:
            print("🔍 DEBUG: Iniciando _load_persisted_data")
            # Carrega parâmetros de teste
            self._load_test_parameters()
            
            # Carrega dados de teste
            self._load_test_data()
            
            # Carrega tags selecionadas
            self._load_selected_tags()
            
            print("✅ Dados persistidos carregados com sucesso (tags reprovadas incluídas em test_data)")
        except Exception as e:
            print(f"⚠️ Erro ao carregar dados persistidos: {e}")

    def _load_test_parameters(self):
        """Carrega parâmetros de teste salvos"""
        try:
            legacy_params_file = os.path.join("data", "populacao_params.json")
            target_params_file = self.PARAMS_FILE
            
            if os.path.exists(target_params_file) or os.path.exists(legacy_params_file):
                src_file = target_params_file if os.path.exists(target_params_file) else legacy_params_file
                with open(src_file, 'r', encoding='utf-8') as f:
                    params = json.load(f)
                
                # Atualiza variáveis com valores salvos
                self.frequency_var.set(str(params.get('frequency', '915')))
                self.power_var.set(str(params.get('power', '25')))
                self.distance_var.set(str(params.get('distance', '1.0')))
                self.attenuator_var.set(str(params.get('attenuator', '1.0')))
                self.description_var.set(str(params.get('description', '')))
                
                # Migra para o caminho absoluto para garantir consistência nos próximos inícios
                if src_file != target_params_file:
                    try:
                        os.makedirs(self.PERSISTENCE_DIR, exist_ok=True)
                        with open(target_params_file, 'w', encoding='utf-8') as wf:
                            json.dump(params, wf, indent=2, ensure_ascii=False)
                        print(f"🔄 Migration: parâmetros carregados de legacy e movidos para: {target_params_file}")
                    except Exception as e:
                        print(f"⚠️ Migration: falha ao mover params para o caminho absoluto: {e}")
                
                print(f"📋 Parâmetros carregados: {params}")
        except Exception as e:
            print(f"⚠️ Erro ao carregar parâmetros: {e}")

    def _load_test_data(self):
        """Carrega dados de teste salvos"""
        try:
            legacy_test_data_file = os.path.join("data", "populacao_test_data.json")
            target_test_data_file = self.TEST_DATA_FILE
            
            if os.path.exists(target_test_data_file) or os.path.exists(legacy_test_data_file):
                src_file = target_test_data_file if os.path.exists(target_test_data_file) else legacy_test_data_file
                with open(src_file, 'r', encoding='utf-8') as f:
                    test_data = json.load(f)
                
                # Converte timestamps de string para datetime
                for data_point in test_data:
                    if 'timestamp' in data_point:
                        data_point['timestamp'] = datetime.fromisoformat(data_point['timestamp'])
                
                self.test_data = test_data
                print(f"📊 {len(test_data)} pontos de dados de teste carregados")

                # --- INÍCIO DA CORREÇÃO ---
                # Reconstrói o mapa de EPCs e apelidos por potência para o tooltip
                self.power_epcs_map.clear()
                for data_point in self.test_data:
                    power = data_point.get('power')
                    epcs = data_point.get('detected_epcs')
                    apelido = data_point.get('apelido', '')
                    if power is not None and epcs:
                        # Garante que a chave exista antes de adicionar
                        if power not in self.power_epcs_map:
                            self.power_epcs_map[power] = []
                        # Adiciona EPCs com apelidos, evitando duplicatas
                        for epc in epcs:
                            # Verifica se o EPC já existe na lista
                            epc_exists = any(item['epc'] == epc for item in self.power_epcs_map[power])
                            if not epc_exists:
                                self.power_epcs_map[power].append({'epc': epc, 'apelido': apelido})
                print("✅ Mapa de EPCs para tooltip reconstruído com sucesso.")
                
                # Popula a tabela com os dados carregados
                self._populate_table_with_loaded_data()
                # --- FIM DA CORREÇÃO ---
                
                # Migra para o caminho absoluto para garantir consistência
                if src_file != target_test_data_file:
                    try:
                        os.makedirs(self.PERSISTENCE_DIR, exist_ok=True)
                        with open(target_test_data_file, 'w', encoding='utf-8') as wf:
                            json.dump(test_data, wf, indent=2, ensure_ascii=False, default=str)
                        print(f"🔄 Migration: test_data carregado de legacy e movido para: {target_test_data_file}")
                    except Exception as e:
                        print(f"⚠️ Migration: falha ao mover test_data para o caminho absoluto: {e}")

        except Exception as e:
            print(f"⚠️ Erro ao carregar dados de teste: {e}")

    def _populate_table_with_loaded_data(self):
        """Popula a tabela com os dados carregados do arquivo"""
        try:
            if not hasattr(self, 'test_data') or not self.test_data:
                return
            
            # Limpa apenas as linhas que NÃO são tags reprovadas (não lidas)
            # Preserva as tags reprovadas que já estão na tabela
            items_to_delete = []
            for item in self.tree.get_children():
                tags = self.tree.item(item).get('tags', [])
                # Se não é uma tag reprovada (not_read), marca para deletar
                if 'not_read' not in tags:
                    items_to_delete.append(item)
            
            # Remove apenas as linhas que não são tags reprovadas
            for item in items_to_delete:
                self.tree.delete(item)
            
            # Adiciona cada ponto de dados à tabela
            for data_point in self.test_data:
                # Prepara EPCs detectados para exibição
                detected_epcs = data_point.get('detected_epcs', [])
                if isinstance(detected_epcs, (list, tuple)) and detected_epcs:
                    epc_display = ', '.join(str(epc) for epc in detected_epcs)
                else:
                    epc_display = "Nenhuma tag detectada"
                
                # Prepara dados para inserção
                comment = data_point.get('comment', '')
                apelido = data_point.get('apelido', '')  # Campo apelido (vazio para dados antigos)
                timestamp = data_point.get('timestamp', datetime.now())
                if isinstance(timestamp, str):
                    timestamp = datetime.fromisoformat(timestamp)
                
                # Formata data/hora conforme idioma
                translator = get_translator()
                if translator.get_language() == 'en':
                    timestamp_str = timestamp.strftime('%m/%d/%y %H:%M:%S')  # Data / Hora
                else:
                    timestamp_str = timestamp.strftime('%d/%m/%Y %H:%M:%S')  # Data / Hora
                
                values = (
                    str(epc_display),  # EPC (garante que seja string)
                    apelido,  # Apelido (vazio para dados antigos)
                    comment,  # Comentário
                    f"{data_point.get('power', 0):.1f}",  # Potência (dBm)
                    f"{data_point.get('rssi_avg', 0):.1f}",  # RSSI (dBm)
                    f"{data_point.get('frequency', 915):.0f}",  # Frequência
                    timestamp_str  # Data / Hora
                )
                
                self.tree.insert('', 'end', values=values)
            
            print(f"✅ Tabela populada com {len(self.test_data)} pontos de dados")
            
        except Exception as e:
            print(f"⚠️ Erro ao popular tabela com dados carregados: {e}")


    def _clear_persisted_data(self):
        """Limpa dados persistidos para evitar conflitos na importação"""
        try:
            # Remove arquivos de persistência se existirem
            if os.path.exists(self.TEST_DATA_FILE):
                os.remove(self.TEST_DATA_FILE)
            if os.path.exists(self.SELECTED_TAGS_FILE):
                os.remove(self.SELECTED_TAGS_FILE)
            if os.path.exists(self.TABLE_STATE_FILE):
                os.remove(self.TABLE_STATE_FILE)
            if os.path.exists(self.GRAPH_STATE_FILE):
                os.remove(self.GRAPH_STATE_FILE)
            if os.path.exists(self.UI_STATE_FILE):
                os.remove(self.UI_STATE_FILE)
            print("🧹 Dados persistidos limpos para importação")
        except Exception as e:
            print(f"⚠️ Erro ao limpar dados persistidos: {e}")

    def _save_persisted_data(self):
        """Salva dados do módulo para persistência"""
        try:
            # Salva parâmetros de teste
            self._save_test_parameters()
            
            # Salva dados de teste
            self._save_test_data()
            
            # Salva tags selecionadas
            self._save_selected_tags()
            
            # CORREÇÃO: Tags reprovadas agora são salvas em test_data - não precisa mais de arquivo separado
            
            print("💾 Dados persistidos salvos com sucesso (tags reprovadas incluídas em test_data)")
        except Exception as e:
            print(f"⚠️ Erro ao salvar dados persistidos: {e}")

    def _save_test_parameters(self):
        """Salva parâmetros de teste"""
        try:
            params = {
                'frequency': self.frequency_var.get(),
                'power': self.power_var.get(),
                'distance': self.distance_var.get(),
                'attenuator': self.attenuator_var.get(),
                'description': self.description_var.get(),
                'last_saved': datetime.now().isoformat()
            }
            
            with open(self.PARAMS_FILE, 'w', encoding='utf-8') as f:
                json.dump(params, f, indent=2, ensure_ascii=False)
                
        except Exception as e:
            print(f"⚠️ Erro ao salvar parâmetros: {e}")

    def _save_test_data(self):
        """Salva dados de teste"""
        try:
            # Converte timestamps para string para serialização JSON
            serializable_data = []
            if hasattr(self, 'test_data') and isinstance(self.test_data, (list, tuple)):
                for data_point in self.test_data:
                    serializable_point = data_point.copy()
                    if 'timestamp' in serializable_point and isinstance(serializable_point['timestamp'], datetime):
                        serializable_point['timestamp'] = serializable_point['timestamp'].isoformat()
                    serializable_data.append(serializable_point)
            
            with open(self.TEST_DATA_FILE, 'w', encoding='utf-8') as f:
                json.dump(serializable_data, f, indent=2, ensure_ascii=False)
            
            print(f"💾 Dados de teste salvos: {len(serializable_data)} pontos")
            
            # Debug: verifica se os campos apelido e comment estão sendo salvos
            apelidos_count = sum(1 for dp in serializable_data if dp.get('apelido', '').strip())
            comments_count = sum(1 for dp in serializable_data if dp.get('comment', '').strip())
            print(f"🔍 Debug: {apelidos_count} apelidos e {comments_count} comentários não vazios salvos")
                
        except Exception as e:
            print(f"⚠️ Erro ao salvar dados de teste: {e}")

    def _load_selected_tags(self):
        """Carrega tags selecionadas salvas"""
        try:
            legacy_selected_tags_file = os.path.join("data", "populacao_selected_tags.json")
            target_selected_tags_file = self.SELECTED_TAGS_FILE
            
            if os.path.exists(target_selected_tags_file) or os.path.exists(legacy_selected_tags_file):
                src_file = target_selected_tags_file if os.path.exists(target_selected_tags_file) else legacy_selected_tags_file
                with open(src_file, 'r', encoding='utf-8') as f:
                    selected_data = json.load(f)
                
                # Restaura as tags selecionadas
                self.selected_tags = set(selected_data.get('selected_tags', []))
                
                # Migra para o caminho absoluto para garantir consistência
                if src_file != target_selected_tags_file:
                    try:
                        os.makedirs(self.PERSISTENCE_DIR, exist_ok=True)
                        with open(target_selected_tags_file, 'w', encoding='utf-8') as wf:
                            json.dump(selected_data, wf, indent=2, ensure_ascii=False)
                        print(f"🔄 Migration: selected_tags carregados de legacy e movidos para: {target_selected_tags_file}")
                    except Exception as e:
                        print(f"⚠️ Migration: falha ao mover selected_tags para o caminho absoluto: {e}")
                
                print(f"🏷️ {len(self.selected_tags)} tags selecionadas carregadas")
        except Exception as e:
            print(f"⚠️ Erro ao carregar tags selecionadas: {e}")

    def _save_selected_tags(self):
        """Salva tags selecionadas"""
        try:
            selected_data = {
                'selected_tags': list(self.selected_tags),
                'last_saved': datetime.now().isoformat()
            }
            
            with open(self.SELECTED_TAGS_FILE, 'w', encoding='utf-8') as f:
                json.dump(selected_data, f, indent=2, ensure_ascii=False)
                
        except Exception as e:
            print(f"⚠️ Erro ao salvar tags selecionadas: {e}")


    def _on_destroy(self, event):
        """Chamado quando o módulo é destruído - salva dados automaticamente"""
        try:
            # Para o auto-save
            if self.auto_save_timer:
                self.after_cancel(self.auto_save_timer)
            
            # Salva estado completo da interface
            self._save_complete_ui_state()
            
            # Salva dados persistidos
            self._save_persisted_data()
            
            # Salva tags detectadas permanentemente
            self._save_detected_tags()
            
            # Salva tags NÃO lidas persistidas (se houver)
            try:
                if hasattr(self, 'not_read_tags_permanent') and self.not_read_tags_permanent:
                    os.makedirs(self.PERSISTENCE_DIR, exist_ok=True)
                    with open(self.NOT_READ_TAGS_FILE, 'w', encoding='utf-8') as f:
                        json.dump({'not_read_tags': list(self.not_read_tags_permanent), 'last_updated': datetime.now().isoformat()}, f, indent=2, ensure_ascii=False)
                    print(f"💾 Não lidas persistidas no fechamento: {len(self.not_read_tags_permanent)} tags")
            except Exception as _e_close_nr:
                print(f"⚠️ Erro ao salvar não lidas no fechamento: {_e_close_nr}")
            
            print("💾 Estado completo salvo no fechamento")
        except Exception as e:
            print(f"⚠️ Erro ao salvar dados no fechamento: {e}")

    def generate_pdf_report(self):
        """Gera relatório PDF com dados de população"""
        try:
            # Verifica se há dados para relatório
            if not hasattr(self, 'test_data') or not self.test_data:
                messagebox.showwarning(t('population.warning_no_tags_selected'), t('population.warning_no_data_report'), parent=self)
                return
            
            # Gera nome do arquivo com data e hora
            now = datetime.now()
            filename = f"Relatorio_Populacao_{now.strftime('%Y%m%d_%H%M%S')}.pdf"
            
            # Diálogo para salvar arquivo
            filepath = filedialog.asksaveasfilename(
                defaultextension=".pdf",
                filetypes=[("PDF Files", "*.pdf")],
                initialfile=filename,
                title=t('population.save_pdf_report'),
                parent=self
            )
            
            if not filepath:
                return  # Usuário cancelou
            
            # Mostra mensagem de progresso
            print("🔄 Gerando relatório PDF...")
            self.update()
            
            # Gera o PDF com dados de população
            result = self._generate_pdf_with_data(filepath, self.test_data)
            
            if result['success']:
                print("✅ Relatório PDF gerado com sucesso!")
                messagebox.showinfo(t('population.success'), t('population.success_report_saved').format(filepath=filepath), parent=self)
                
                # Abre o PDF automaticamente (fallbacks se startfile falhar)
                open_path_with_default_app(filepath, log_tag="populacao")
            else:
                error_msg = result.get('error', 'Erro desconhecido')
                print(f"❌ Erro ao gerar relatório PDF: {error_msg}")
                messagebox.showerror(t('population.error'), t('population.error_generate_pdf_failed').format(error=error_msg), parent=self)
                
        except Exception as e:
            print(f"❌ Erro ao gerar relatório PDF: {str(e)}")
            import traceback
            traceback.print_exc()
            messagebox.showerror(t('population.error'), t('population.error_generate_pdf_failed').format(error=str(e)), parent=self)

    def _generate_pdf_with_data(self, filepath, test_data):
        """Gera PDF usando ReportLab com dados de população"""
        try:
            print(f"🔄 Gerando PDF para {len(test_data)} pontos de dados...")
            print(f"📁 Caminho do arquivo: {filepath}")
            
            from reportlab.lib.pagesizes import A4
            from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
            from reportlab.lib.units import inch
            from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image, PageBreak, KeepTogether, CondPageBreak
            from reportlab.lib import colors
            from reportlab.lib.enums import TA_CENTER, TA_LEFT
            from .pdf_pagination_utils import create_smart_pagination, get_height_estimate
            import tempfile
            import io
            import matplotlib.pyplot as plt
            from datetime import datetime
            
            print("✅ Importações do ReportLab concluídas com sucesso")
            
            # Função para obter informações do sistema
            def _get_system_info():
                from datetime import datetime
                try:
                    # Tenta obter informações via app_shell (método preferido)
                    if hasattr(self, 'app_shell') and self.app_shell and hasattr(self.app_shell, 'license_manager'):
                        system_info = self.app_shell.license_manager.get_active_license_system_info(4)  # com_port=4
                        return system_info
                    
                    # Fallback: cria LicenseManager temporário
                    try:
                        from .license_module import LicenseManager
                        import os
                        LICENSE_DB_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "licenses.json")
                        license_manager = LicenseManager(LICENSE_DB_FILE)
                        system_info = license_manager.get_active_license_system_info(4)
                        return system_info
                    except Exception as fallback_error:
                        print(f"⚠️ Erro no fallback de informações do sistema: {fallback_error}")
                    
                    # Fallback final: informações básicas
                    from .i18n import get_translator
                    translator = get_translator()
                    date_format = '%d/%m/%Y %H:%M:%S' if translator.get_language() == 'pt' else '%m/%d/%Y %I:%M:%S %p'
                    return {
                        'software': SOFTWARE_INFO['software'],
                        'hardware': 'N/A',
                        'firmware': 'N/A',
                        'serial_number': 'N/A',
                        'license': 'N/A',
                        'generated_at': datetime.now().strftime(date_format)
                    }
                    
                except Exception as e:
                    print(f"⚠️ Erro geral ao obter informações do sistema: {e}")
                    from datetime import datetime
                    from .i18n import get_translator
                    translator = get_translator()
                    date_format = '%d/%m/%Y %H:%M:%S' if translator.get_language() == 'pt' else '%m/%d/%Y %I:%M:%S %p'
                    return {
                        'software': SOFTWARE_INFO['software'],
                        'hardware': 'N/A',
                        'firmware': 'N/A',
                        'serial_number': 'N/A',
                        'license': 'N/A',
                        'generated_at': datetime.now().strftime(date_format)
                    }
            
            # Cria o documento PDF
            doc = SimpleDocTemplate(filepath, pagesize=A4)
            styles = getSampleStyleSheet()
            
            # Estilos customizados
            title_style = ParagraphStyle(
                'CustomTitle',
                parent=styles['Title'],
                fontSize=24,
                spaceAfter=30,
                alignment=TA_CENTER,
                textColor=colors.HexColor('#2c3e50')
            )
            
            heading_style = ParagraphStyle(
                'CustomHeading',
                parent=styles['Heading2'],
                fontSize=16,
                spaceAfter=12,
                textColor=colors.HexColor('#2c3e50')
            )
            try:
                heading_style.keepWithNext = 1
            except Exception:
                pass
            
            # Conteúdo do documento com paginação inteligente
            pagination = create_smart_pagination()
            
            # Logo Fasttag
            try:
                root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
                logo_path = os.path.join(root, 'assets', 'images', 'fasttag_logo.png')
                if os.path.exists(logo_path):
                    logo_size = 1.5*inch
                    logo_img = Image(logo_path, width=logo_size, height=logo_size)
                    pagination.add_element(logo_img, get_height_estimate('logo'))
                    pagination.add_spacer(0.2*inch)
            except Exception as e:
                print(f"⚠️ Erro ao carregar logo: {e}")
            
            # Título
            pagination.add_element(Paragraph(t('population.report_title'), title_style), get_height_estimate('title'))
            pagination.add_spacer(0.2*inch)
            
            # Informações do Sistema
            system_info = _get_system_info()
            pagination.add_element(Paragraph(t('population.system_info'), heading_style), get_height_estimate('heading'))
            
            system_data = [
                [t('population.software_label'), system_info.get('software', 'N/A')],
                [t('population.hardware_label'), system_info.get('hardware', 'N/A')],
                [t('population.firmware_label'), system_info.get('firmware', 'N/A')],
                [t('population.serial_number_label'), system_info.get('serial_number', 'N/A')],
                [t('population.license_label'), system_info.get('license', 'N/A')],
                [t('population.generated_at_label'), system_info.get('generated_at', 'N/A')]
            ]
            
            system_table = Table(system_data, colWidths=[2*inch, 3*inch])
            system_table.setStyle(TableStyle([
                ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#f8f9fa')),
                ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
                ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
                ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
                ('FONTSIZE', (0, 0), (-1, -1), 10),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
                ('BACKGROUND', (0, 0), (0, -1), colors.HexColor('#e9ecef')),
            ]))
            pagination.add_element(system_table, len(system_data) * get_height_estimate('table_row') + get_height_estimate('table_header'))
            pagination.add_spacer(0.2*inch)
            
            # Configuração do Teste (quebra condicional para não separar título do conteúdo)
            pagination.add_with_break_if_needed(Paragraph(t('population.test_config_title'), heading_style), get_height_estimate('heading'), force_break=False)
            
            # Obtém parâmetros atuais das variáveis da interface
            current_power = self.power_var.get() if hasattr(self, 'power_var') else 'N/A'
            current_freq = self.frequency_var.get() if hasattr(self, 'frequency_var') else 'N/A'
            current_distance = self.distance_var.get() if hasattr(self, 'distance_var') else 'N/A'
            current_attenuator = self.attenuator_var.get() if hasattr(self, 'attenuator_var') else 'N/A'
            current_description = self.description_var.get() if hasattr(self, 'description_var') else 'N/A'
            
            config_data = [
                [t('population.parameter'), t('population.value')],
                [t('population.power_max_dbm'), str(current_power)],
                [t('population.frequency_mhz'), str(current_freq)],
                [t('population.distance_m'), str(current_distance)],
                [t('population.power_step_db'), str(current_attenuator)],
                [t('population.description'), str(current_description)]
            ]
            
            config_table = Table(config_data, colWidths=[2.5*inch, 2.5*inch])
            config_table.setStyle(TableStyle([
                ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2c3e50')),
                ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
                ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
                ('FONTSIZE', (0, 0), (-1, -1), 10),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
                ('GRID', (0, 0), (-1, -1), 1, colors.black)
            ]))
            pagination.add_group_with_break([config_table], len(config_data) * get_height_estimate('table_row') + get_height_estimate('table_header'))
            pagination.add_spacer(0.2*inch)
            
            # Resumo dos Resultados
            try:
                pagination.add_with_break_if_needed(Paragraph(t('population.test_results_summary'), heading_style), get_height_estimate('heading'), force_break=False)
                
                # Coleta de métricas
                # Tags selecionadas
                try:
                    with open(self.SELECTED_TAGS_FILE, 'r', encoding='utf-8') as f:
                        _sel = json.load(f)
                        selected_tags_list = _sel.get('selected_tags', []) if isinstance(_sel, dict) else []
                except Exception:
                    selected_tags_list = list(getattr(self, 'selected_tags', set())) if hasattr(self, 'selected_tags') else []
                num_testadas = len(set(selected_tags_list))
                
                # Tags lidas
                try:
                    read_tags_set = set(getattr(self, 'detected_tags_permanent', set()))
                except Exception:
                    read_tags_set = set()
                
                num_lidas = len(read_tags_set)
                num_nao_lidas = max(0, num_testadas - num_lidas)
                
                # Margens por leitura individual (apenas quando temos power por tag)
                margins = []
                try:
                    max_power_for_summary = float(self.power_var.get()) if hasattr(self, 'power_var') else 25.0
                except Exception:
                    max_power_for_summary = 25.0
                
                try:
                    for dp in getattr(self, 'test_data', []) or []:
                        if isinstance(dp.get('detected_epcs', []), (list, tuple)) and len(dp['detected_epcs']) == 1:
                            p = float(dp.get('power', 0))
                            margins.append(max_power_for_summary - p)
                except Exception:
                    pass
                
                if margins:
                    margem_media = sum(margins) / len(margins)
                    menor_margem = min(margins)
                else:
                    margem_media = 0.0
                    menor_margem = 0.0
                
                resumo_data = [
                    [t('pdf.population_summary_num_tested'), str(num_testadas)],
                    [t('pdf.population_summary_num_read'), str(num_lidas)],
                    [t('pdf.population_summary_num_not_read'), str(num_nao_lidas)],
                    [t('pdf.population_summary_avg_margin'), f"{margem_media:.1f}"],
                    [t('pdf.population_summary_min_margin'), f"{menor_margem:.1f}"]
                ]
                resumo_table = Table(resumo_data, colWidths=[3.0*inch, 2.0*inch])
                resumo_table.setStyle(TableStyle([
                    ('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#f8f9fa')),
                    ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
                    ('FONTSIZE', (0, 0), (-1, -1), 10),
                    ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
                    ('GRID', (0, 0), (-1, -1), 0.5, colors.grey)
                ]))
                pagination.add_group_with_break([resumo_table], len(resumo_data) * get_height_estimate('table_row') + get_height_estimate('table_header'))
                pagination.add_spacer(0.2*inch)
            except Exception as _e_resumo:
                print(f"⚠️ Erro ao gerar Resumo dos Resultados: {_e_resumo}")
            
            # Dados dos Testes
            pagination.add_with_break_if_needed(Paragraph(t('pdf.population_test_data'), heading_style), get_height_estimate('heading'), force_break=False)
            
            if test_data:
                # Prepara dados para tabela - exatamente como no módulo
                table_data = [[t('pdf.population_epc_col'), t('pdf.population_nickname_col'), t('pdf.population_comment_col'), t('pdf.population_coords_col'), t('pdf.population_power_col'), t('pdf.population_rssi_col'), t('pdf.population_margin_col'), t('pdf.population_freq_col'), t('pdf.population_datetime_col')]]
                
                # Conjunto de EPCs lidos ao longo do teste (para identificar "não lidas")
                read_epcs = set()
                nao_lida_row_indices = []
                
                for data_point in test_data[-20:]:  # Últimos 20 pontos
                    # EPC detectados
                    detected_epcs = data_point.get('detected_epcs', [])
                    if isinstance(detected_epcs, (list, tuple)) and detected_epcs:
                        epc_display = ', '.join(str(epc) for epc in detected_epcs)
                        for _epc in detected_epcs:
                            try:
                                read_epcs.add(str(_epc))
                            except Exception:
                                pass
                    else:
                        epc_display = t('pdf.population_no_tag_detected')
                    
                    # Coordenadas - obtém do banco de dados
                    coordinates = ''
                    if detected_epcs:
                        # Pega as coordenadas da primeira tag detectada
                        for epc in detected_epcs:
                            tag_coords = self.get_tag_coordinates(epc)
                            if tag_coords:
                                coordinates = tag_coords
                                break  # Usa apenas a primeira tag com coordenadas
                    
                    # Outros campos
                    apelido = data_point.get('apelido', '')
                    comment = data_point.get('comment', '')
                    power = data_point.get('power', 0)
                    rssi_avg = data_point.get('rssi_avg', 0)
                    frequency = data_point.get('frequency', 915)
                    
                    # Timestamp
                    timestamp = data_point.get('timestamp', datetime.now())
                    if isinstance(timestamp, str):
                        try:
                            timestamp = datetime.fromisoformat(timestamp)
                        except:
                            timestamp = datetime.now()
                    elif isinstance(timestamp, datetime):
                        pass
                    else:
                        timestamp = datetime.now()
                    
                    # Calcula margem para o PDF
                    try:
                        max_power_pdf = float(self.power_var.get()) if hasattr(self, 'power_var') else 25.0
                        margin_pdf = max_power_pdf - power
                    except (ValueError, AttributeError):
                        margin_pdf = 0.0
                    
                    # Se a tag não foi lida, exibe '-' para Potencia, RSSI, Margem e Data/Hora no PDF
                    if comment == t('pdf.population_not_read') or comment == '(NAO LIDA)':  # Compatibilidade com dados antigos
                        potencia_pdf = '-'
                        rssi_pdf = '-'
                        margem_pdf_str = '-'
                        data_hora_pdf = '-'
                        # Adiciona índice da linha para aplicar fundo rosa
                        row_index = len(table_data)
                        nao_lida_row_indices.append(row_index)
                    else:
                        potencia_pdf = f"{power:.1f}"
                        rssi_pdf = f"{rssi_avg:.1f}"
                        margem_pdf_str = f"{margin_pdf:.1f}"
                        # Formata data/hora conforme idioma (mm/dd/yy para inglês)
                        translator = get_translator()
                        current_lang = translator.get_language()
                        if current_lang == 'en':
                            data_hora_pdf = timestamp.strftime('%m/%d/%y %I:%M:%S %p')
                        else:
                            data_hora_pdf = timestamp.strftime('%d/%m/%Y %H:%M:%S')
                    
                    table_data.append([
                        epc_display[:50] + '...' if len(epc_display) > 50 else epc_display,  # EPC
                        apelido,  # Apelido
                        comment,  # Comentário
                        coordinates,  # Coordenadas
                        potencia_pdf,  # Potência (dBm)
                        rssi_pdf,  # RSSI (dBm)
                        margem_pdf_str,  # Margem (dBm)
                        f"{frequency:.0f}",  # Frequência
                        data_hora_pdf  # Data / Hora
                    ])

                # Adiciona linhas para tags selecionadas que NÃO foram lidas no teste
                try:
                    # Preferência: tags selecionadas no módulo
                    selected_tags = []
                    try:
                        with open(self.SELECTED_TAGS_FILE, 'r', encoding='utf-8') as f:
                            _sel = json.load(f)
                            selected_tags = _sel.get('selected_tags', []) if isinstance(_sel, dict) else []
                    except Exception:
                        selected_tags = list(getattr(self, 'selected_tags', set())) if hasattr(self, 'selected_tags') else []
                    
                    # Tenta obter frequência atual configurada
                    try:
                        current_freq_value = float(self.frequency_var.get()) if hasattr(self, 'frequency_var') else None
                    except Exception:
                        current_freq_value = None
                    
                    for epc in selected_tags:
                        if str(epc) not in read_epcs:
                            # Busca apelido e coordenadas
                            apelido_nao_lida = self.get_tag_apelido(epc) if hasattr(self, 'get_tag_apelido') else ''
                            coords_nao_lida = self.get_tag_coordinates(epc) if hasattr(self, 'get_tag_coordinates') else ''
                            row_index = len(table_data)  # índice da linha que será adicionada
                            table_data.append([
                                str(epc),                                  # EPC
                                apelido_nao_lida,                           # Apelido
                                t('pdf.population_not_read'),                # Comentário
                                coords_nao_lida,                             # Coordenadas
                                '-',                                         # Potência (dBm)
                                '-',                                         # RSSI (dBm)
                                '-',                                         # Margem (dBm)
                                f"{current_freq_value:.0f}" if isinstance(current_freq_value, (int, float)) else '-',  # Frequência
                                '-'                                          # Data / Hora
                            ])
                            nao_lida_row_indices.append(row_index)
                except Exception as _e_nao_lida:
                    print(f"⚠️ Falha ao adicionar linhas de tags não lidas: {_e_nao_lida}")
                
                # Ajuste: mais espaço para EPC e Data/Hora, mantendo margem pequena
                test_table = Table(table_data, colWidths=[1.6*inch, 0.7*inch, 0.9*inch, 0.7*inch, 0.7*inch, 0.7*inch, 0.7*inch, 0.7*inch, 1.0*inch])
                style_cmds = [
                    ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2c3e50')),
                    ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
                    ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                    ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
                    ('FONTSIZE', (0, 0), (-1, -1), 7),
                    ('BOTTOMPADDING', (0, 0), (-1, -1), 3),
                    ('GRID', (0, 0), (-1, -1), 1, colors.black)
                ]
                # Aplica fundo rosa claro nas linhas de tags não lidas
                for row in nao_lida_row_indices:
                    try:
                        style_cmds.append(('BACKGROUND', (0, row), (-1, row), colors.HexColor('#ffe6f0')))
                    except Exception:
                        pass
                test_table.setStyle(TableStyle(style_cmds))
                pagination.add_element(test_table, len(table_data) * get_height_estimate('table_row') + get_height_estimate('table_header'))
            else:
                pagination.add_element(Paragraph(t('pdf.no_data_available'), styles['Normal']), get_height_estimate('paragraph'))
            
            pagination.add_spacer(0.2*inch)
            
            # Gráficos dos Testes
            pagination.add_with_break_if_needed(Paragraph(t('pdf.charts_title_antenna'), heading_style), get_height_estimate('heading'), force_break=False)  # Reutiliza chave de antenna já que é a mesma string
            
            # Tenta usar um snapshot FIEL do gráfico atual da UI (mesmas dimensões e estilo)
            snapshot_done = False
            try:
                if hasattr(self, 'fig') and self.fig is not None:
                    # Oculta a linha vermelha de progresso, se existir
                    original_progress_visible = False
                    try:
                        if hasattr(self, 'progress_line') and self.progress_line is not None:
                            original_progress_visible = self.progress_line.get_visible()
                            self.progress_line.set_visible(False)
                    except Exception:
                        pass
                    
                    # Renderiza a figura atual e salva em PNG com o mesmo tamanho/DPI
                    import io as _io
                    img_buf = _io.BytesIO()
                    self.fig.savefig(img_buf, format='png', dpi=self.fig.dpi, bbox_inches='tight')
                    img_buf.seek(0)
                    
                    # Restaura visibilidade da linha de progresso
                    try:
                        if hasattr(self, 'progress_line') and self.progress_line is not None:
                            self.progress_line.set_visible(original_progress_visible)
                    except Exception:
                        pass
                    
                    # Usa exatamente as dimensões da figura
                    fig_w_in, fig_h_in = self.fig.get_size_inches()
                    snapshot_img = Image(img_buf, width=fig_w_in*inch, height=fig_h_in*inch)
                    pagination.add_element(snapshot_img, get_height_estimate('chart_large'))
                    snapshot_done = True
                    print("✅ Snapshot do gráfico da UI incluído no PDF")

                    # Também tenta incluir o snapshot do gráfico 3D, se existir
                    try:
                        if hasattr(self, 'fig_3d') and self.fig_3d is not None:
                            _img3d = _io.BytesIO()
                            self.fig_3d.savefig(_img3d, format='png', dpi=self.fig_3d.dpi, bbox_inches='tight')
                            _img3d.seek(0)
                            w3d_in, h3d_in = self.fig_3d.get_size_inches()
                            snapshot_img_3d = Image(_img3d, width=w3d_in*inch, height=h3d_in*inch)
                            pagination.add_spacer(0.1*inch)
                            pagination.add_element(snapshot_img_3d, get_height_estimate('chart_medium'))
                            print("✅ Snapshot do gráfico 3D incluído no PDF")
                    except Exception as _snap3d_err:
                        print(f"⚠️ Falha ao capturar snapshot do gráfico 3D: {_snap3d_err}")
            except Exception as _snap_err:
                print(f"⚠️ Falha ao capturar snapshot do gráfico: {_snap_err}")
                snapshot_done = False
            
            if not snapshot_done and self.test_data and len(self.test_data) > 1:
                try:
                    # CORREÇÃO: Usa exatamente a mesma lógica do módulo
                    # Processa TODOS os pontos de dados, incluindo duplicatas
                    powers = []
                    cumulative_tags = []
                    
                    print(f"🔍 DEBUG RELATÓRIO: Processando {len(self.test_data)} pontos de dados...")
                    
                    # Processa cada ponto de dados individualmente (como o módulo)
                    for i, data_point in enumerate(self.test_data):
                        power = data_point.get('power', 0)
                        cum_tags = data_point.get('cumulative_tags', 0)
                        
                        # Adiciona TODOS os pontos, mesmo duplicatas
                        powers.append(power)
                        cumulative_tags.append(cum_tags)
                        
                        if i < 10:  # Mostra apenas os primeiros 10 para debug
                            print(f"   📊 Ponto {i+1}: potência={power}, tags={cum_tags}")
                    
                    print(f"🔍 DEBUG RELATÓRIO: Total de pontos processados: {len(powers)}")
                    print(f"🔍 DEBUG RELATÓRIO: Deveria ter {len(powers)} barras")
                    
                    print(f"🔍 DEBUG RELATÓRIO: Range de potências: {min(powers) if powers else 'N/A'} a {max(powers) if powers else 'N/A'}")
                    print(f"🔍 DEBUG RELATÓRIO: Potências únicas: {len(set(powers)) if powers else 0}")
                    
                    if powers and cumulative_tags:
                        # Gráfico 1: Tags Acumuladas vs Potência (formato de barras como no módulo)
                        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
                        
                        # Gráfico de barras - EXATAMENTE como no módulo (tempo real)
                        # Usa a mesma lógica da função _update_plot (tempo real)
                        
                        # Largura da barra baseada no step de potência (como no módulo)
                        # Fallback robusto: infere step a partir dos dados quando variável de interface não estiver confiável
                        inferred_step = None
                        try:
                            sorted_unique_powers = sorted(set([float(p) for p in powers]))
                            if len(sorted_unique_powers) >= 2:
                                diffs = [round(b - a, 3) for a, b in zip(sorted_unique_powers, sorted_unique_powers[1:]) if b - a > 0]
                                if diffs:
                                    inferred_step = min(diffs)
                        except Exception:
                            inferred_step = None
                        
                        try:
                            power_step = float(self.attenuator_var.get())
                        except (ValueError, AttributeError):
                            power_step = inferred_step if inferred_step is not None else 1.0
                        
                        if abs(power_step - 0.5) < 0.26:  # trata 0.5 e arredondamentos próximos
                            power_step = 0.5
                            bar_width = 0.3
                        else:
                            power_step = 1.0
                            bar_width = 0.6
                        
                        # Configurações do eixo X PRIMEIRO (EXATAMENTE como no módulo _setup_plot_scales)
                        # Eixo X: de 5 dBm até a potência máxima configurada pelo usuário
                        try:
                            max_power = float(self.power_var.get())
                        except (ValueError, AttributeError):
                            max_power = 25  # Valor padrão
                        
                        min_power = 5  # Sempre começa em 5 dBm (como no módulo)
                        
                        # Adiciona espaços nas laterais para melhor visualização do gráfico de barras
                        x_margin = 2  # Espaço de 2 dBm em cada lado
                        x_min = max(0, min_power - x_margin)  # Não pode ser negativo
                        x_max = max_power + x_margin
                        
                        print(f"🔍 DEBUG RELATÓRIO: Eixo X configurado: {x_min} a {x_max} (baseado na configuração do usuário)")
                        
                        # Define escala do eixo X com margens (como no módulo)
                        ax1.set_xlim(x_min, x_max)
                        
                        # CORREÇÃO: Desenha UMA BARRA POR PONTO DE DADO, como na UI (_update_plot)
                        # Não agrupa por potência para não "perder" colunas por sobreposição/ordem
                        bar_count = 0
                        for power, tags in zip(powers, cumulative_tags):
                            try:
                                ax1.bar(power, tags, width=bar_width, alpha=0.8, color='lightblue')
                                print(f"   📊 Barra {bar_count+1}: x={power:.1f}, altura={tags}")
                                bar_count += 1
                            except Exception as e:
                                print(f"   ❌ Erro ao desenhar barra {bar_count+1}: {e}")
                                bar_count += 1
                                continue
                        
                        print(f"🔍 DEBUG RELATÓRIO: Total de barras desenhadas: {bar_count}")
                        print(f"🔍 DEBUG RELATÓRIO: Total de pontos de dados: {len(powers)}")
                        
                        # DEBUG: Verifica se as barras estão dentro do range do eixo X
                        if powers:
                            print(f"🔍 DEBUG RELATÓRIO: Range dos dados: {min(powers)} a {max(powers)}")
                            print(f"🔍 DEBUG RELATÓRIO: Range do eixo X: {x_min} a {x_max}")
                            print(f"🔍 DEBUG RELATÓRIO: Todas as barras estão dentro do range? {all(x_min <= p <= x_max for p in powers)}")
                        ax1.set_xlabel(t('population.graph_xlabel'), fontsize=12)
                        
                        # Configura graduações dos eixos (como no módulo)
                        # Eixo X: graduação principal de 5 em 5 dBm + subdivisões baseadas no step
                        # Usa o mesmo power_step calculado/Inferido acima
                        
                        # Cria lista de ticks principais (5 em 5 dBm)
                        main_ticks = list(range(int(min_power), int(max_power) + 1, 5))
                        
                        # Cria lista de ticks secundários baseados no step de potência
                        minor_ticks = []
                        current_power = min_power
                        while current_power <= max_power:
                            # Adiciona tick se não for um tick principal
                            if current_power not in main_ticks:
                                minor_ticks.append(current_power)
                            current_power += power_step
                        
                        # Configura ticks principais e secundários (como no módulo)
                        ax1.set_xticks(main_ticks)  # Ticks principais
                        ax1.set_xticks(minor_ticks, minor=True)  # Ticks secundários
                        
                        # Configura estilo dos ticks - subdivisões menores (como no módulo)
                        ax1.tick_params(axis='x', which='major', length=8, width=1.5)  # Ticks principais maiores
                        ax1.tick_params(axis='x', which='minor', length=4, width=0.8)  # Ticks secundários menores
                        
                        # Habilita exibição dos valores das subdivisões (como no módulo)
                        ax1.tick_params(axis='x', which='minor', labelsize=8)  # Tamanho menor para subdivisões
                        ax1.tick_params(axis='x', which='minor', labelcolor='gray')  # Cor cinza para subdivisões
                        
                        # Força a exibição dos labels das subdivisões (como no módulo)
                        ax1.tick_params(axis='x', which='minor', labelbottom=True)
                        
                        # Adiciona locator para as subdivisões (como no módulo)
                        from matplotlib.ticker import MultipleLocator
                        ax1.xaxis.set_minor_locator(MultipleLocator(power_step))
                        
                        # Configurações do eixo Y (simplificado para evitar erros)
                        max_tags = max(cumulative_tags) if cumulative_tags else 0
                        max_tags = max(max_tags, 1)  # Mínimo de 1 para visualização
                        
                        ax1.set_ylim(0, max_tags + 1)  # Adiciona margem no topo
                        ax1.set_ylabel(t('population.graph_ylabel'), fontsize=12)
                        
                        print(f"🔍 DEBUG RELATÓRIO: Eixo Y configurado: 0 a {max_tags + 1} (baseado nos dados)")
                        
                        ax1.set_title(t('population.graph_title'), fontsize=12)
                        ax1.grid(True, alpha=0.3)
                        
                        # Gráfico 3D - Posicionamento das Tags (EXATAMENTE como no módulo)
                        from mpl_toolkits.mplot3d import Axes3D
                        ax2.remove()  # Remove o subplot 2D
                        ax2_3d = fig.add_subplot(122, projection='3d')
                        
                        # Obtém tags selecionadas (como no módulo)
                        selected_tags = []
                        try:
                            with open(self.SELECTED_TAGS_FILE, 'r', encoding='utf-8') as f:
                                data = json.load(f)
                                selected_tags = data.get('selected_tags', [])
                        except:
                            selected_tags = list(getattr(self, 'selected_tags', set()))
                        
                        # Obtém tags registradas com coordenadas (como no módulo)
                        x_coords = []
                        y_coords = []
                        z_coords = []
                        tag_labels = []
                        
                        if hasattr(self, 'database') and self.database:
                            all_tags = self.database.get_tags()
                            registered_tags = [tag for tag in all_tags if tag.get('epc', '') in selected_tags]
                            
                            for tag_data in registered_tags:
                                epc = tag_data.get('epc', '')
                                coordinates = tag_data.get('coordinates', '')
                                
                                if not coordinates or not coordinates.strip():
                                    continue
                                
                                try:
                                    # Parse das coordenadas (formato: "X,Y,Z" como no módulo)
                                    coords_parts = coordinates.strip().split(',')
                                    if len(coords_parts) != 3:
                                        continue
                                    
                                    x = float(coords_parts[0].strip())
                                    y = float(coords_parts[1].strip())
                                    z = float(coords_parts[2].strip())
                                    
                                    x_coords.append(x)
                                    y_coords.append(y)
                                    z_coords.append(z)
                                    
                                    # Obtém apelido da tag (como no módulo)
                                    apelido = tag_data.get('apelido', '')
                                    if not apelido or not apelido.strip():
                                        apelido = self.get_tag_apelido(epc)
                                    
                                    if apelido:
                                        tag_labels.append(apelido)
                                    else:
                                        tag_labels.append(f"T{epc[-3:]}")
                                        
                                except:
                                    continue
                        
                        if x_coords:
                            ax2_3d.scatter(x_coords, y_coords, z_coords, c='green', s=100)
                            ax2_3d.set_xlabel('X', fontsize=10)
                            ax2_3d.set_ylabel('Y', fontsize=10)
                            ax2_3d.set_zlabel('Z', fontsize=10)
                            ax2_3d.set_title(t('population.graph_3d_title'), fontsize=12)
                            
                            # Adiciona labels das tags (como no módulo)
                            for i, label in enumerate(tag_labels):
                                ax2_3d.text(x_coords[i], y_coords[i], z_coords[i], label, 
                                          fontsize=8, ha='center', va='bottom',
                                          bbox=dict(boxstyle='round,pad=0.2', facecolor='white', alpha=0.8))
                        else:
                            ax2_3d.text(0, 0, 0, "Nenhuma tag selecionada", 
                                       fontsize=12, ha='center', va='center')
                            ax2_3d.set_xlim(-5, 5)
                            ax2_3d.set_ylim(-5, 5)
                            ax2_3d.set_zlim(-5, 5)
                            ax2_3d.set_xlabel('X', fontsize=10)
                            ax2_3d.set_ylabel('Y', fontsize=10)
                            ax2_3d.set_zlabel('Z', fontsize=10)
                            ax2_3d.set_title(t('population.graph_3d_title'), fontsize=12)
                        
                        plt.tight_layout()
                        
                        # Salva o gráfico em buffer
                        img_buffer = io.BytesIO()
                        plt.savefig(img_buffer, format='png', dpi=150, bbox_inches='tight')
                        img_buffer.seek(0)
                        
                        # Adiciona ao PDF
                        chart_img = Image(img_buffer, width=6*inch, height=3*inch)
                        pagination.add_element(chart_img, get_height_estimate('chart_medium'))
                        
                        plt.close(fig)
                        print("✅ Gráficos adicionados ao PDF")
                    else:
                        pass  # Não incluir mensagem de "dados insuficientes" no relatório
                        
                except Exception as e:
                    print(f"⚠️ Erro ao gerar gráficos: {e}")
                    import traceback
                    traceback.print_exc()
                    pagination.add_element(Paragraph(f"Erro ao gerar gráficos: {str(e)}", styles['Normal']), get_height_estimate('paragraph'))
            else:
                pass  # Não incluir mensagem de "dados insuficientes" no relatório
            
            # Rodapé do relatório com paginação inteligente
            pagination.add_with_break_if_needed(Paragraph(t('pdf.additional_info'), heading_style), get_height_estimate('heading'), force_break=True)
            pagination.add_spacer(0.3*inch)
            
            footer_text = t('pdf.footer_text')
            pagination.add_element(Paragraph(footer_text, styles['Normal']), get_height_estimate('paragraph'))
            
            # Formata data/hora conforme idioma
            translator = get_translator()
            current_lang = translator.get_language()
            if current_lang == 'en':
                timestamp = datetime.now().strftime("%m/%d/%y at %I:%M:%S %p")
            else:
                timestamp = datetime.now().strftime("%d/%m/%Y às %H:%M:%S")
            pagination.add_element(Paragraph(f"<b>{t('pdf.document_generated_at')}</b> {timestamp}", styles['Normal']), get_height_estimate('paragraph'))
            
            # Gera o PDF com paginação inteligente
            print("🔄 Iniciando geração do PDF com paginação inteligente...")
            story = pagination.get_story()
            doc.build(story)
            print("✅ PDF gerado com sucesso!")
            
            return {'success': True}
            
        except Exception as e:
            print(f"❌ Erro ao gerar PDF: {str(e)}")
            import traceback
            traceback.print_exc()
            return {'success': False, 'error': str(e)}

    def on_closing(self):
        """Chamado quando o módulo é fechado"""
        try:
            if self.test_in_progress:
                print("🛑 Fechando módulo com teste em andamento...")
                self.global_test_cancelled = True
                
                # CORREÇÃO: Usa port_manager centralizado para fechar comunicação COM
                try:
                    if hasattr(self, 'port_manager') and self.port_manager:
                        self.port_manager.close_port(self.module_name)
                        print("✅ Population: Comunicação COM fechada via port_manager no fechamento")
                    elif HARDWARE_AVAILABLE and rfid_sdk:
                        # Fallback: usa método direto se port_manager não estiver disponível
                        with self.com_port_lock:
                            try:
                                rfid_sdk.UHF_RFID_Close(self.com_port)
                                print("✅ Population: Comunicação COM fechada no fechamento (fallback)")
                            except Exception as e:
                                print(f"⚠️ Population: Erro ao fechar COM no fechamento: {e}")
                except Exception as e:
                    print(f"⚠️ Population: Erro geral ao fechar comunicação no fechamento: {e}")
                
                if self.active_test_thread and self.active_test_thread.is_alive():
                    self.active_test_thread.join(timeout=2)
                    if self.active_test_thread.is_alive():
                        print("⚠️ Population: Thread ainda ativa no fechamento - forçando")
                        self.active_test_thread.daemon = True
            
            # Para o auto-save
            if self.auto_save_timer:
                self.after_cancel(self.auto_save_timer)
            
            # Salva estado completo da interface
            self._save_complete_ui_state()
            
            self._save_state()
            self._save_persisted_data()
            
            print("💾 Estado completo salvo no fechamento do módulo")
            
        except Exception as e:
            print(f"❌ Erro ao fechar módulo Population: {e}")

    def _show_risk_image_enlarged(self, event=None):
        """Mostra a imagem de Risco ampliada em uma janela popup"""
        try:
            # Carrega a imagem do módulo embedded_images baseada no idioma atual
            translator = get_translator()
            current_lang = translator.get_language()
            img = get_risk_image(current_lang)
            
            if img is None:
                messagebox.showwarning(t('population.warning_risk_image_not_found'), t('population.warning_risk_image_not_found_msg'), parent=self)
                print(f"❌ Imagem de risco não encontrada")
                return
            
            # Cria janela popup
            popup = tk.Toplevel(self)
            popup.title(t('population.risk_image'))
            popup.transient(self)
            popup.grab_set()
            
            # Usa a imagem carregada do módulo embedded_images
            
            # Redimensiona para um tamanho adequado (500 pixels de largura)
            original_width, original_height = img.size
            new_width = 500
            new_height = int((original_height / original_width) * new_width)
            
            img_enlarged = img.resize((new_width, new_height), Image.LANCZOS)
            
            # Converte para PhotoImage
            photo = ImageTk.PhotoImage(img_enlarged)
            
            # Cria label para exibir a imagem
            label = tk.Label(popup, image=photo)
            label.image = photo  # Mantém referência
            label.pack(padx=10, pady=10)
            
            # Botão para fechar
            close_btn = ttk.Button(popup, text="Fechar", command=popup.destroy)
            close_btn.pack(pady=(0, 10))
            
            # Centraliza a janela na tela
            popup.update_idletasks()
            width = popup.winfo_width()
            height = popup.winfo_height()
            x = (popup.winfo_screenwidth() // 2) - (width // 2)
            y = (popup.winfo_screenheight() // 2) - (height // 2)
            popup.geometry(f'{width}x{height}+{x}+{y}')
            
            print("✅ Imagem de Risco exibida no módulo População")
            
        except Exception as e:
            messagebox.showerror(t('population.error'), t('population.error_display_risk_image').format(error=str(e)), parent=self)
            print(f"❌ Erro ao exibir imagem de Risco: {e}")


if __name__ == '__main__':
    root = tk.Tk()
    root.title("Módulo Population (Desenvolvimento)")
    root.geometry("1200x900")
    root.minsize(1000, 800)  # Tamanho mínimo da janela
    root.resizable(True, True)  # Permite redimensionamento
    app = PopulacaoModule(root)
    app.pack(fill="both", expand=True)
    
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()