"""
Módulo Threshold - FastChecker II (Versão com Gerenciamento de Tags e Projeto)
"""
import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog
import os
import sys
import queue
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 src.core.port_manager import get_port_manager
from typing import Optional

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

from .threshold_database import ThresholdDatabase
from .register_tags_popup import RegisterTagsPopup
from .rename_popup import RenamePopup
from .delete_confirmation_popup import DeleteConfirmationPopup
from .export_popup import ExportPopup
from .correction_table import (
    get_correction_table,
    apply_corrections_to_result,
    max_link_forward_eirp_dbm_for_license,
)
from .state_persistence import StatePersistence
from .i18n import get_translator as _get_translator, t
from .app_console import fc_log, fc_log_exc
from .shell_open import open_path_with_default_app

# Importa sistema de backup automático
try:
    from tests.auto_backup import AutoBackupSystem
    BACKUP_AVAILABLE = True
except ImportError:
    BACKUP_AVAILABLE = False


# --- Bloco de Controle de Hardware ---
DLL_NAME = "UHFRFID.dll"
try:
    # CORREÇÃO: Caminho robusto para executável e desenvolvimento
    if hasattr(sys, '_MEIPASS'):
        # Modo executável (PyInstaller)
        dll_path = os.path.join(sys._MEIPASS, DLL_NAME)
    else:
        # Modo desenvolvimento
        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
    # CORREÇÃO: Status contextual baseado na licença
    fc_log("threshold", "Sistema RFID inicializado")
except (OSError, AttributeError) as e:
    rfid_sdk = None
    HARDWARE_AVAILABLE = False
    fc_log_exc("threshold", "Hardware RFID não disponível", e)

# Porta COM agora é detectada dinamicamente
BAUD_RATE = 115200
RFID_CMD_SET_TXPOWER = 0x10
RFID_CMD_INV_TAG = 0x80
RFID_CMD_SET_FREQ_TABLE = 0x14
RFID_CMD_SET_CW_STATUS = 0x24
RFID_CMD_GET_RSSIVALU = 0x64
RFID_CMD_GET_TEMPERATURE = 0x34
RFID_CMD_GET_PORT_LOSS = 0x32
RFID_CMD_STOP_INVENTORY = 0x8C
RFID_CMD_SET_SOFTRESET = 0x68  # Comando de Software Reset para bip

# CORREÇÃO: Adiciona tabela RxAdcTable como no FastSurance (funcionando)
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
]

# CORREÇÃO: Constantes de segurança alinhadas ao Fast Threshold (que funciona)
HEATING_POWER_DBM = 25
REFLECTED_POWER_LIMIT = 10.0   # dBm - Potência refletida máxima aceitável
TEMPERATURE_LIMIT = 50.0       # °C - Temperatura máxima (bloqueia teste acima)


def _normalize_threshold_freq_step_str(freq_step: str) -> str:
    """Passo 0,5 MHz foi removido da UI; perfis/estado antigos migram para 1 MHz."""
    if not freq_step or not str(freq_step).strip():
        return freq_step or ""
    raw = str(freq_step).strip().replace(",", ".")
    try:
        if abs(float(raw) - 0.5) < 1e-9:
            return "1"
    except ValueError:
        pass
    return str(freq_step).strip()


def _normalize_threshold_power_step_str(power_step: str) -> str:
    """Garante valor válido para o combobox Threshold: 0.5 ou 1.0 dBm."""
    if not power_step or not str(power_step).strip():
        return "0.5"
    raw = str(power_step).strip().replace(",", ".")
    if raw in ("0.5", "1.0"):
        return raw
    if raw == "1":
        return "1.0"
    try:
        v = float(raw)
        if abs(v - 0.5) < 1e-9:
            return "0.5"
        if abs(v - 1.0) < 1e-9:
            return "1.0"
    except ValueError:
        pass
    return "0.5"

# --- NOVAS CONSTANTES PARA ESTABILIZAÇÃO RSSI ---
RSSI_STABILITY_THRESHOLD = 0.5      # dBm - variação máxima aceitável para estabilidade
RSSI_STABILITY_READINGS = 3         # Número de leituras para verificar estabilidade
RSSI_OUTLIER_THRESHOLD = 2.0       # dBm - limite para considerar outlier
RSSI_MAX_ATTEMPTS = 2              # OTIMIZAÇÃO #4: Máximo de tentativas reduzido de 5 para 3
RSSI_STABILITY_DELAY = 0.025        # Delay entre leituras para estabilização (50ms)

# --- NOVAS CONSTANTES PARA DELAYS ADAPTATIVOS ---
ADAPTIVE_DELAY_MIN = 0.008          # Delay mínimo (8ms) - OTIMIZADO!






class TestCancelledException(Exception):
    """Exceção personalizada para cancelamento de teste"""
    pass

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


# ========== FUNÇÃO AUXILIAR PARA PLOTAGEM EM TEMPO REAL ==========
def create_continuous_segments_threshold(x_values, y_values, license_limits):
    """
    Cria segmentos contínuos de pontos, separando onde há gaps de frequência ou faixas excluídas
    Baseado na lógica do Antenna Check
    """
    if not x_values or not y_values:
        return []
    
    # Ordena os pontos por frequência
    sorted_points = sorted(zip(x_values, y_values))
    x_sorted = [point[0] for point in sorted_points]
    y_sorted = [point[1] for point in sorted_points]
    
    segments = []
    current_segment_x = [x_sorted[0]]
    current_segment_y = [y_sorted[0]]
    
    # Verifica se há faixas excluídas para determinar gaps
    excluded_ranges = license_limits.get('excluded_ranges', [])
    
    for i in range(1, len(x_sorted)):
        prev_freq = x_sorted[i-1]
        curr_freq = x_sorted[i]
        
        # Verifica se a frequência atual está em uma faixa excluída
        in_excluded = False
        for excl_min, excl_max in excluded_ranges:
            if excl_min <= curr_freq <= excl_max:
                in_excluded = True
                fc_log("threshold", f"create_continuous_segments_threshold: Frequência {curr_freq} MHz está na faixa excluída {excl_min}-{excl_max} MHz")
                break
        
        # Se há gap significativo OU frequência está excluída, cria novo segmento
        # Gap significativo = diferença maior que 5 MHz (para permitir curvas mais contínuas)
        if (curr_freq - prev_freq) > 5.0 or in_excluded:
            # Salva o segmento atual
            if len(current_segment_x) > 0:
                segments.append((current_segment_x.copy(), current_segment_y.copy()))
                fc_log("threshold", f"create_continuous_segments_threshold: Segmento finalizado com {len(current_segment_x)} pontos, faixa {min(current_segment_x):.1f}-{max(current_segment_x):.1f} MHz")
            
            # Inicia novo segmento (apenas se não estiver excluído)
            if not in_excluded:
                current_segment_x = [curr_freq]
                current_segment_y = [y_sorted[i]]
            else:
                # Pula pontos excluídos
                current_segment_x = []
                current_segment_y = []
        else:
            # Adiciona ao segmento atual
            current_segment_x.append(curr_freq)
            current_segment_y.append(y_sorted[i])
    
    # Adiciona o último segmento
    if len(current_segment_x) > 0:
        segments.append((current_segment_x, current_segment_y))
        fc_log("threshold", f"create_continuous_segments_threshold: Último segmento adicionado com {len(current_segment_x)} pontos, faixa {min(current_segment_x):.1f}-{max(current_segment_x):.1f} MHz")
    
    fc_log("threshold", f"create_continuous_segments_threshold: Criados {len(segments)} segmentos contínuos")
    for i, (x_seg, y_seg) in enumerate(segments):
        fc_log("threshold", f"   Segmento {i+1}: {len(x_seg)} pontos, faixa {min(x_seg):.1f}-{max(x_seg):.1f} MHz")
    
    return segments

def plot_realtime_segments_connected(ax, frequencies, values, color, license_limits):
    """Plota segmentos conectados em tempo real sem conectar através de faixas excluídas"""
    try:
        if not frequencies or not values or len(frequencies) != len(values):
            fc_log("threshold", "plot_realtime_segments_connected: Dados inválidos")
            return
        
        # CORREÇÃO: Limita o número de pontos para evitar lentidão
        MAX_PLOT_POINTS = 500
        if len(frequencies) > MAX_PLOT_POINTS:
            # Mantém apenas os pontos mais recentes
            frequencies = frequencies[-MAX_PLOT_POINTS:]
            values = values[-MAX_PLOT_POINTS:]
            fc_log("threshold", f"Limpeza de plotting: Mantendo apenas os últimos {MAX_PLOT_POINTS} pontos para performance")
        
        fc_log("threshold", f"plot_realtime_segments_connected: Plotando {len(frequencies)} pontos em segmentos")
        
        # Remove linhas anteriores do teste atual
        for line in ax.lines[:]:
            if hasattr(line, '_realtime_test') and line._realtime_test:
                line.remove()
        
        # Usa a mesma lógica do Antenna Check: cria segmentos contínuos
        segments = create_continuous_segments_threshold(frequencies, values, license_limits)
        
        fc_log("threshold", f"plot_realtime_segments_connected: Criados {len(segments)} segmentos")
        
        # Plota cada segmento separadamente com linhas conectadas
        for i, (seg_freqs, seg_values) in enumerate(segments):
            fc_log("threshold", f"plot_realtime_segments_connected: Plotando segmento {i+1} com {len(seg_freqs)} pontos")
            if len(seg_freqs) > 1:
                # Segmento com múltiplos pontos - plota linha conectada com marcadores
                # Sempre mostra marcadores para dados em tempo real
                line, = ax.plot(seg_freqs, seg_values, color=color,
                              marker='o', linestyle='-', linewidth=1.2, markersize=4, alpha=0.8)
                
                line._realtime_test = True
                step_avg = (max(seg_freqs) - min(seg_freqs)) / (len(seg_freqs) - 1) if len(seg_freqs) > 1 else 1.0
                fc_log("threshold", f"plot_realtime_segments_connected: Linha conectada plotada para segmento {i+1} (step: {step_avg:.2f} MHz)")
            elif len(seg_freqs) == 1:
                # Segmento com apenas um ponto - plota apenas marcador
                line, = ax.plot(seg_freqs, seg_values, color=color,
                              marker='o',
                              linestyle='', linewidth=0.8, markersize=2, alpha=0.8)
                line._realtime_test = True
                fc_log("threshold", f"plot_realtime_segments_connected: Ponto único plotado para segmento {i+1}")
                
    except Exception as e:
        fc_log_exc("threshold", "Erro ao plotar segmentos conectados em tempo real", e)
        import traceback
        traceback.print_exc()

class ThresholdModule(tk.Frame):
    def __init__(self, parent, app_shell=None, com_port=4):
        super().__init__(parent)
        self.parent = parent
        self.app_shell = app_shell
        self.com_port = com_port  ### NOVO: Armazena a porta COM da instância
        self.com_port_lock = threading.Lock()
        self.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
            fc_log("threshold", "THRESHOLD: Port_manager inicializado via app_shell no __init__")
        else:
            self.port_manager = None
            fc_log("threshold", "THRESHOLD: Port_manager não disponível no __init__, aguardando...")
        
        # --- INTEGRAÇÃO COM FASTCHECKER ---
        self.license_limits = self._get_license_limits()
        fc_log("threshold", f"__init__ - license_limits iniciais: {self.license_limits}")
        self.module_name = "Threshold"  # 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
        
        # --- Organização das Variáveis da Interface ---
        # Ação padrão será definida depois que o dropdown for criado
        self.action_var = tk.StringVar(value=t('threshold.select_an_action'))
        
        # --- Variável de Cancelamento Global ---
        self.global_test_cancelled = False
        
        # --- Thread de Teste Ativa ---
        self.active_test_thread = None
        
        # --- Teste Selecionado para Visualização de Tabela ---
        self.selected_test_for_table = None
        
        self.min_freq_var = tk.StringVar(value="")
        self.max_freq_var = tk.StringVar(value="")
        self.max_power_var = tk.StringVar(value="")
        self.freq_step_var = tk.StringVar(value="")
        self.power_step_var = tk.StringVar(value="0.5")
        self.distance_var = tk.StringVar(value="") 
        self.attenuator_var = tk.StringVar(value="")
        self.description_input_var = tk.StringVar(value="")

        # Projeto (nome/cliente): deve existir antes de _load_previous_state e ser persistido no JSON
        self.project_name_var = tk.StringVar(value="")
        self.client_name_var = tk.StringVar(value="")
        self._suppress_project_field_trace = False

        self.graph1_var = tk.StringVar(value=t('threshold.graph_type_module_power'))
        self.graph2_var = tk.StringVar(value=t('threshold.graph_type_module_power'))
        
        # --- Carrega Estado Anterior ---
        self._load_previous_state()

        # Atualiza indicação inicial de calibração (se houver perfil ativo)
        try:
            self._update_calibration_status_label()
        except Exception:
            # Indicador de calibração é apenas informativo; não deve quebrar o módulo
            pass

        # Inicia atualização periódica do status de calibração (casos em que é ativada/desativada em outro módulo)
        self._calibration_status_job = None
        self._schedule_calibration_status_refresh()
        
        # Dados dos gráficos
        self.graph_data = {
            'frequencies': [],
            'module_power': [],
            'module_rssi': [],
            'irradiated_power': [],
            'backscatter': [],
            'power_tag_forward': [],
            'power_tag_reverse': [],
            'link_forward': [],
            'link_reverse': []
        }
        
        # CARREGA DADOS DO BANCO PARA SINCRONIZAÇÃO
        fc_log("threshold", "Carregando dados do banco para sincronização...")
        self.test_history = self.database.get_test_history()
        self.test_counter = len(self.test_history)  # Sincroniza o contador
        
        # Verifica integridade dos dados carregados
        fc_log("threshold", f"Carregados {len(self.test_history)} testes do banco de dados")
        for test in self.test_history:
            if 'id' not in test:
                fc_log("threshold", f"Teste sem ID encontrado: {test}")
            else:
                fc_log("threshold", f"Teste {test['id']}: {test.get('description', 'Sem descrição')} - show_in_graphs: {test.get('show_in_graphs', False)}")
        
        # Lock para acesso à porta COM
        self.com_port_lock = threading.Lock()
        # Dados de plot em tempo real: o teste corre numa thread; Tk/Matplotlib só no thread principal.
        # Não usar widget.after() a partir da thread do teste — usar fila + poll no main thread.
        self._realtime_plot_lock = threading.Lock()
        self._realtime_ui_queue = queue.SimpleQueue()
        
        # Controle de tags selecionadas
        self.selected_tags = set()
        self.tags_data = []
        
        # Figuras dos gráficos
        self.fig1 = None
        self.fig2 = None
        
        # Variáveis de zoom para ambos os gráficos
        self.zoom_factor_graph1 = 1.0
        self.zoom_factor_graph2 = 1.0
        self.original_xlim_graph1 = None
        self.original_ylim_graph1 = None
        self.original_xlim_graph2 = None
        self.original_ylim_graph2 = None
        self.canvas1 = None
        self.canvas2 = None
        self._graph1_motion_cid = None
        self._graph2_motion_cid = None
        self._graph_refresh_after_id = None
        self._last_hover_graph1 = 0.0
        self._last_hover_graph2 = 0.0
        
        # --- DADOS EM TEMPO REAL (SIMPLIFICADO) ---
        self.current_test_description = ""
        self.is_test_running = False
        self._verification_result_queue = queue.Queue()

        # Inicializa sistema de backup automático
        if BACKUP_AVAILABLE:
            try:
                self.backup_system = AutoBackupSystem()
                self.backup_system.start_monitoring()
                fc_log("threshold", "Sistema de backup automático iniciado")
            except Exception as e:
                fc_log_exc("threshold", "Erro ao iniciar backup automático", e)
                self.backup_system = None
        else:
            self.backup_system = None
            fc_log("threshold", "Sistema de backup automático não disponível")
        
        # Inicializa sistema de segurança
        try:
            self._initialize_safety_system()
        except AttributeError:
            # Método não implementado ainda, inicializa valores padrão
            self.safety_monitoring = False
            self.safety_thread = None
            self.blinking_job_id = None
            self.current_sort_column = None
            self.current_sort_reverse = False
            fc_log("threshold", "Sistema de segurança não implementado - usando valores padrão")
        
        # Inicializa hardware com melhor tratamento de erros (CORRIGIDO - sem dupla inicialização)
        if HARDWARE_AVAILABLE:
            try:
                # Testa conexão com hardware (CORRIGIDO - apenas teste, sem manter aberto)
                result = rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE)
                if result == 0:
                    # CORREÇÃO: Status contextual baseado na licença
                    is_licensed = self.license_limits.get('is_licensed', False)
                    if is_licensed:
                        fc_log("threshold", f"Hardware conectado: COM{self.com_port}, {BAUD_RATE} baud")
                    else:
                        fc_log("threshold", f"Modo browser - hardware disponível em COM{self.com_port}")
                    # [OK] CORRIGIDO: Sempre fecha conexão de teste
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    fc_log("threshold", "Conexão de teste fechada - hardware em modo seguro")
                else:
                    fc_log("threshold", f"Erro ao conectar hardware: {result}")
                    fc_log("threshold", "Verifique se o hardware está conectado e a porta COM está correta")
                    # CORREÇÃO: Não exibe popup de hardware quando sem licença (modo browser)
                    is_licensed = self.license_limits.get('is_licensed', False)
                    if is_licensed:
                        messagebox.showwarning(t('threshold.warning_hardware'), t('threshold.error_hardware_connection').format(port=self.com_port))
            except Exception as e:
                fc_log_exc("threshold", "Erro na inicialização do hardware", e)
                # CORREÇÃO: Não exibe popup de erro de hardware quando sem licença (modo browser)
                is_licensed = self.license_limits.get('is_licensed', False)
                if is_licensed:
                    messagebox.showerror(t('threshold.error_hardware'), t('threshold.error_hardware_init').format(error=str(e)))
        else:
            fc_log("threshold", f"DLL '{DLL_NAME}' não encontrada - usando modo simulação")
            fc_log("threshold", f"'{DLL_NAME}' não encontrada. Usando modo simulação para testes.")
        
        
        if not MATPLOTLIB_OK:
            messagebox.showwarning(t('threshold.warning_matplotlib'), t('threshold.warning_matplotlib_msg'))
        
        # Configura a interface primeiro
        self.setup_ui()
        self.update_tags_list()
        
        # Atualiza idioma após criar a interface
        self.after(100, self.refresh_language)
        
        # Interface configurada com sucesso
        # CORREÇÃO: Status contextual baseado na licença
        is_licensed = self.license_limits.get('is_licensed', False)
        if is_licensed:
            fc_log("threshold", "Interface configurada com sucesso")
        else:
            fc_log("threshold", "Interface configurada (modo browser)")
        
        # --- APLICA LIMITES DE LICENÇA ---
        self._apply_license_limits()
        
        # Atualiza a tabela do histórico com dados do banco
        fc_log("threshold", "Atualizando tabela de histórico...")
        self.update_history_tree()
        
        # FORÇA SINCRONIZAÇÃO COMPLETA ENTRE BANCO E INTERFACE
        fc_log("threshold", "Forçando sincronização entre banco e interface...")
        self._force_sync_database_interface()
        
        # Inicializa os gráficos DEPOIS da interface estar pronta
        fc_log("threshold", "Inicializando gráficos...")
        self.initialize_graphs()
        
        # Inicia auto-save do estado
        self.state_persistence.start_auto_save(60)  # Salva a cada 60 segundos
        
        # Atualiza os gráficos com base nos testes marcados no histórico
        fc_log("threshold", "Atualizando gráficos com dados do histórico...")
        self.update_graphs_from_history()
        
        # CORREÇÃO: Status contextual baseado na licença
        is_licensed = self.license_limits.get('is_licensed', False)
        if is_licensed:
            fc_log("threshold", "Inicialização completa - sistema pronto para uso")
        else:
            fc_log("threshold", "Inicialização completa - modo browser ativo")
        
        # CORREÇÃO: Atualiza o label de limites de frequência após inicialização completa
        self._update_frequency_limits_label()
        
        # REQUISITOS: Captura porta + Reset ao entrar
        self._initialize_hardware_on_enter()
        
        # Inicializa VSWR com valor padrão (apenas se hardware disponível)
        if HARDWARE_AVAILABLE:
            self._initialize_vswr_display()
        else:
            # Se hardware não disponível, mostra "--" como temperatura
            self._update_vswr_display("--")

    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:
            fc_log("threshold", "Inicializando hardware...")
            pm = getattr(self, 'port_manager', None) or get_port_manager()
            if pm.acquire_port("Threshold", timeout=2.0):
                try:
                    if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) == 0:
                        fc_log("threshold", f"Porta COM{self.com_port} capturada")
                        
                        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))
                        
                        rfid_sdk.UHF_RFID_Close(self.com_port)
                        fc_log("threshold", "Hardware inicializado (sem bip)")
                finally:
                    pm.release_port("Threshold")
            else:
                fc_log("threshold", "Falha ao capturar porta")
                
        except Exception as e:
            fc_log_exc("threshold", "Threshold: erro ao inicializar hardware ao entrar", e)
    
    def _initialize_vswr_display(self):
        """Inicializa o display do VSWR com valor padrão"""
        try:
            fc_log("threshold", "VSWR: Inicializando display...")
            
            # Inicializa com "--" como a temperatura (não mede automaticamente)
            self._update_vswr_display("--")
            fc_log("threshold", "VSWR: Display inicializado com '--' (medição apenas durante testes)")
                
        except Exception as e:
            fc_log_exc("threshold", "VSWR: Erro na inicialização", e)
            self._update_vswr_display("--")  # Fallback para "--"
    
    def _on_language_changed(self, old_language=None, new_language=None):
        """Callback quando o idioma é alterado"""
        try:
            self.refresh_language()
        except Exception as e:
            fc_log_exc("threshold", "Erro ao atualizar idioma no Threshold", e)

        # Após mudança de idioma, garante que o texto da calibração também seja atualizado
        try:
            if hasattr(self, '_schedule_calibration_status_refresh'):
                self._schedule_calibration_status_refresh()
        except Exception:
            pass
    
    def refresh_language(self):
        """Atualiza todos os textos da interface quando o idioma é alterado"""
        try:
            # Atualiza títulos de frames
            if 'safety_frame' in self._widget_refs:
                self._widget_refs['safety_frame'].config(text=t('threshold.safety_system'))
            if 'tags_frame' in self._widget_refs:
                self._widget_refs['tags_frame'].config(text=t('threshold.registered_tags'))
            if 'project_frame' in self._widget_refs:
                self._widget_refs['project_frame'].config(text=t('threshold.project'))
            if 'params_frame' in self._widget_refs:
                self._widget_refs['params_frame'].config(text=t('threshold.test_config'))
            if 'data_frame' in self._widget_refs:
                self._widget_refs['data_frame'].config(text=t('threshold.test_history'))
            
            # Atualiza labels
            if 'temperature_label_text' in self._widget_refs:
                self._widget_refs['temperature_label_text'].config(text=t('threshold.temperature'))
            if 'vswr_label_text' in self._widget_refs:
                self._widget_refs['vswr_label_text'].config(text=t('threshold.vswr'))
            if 'browser_mode_label' in self._widget_refs:
                self._widget_refs['browser_mode_label'].config(text=t('threshold.browser_mode'))
            if 'project_name_label' in self._widget_refs:
                self._widget_refs['project_name_label'].config(text=t('threshold.project_name'))
            if 'client_label' in self._widget_refs:
                self._widget_refs['client_label'].config(text=t('threshold.client'))
            if 'freq_label' in self._widget_refs:
                self._widget_refs['freq_label'].config(text=t('threshold.freq_mhz'))
            if 'to_label' in self._widget_refs:
                self._widget_refs['to_label'].config(text=t('threshold.to'))
            if 'power_label' in self._widget_refs:
                self._widget_refs['power_label'].config(text=t('threshold.power_dbm'))
            if 'freq_step_label' in self._widget_refs:
                self._widget_refs['freq_step_label'].config(text=t('threshold.freq_step'))
            if 'power_step_label' in self._widget_refs:
                self._widget_refs['power_step_label'].config(text=t('threshold.power_step'))
            if 'distance_label' in self._widget_refs:
                self._widget_refs['distance_label'].config(text=t('threshold.distance'))
            if 'attenuator_label' in self._widget_refs:
                self._widget_refs['attenuator_label'].config(text=t('threshold.attenuator'))
            if 'description_label' in self._widget_refs:
                self._widget_refs['description_label'].config(text=t('threshold.description'))

            # Atualiza indicação de calibração (texto depende do idioma)
            if hasattr(self, 'calibration_status_label'):
                self._update_calibration_status_label()
            
            # Atualiza botões
            if 'save_project_button' in self._widget_refs:
                self._widget_refs['save_project_button'].config(text=t('threshold.save'))
            if 'delete_project_button' in self._widget_refs:
                self._widget_refs['delete_project_button'].config(text=t('threshold.clear'))
            if 'import_project_button' in self._widget_refs:
                self._widget_refs['import_project_button'].config(text=t('threshold.import'))
            if 'start_button' in self._widget_refs:
                if not getattr(self, 'test_in_progress', False):
                    self.start_button.config(text=t('threshold.start_test'), command=self._on_test_button_click)
            if 'show_table_button' in self._widget_refs:
                self.show_table_button.config(text=t('threshold.show_table'))
            if 'pdf_report_button' in self._widget_refs:
                self.pdf_report_button.config(text=t('threshold.pdf_report'))
            
            # Atualiza dropdown de ações
            if 'action_dropdown' in self._widget_refs:
                select_action_text = t('threshold.select_an_action')
                simplified_actions = [select_action_text, t('threshold.register_new_tags'), t('threshold.import_tag_json'), t('threshold.select_all_tags'), t('threshold.deselect_all_tags'), t('threshold.erase_selected_tags'), t('threshold.save_selected_tags')]
                self._simplified_actions_ref = simplified_actions
                current_selection = self.action_var.get()
                self.action_dropdown.config(values=simplified_actions)
                # Se a seleção atual é o texto padrão ou não está nas ações válidas, reseta para o padrão traduzido
                if current_selection == t('threshold.select_an_action') or current_selection == "":
                    self.action_var.set(t('threshold.select_an_action'))
                # Tenta restaurar a seleção se ainda existir nas ações válidas
                elif current_selection in simplified_actions or any(current_selection in action for action in simplified_actions):
                    try:
                        self.action_var.set(current_selection)
                    except:
                        self.action_var.set(t('threshold.select_an_action'))
                else:
                    # Se não for uma ação válida, reseta para o padrão
                    self.action_var.set(t('threshold.select_an_action'))
            
            # Atualiza cabeçalhos da tabela de histórico
            if hasattr(self, 'tree'):
                try:
                    self.tree.heading('Plot', text=f"{t('threshold.plot')} ↕")
                    self.tree.heading('Nome do Teste', text=f"{t('threshold.test_name')} ↕")
                    self.tree.heading('EPC', text=f"{t('threshold.epc')} ↕")
                    self.tree.heading('Calibration', text=f"{t('threshold.calibration_file_col')} ↕")
                    self.tree.heading('Atenuador', text=f"{t('threshold.attenuator_col')} ↕")
                    self.tree.heading('Distância', text=f"{t('threshold.distance_col')} ↕")
                    self.tree.heading('Date Time', text=f"{t('threshold.date_time')} ↕")
                    self.tree.heading('Tempo', text=f"{t('threshold.time')} ↕")
                except Exception as e:
                    fc_log_exc("threshold", "Erro ao atualizar cabeçalhos da tabela", e)
            
            # Atualiza contador de histórico
            self.update_history_counter()
            
            # Atualiza dropdowns de tipo de gráfico
            graph_type_values = self._get_graph_type_values()
            
            # Mapeia o valor atual para o equivalente traduzido
            def map_to_translated(value):
                key_map = self._get_graph_type_key_map()
                # Se o valor já está nas traduções atuais, retorna como está
                if value in graph_type_values:
                    return value
                # Tenta encontrar pelo mapa reverso (inglês -> traduzido)
                reverse_map = {v: k for k, v in key_map.items()}
                if value in reverse_map:
                    return reverse_map[value]
                # Se não encontrar, retorna o primeiro valor padrão
                return graph_type_values[0] if graph_type_values else value
            
            # Atualiza gráfico 1
            if 'graph1_combobox' in self._widget_refs and hasattr(self, 'graph1_var'):
                current_value = self.graph1_var.get()
                self._widget_refs['graph1_combobox'].config(values=graph_type_values)
                mapped_value = map_to_translated(current_value)
                self.graph1_var.set(mapped_value)
            
            # Atualiza gráfico 2
            if 'graph2_combobox' in self._widget_refs and hasattr(self, 'graph2_var'):
                current_value = self.graph2_var.get()
                self._widget_refs['graph2_combobox'].config(values=graph_type_values)
                mapped_value = map_to_translated(current_value)
                self.graph2_var.set(mapped_value)
            
            # Atualiza dropdown de ações da tabela
            if 'table_action_combobox' in self._widget_refs:
                table_action_values = [
                    t('threshold.table_select_all'),
                    t('threshold.table_deselect_all'),
                    t('threshold.table_statistical'),
                    t('threshold.table_delete_selected'),
                    t('threshold.table_export_excel')
                ]
                current_table_action = self.table_action_var.get()
                self._widget_refs['table_action_combobox'].config(values=table_action_values)
                # Se o valor atual não está nas novas opções, reseta para padrão
                if current_table_action not in table_action_values:
                    self.table_action_var.set(t('threshold.select_an_action'))
            
            # Atualiza labels dos eixos dos gráficos
            if hasattr(self, 'ax1') and self.ax1:
                self.ax1.set_xlabel('')
                self.ax1.set_ylabel('')  # Label do eixo Y removido
                if hasattr(self, 'canvas1') and self.canvas1:
                    self.canvas1.draw()
                    self.canvas1.flush_events()
            
            if hasattr(self, 'ax2') and self.ax2:
                self.ax2.set_xlabel('')
                self.ax2.set_ylabel('')  # Label do eixo Y removido
                if hasattr(self, 'canvas2') and self.canvas2:
                    self.canvas2.draw()
                    self.canvas2.flush_events()
            
            # Recarrega a lista de tags para traduzir o texto "(Nenhuma tag registrada)"
            if hasattr(self, 'tags_tree') and hasattr(self, 'update_tags_list'):
                self.update_tags_list()
        except Exception as e:
            fc_log_exc("threshold", "Erro ao atualizar idioma no Threshold", e)
            import traceback
            traceback.print_exc()
    
    def _get_graph_type_values(self):
        """Retorna a lista de valores traduzidos para os dropdowns de tipo de gráfico"""
        return [
            t('threshold.graph_type_module_power'),
            t('threshold.graph_type_module_rssi'),
            t('threshold.graph_type_irradiated_power'),
            t('threshold.graph_type_backscatter'),
            t('threshold.graph_type_power_tag_forward'),
            t('threshold.graph_type_power_tag_reversed'),
            t('threshold.graph_type_conversion_loss'),
            t('threshold.graph_type_max_fcc_link_forward'),
            t('threshold.graph_type_max_fcc_link_reversed'),
            t('threshold.graph_type_rcs'),
        ]
    
    def _get_graph_type_key_map(self):
        """Retorna um mapa entre valores traduzidos e chaves em inglês para compatibilidade"""
        return {
            t('threshold.graph_type_module_power'): "Module Power (dBm)",
            t('threshold.graph_type_module_rssi'): "Module RSSI (dBm)",
            t('threshold.graph_type_irradiated_power'): "Irradiated Power (dBm)",
            t('threshold.graph_type_backscatter'): "Backscatter (dBm)",
            t('threshold.graph_type_power_tag_forward'): "Power on Tag Forward (dBm)",
            t('threshold.graph_type_power_tag_reversed'): "Power on Tag Reversed (dBm)",
            t('threshold.graph_type_conversion_loss'): "Conversion Loss (dBm)",
            t('threshold.graph_type_max_fcc_link_forward'): "Max FCC Link Forward (m)",
            t('threshold.graph_type_max_fcc_link_reversed'): "Max FCC Link Reversed (m)",
            t('threshold.graph_type_rcs'): "RCS (dBm²)",
        }
    
    def _normalize_graph_type(self, graph_type):
        """Normaliza o tipo de gráfico para a chave em inglês (para compatibilidade com código existente)"""
        # Se já é uma chave em inglês, retorna como está
        english_keys = ["Module Power (dBm)", "Module RSSI (dBm)", "Irradiated Power (dBm)", 
                       "Backscatter (dBm)", "Power on Tag Forward (dBm)", "Power on Tag Reversed (dBm)",
                       "Conversion Loss (dBm)", "Max FCC Link Forward (m)", "Max FCC Link Reversed (m)", "RCS (dBm²)"]
        if graph_type in english_keys:
            return graph_type
        
        # Tenta encontrar no mapa de traduções
        key_map = self._get_graph_type_key_map()
        if graph_type in key_map:
            return key_map[graph_type]
        
        # Fallback: retorna como está
        return graph_type
    
    def _get_y_axis_label(self, graph_type):
        """Retorna o label traduzido para o eixo Y baseado no tipo de gráfico"""
        # Normaliza para chave em inglês primeiro
        graph_type_key = self._normalize_graph_type(graph_type)
        
        label_map = {
            "Module Power (dBm)": t('threshold.y_axis_module_power'),
            "Module RSSI (dBm)": t('threshold.y_axis_module_rssi'),
            "Irradiated Power (dBm)": t('threshold.y_axis_irradiated_power'),
            "Backscatter (dBm)": t('threshold.y_axis_backscatter'),
            "Power on Tag Forward (dBm)": t('threshold.y_axis_power_tag_forward'),
            "Power on Tag Reversed (dBm)": t('threshold.y_axis_power_tag_reversed'),
            "Conversion Loss (dBm)": t('threshold.y_axis_conversion_loss'),
            "Max FCC Link Forward (m)": t('threshold.y_axis_max_fcc_link_forward'),
            "Max FCC Link Reversed (m)": t('threshold.y_axis_max_fcc_link_reversed'),
            "RCS (dBm²)": t('threshold.y_axis_rcs'),
        }
        return label_map.get(graph_type_key, graph_type)
    
    def destroy(self):
        """REQUISITOS 2+3+4: Libera porta, para comandos, reseta ao sair"""
        try:
            fc_log("threshold", "Iniciando cleanup...")

            # Cancela atualização periódica de status de calibração, se existir
            try:
                if hasattr(self, '_calibration_status_job') and self._calibration_status_job:
                    self.after_cancel(self._calibration_status_job)
                    self._calibration_status_job = None
            except Exception:
                pass
            
            if hasattr(self, 'test_in_progress') and self.test_in_progress:
                self.global_test_cancelled = True
                self.test_in_progress = False
            
            if HARDWARE_AVAILABLE and rfid_sdk:
                try:
                    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)
                        rfid_sdk.UHF_RFID_Set(RFID_CMD_STOP_INVENTORY, None, 0, out_buf, ctypes.byref(out_len))
                    
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    fc_log("threshold", "Comandos parados, porta liberada (sem bip)")
                except Exception as e:
                    fc_log_exc("threshold", "Erro no cleanup de hardware", e)
            
            fc_log("threshold", "Cleanup concluído")
            
            # 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

            if hasattr(self, '_save_current_state'):
                try:
                    self._save_current_state()
                except Exception:
                    pass
        except:
            pass
        
        try:
            super().destroy()
        except:
            pass

    def _parse_float(self, raw_value, default=None):
        """Converte string numérica aceitando vírgula ou ponto como separador decimal."""
        if raw_value is None:
            if default is None:
                raise ValueError("Valor numérico vazio")
            return float(default)
        text = str(raw_value).strip()
        if text == "":
            if default is None:
                raise ValueError("Valor numérico vazio")
            text = str(default)
        return float(text.replace(",", "."))

    def _parse_attenuator_db_for_test(self):
        """Atenuador (dB) para orçamento de ligação: campo vazio usa o último perfil carregado (mesma cadeia física em «sem cal»)."""
        if not hasattr(self, "attenuator_var"):
            return 0.0
        raw = self.attenuator_var.get()
        text = str(raw).strip() if raw is not None else ""
        if text == "":
            try:
                from .calibration_service import get_last_loaded_profile_attenuator_db
                hint = get_last_loaded_profile_attenuator_db()
                if hint is not None:
                    return float(hint)
            except (TypeError, ValueError):
                pass
            return 0.0
        return self._parse_float(raw)

    def _test_for_history_tree_item(self, item):
        """Associa a linha do histórico (iid = id do teste na BD, ou hist_<índice> se iid duplicou)."""
        if not item or not getattr(self, "test_history", None):
            return None
        key = str(item).strip()
        if key.startswith("hist_"):
            try:
                idx = int(key.split("_", 1)[1])
                if 0 <= idx < len(self.test_history):
                    return self.test_history[idx]
            except (ValueError, IndexError):
                pass
        for t in self.test_history:
            if str(t.get("id", "")) == key:
                return t
        return None

    def _parse_test_history_date_time(self, raw):
        """Parse de date_time guardado no histórico (vários formatos)."""
        if not raw or not isinstance(raw, str):
            return None
        s = raw.strip()
        for fmt in (
            "%Y-%m-%d %H:%M:%S",
            "%d-%m-%Y %H:%M:%S",
            "%d-%m-%Y %H:%M",
            "%d/%m/%Y %H:%M:%S",
            "%d/%m/%Y %H:%M",
        ):
            try:
                return datetime.strptime(s, fmt)
            except ValueError:
                continue
        return None

    def _threshold_graph_freq_limits(self):
        ll = getattr(self, "license_limits", None) or {}
        return (float(ll.get("min_freq", 800)), float(ll.get("max_freq", 1000)))

    def _apply_threshold_autoscale_y(self, ax):
        """Eixo X = banda da licença; eixo Y só a partir das curvas (Line2D).

        axvspan (faixas de licença) não entra no cálculo — relim()/autoscale com patches
        puxava Y para 0–50+ e escondia séries como Max FCC Link Reversed com valores baixos.
        """
        if ax is None:
            return
        fmin, fmax = self._threshold_graph_freq_limits()
        ax.set_xlim(fmin, fmax)

        ys = []
        for line in ax.lines:
            if not line.get_visible():
                continue
            yd = line.get_ydata()
            if yd is None or len(yd) == 0:
                continue
            arr = np.asarray(yd, dtype=float)
            arr = arr[np.isfinite(arr)]
            if arr.size == 0:
                continue
            ys.append(float(arr.min()))
            ys.append(float(arr.max()))

        if ys:
            lo, hi = min(ys), max(ys)
            if lo == hi:
                pad = abs(lo) * 0.15 if lo != 0 else 0.5
                lo -= pad
                hi += pad
            else:
                span = hi - lo
                pad = max(span * 0.12, 1e-9)
                lo -= pad
                hi += pad
            ax.set_ylim(lo, hi)
            return

        # Sem curvas: não usar relim() (inclui PolyCollection do axvspan e estraga a escala Y).
        try:
            ax.margins(y=0.08)
        except Exception:
            pass
    
    def _request_realtime_redraw(self):
        """Pedido de redesenho: da thread do teste usa fila; no thread principal desenha já."""
        if threading.current_thread() is threading.main_thread():
            try:
                if self.winfo_exists():
                    self._redraw_realtime_graphs_on_main_thread()
            except tk.TclError:
                pass
            except Exception as e:
                fc_log_exc("threshold", "Erro ao redesenhar gráficos em tempo real (main thread)", e)
            return
        try:
            self._realtime_ui_queue.put_nowait(1)
        except Exception:
            pass

    def _realtime_queue_poll_tick(self):
        """Drena pedidos da fila (thread principal) e redesenha; reagenda enquanto o teste corre."""
        try:
            if not self.winfo_exists():
                return
        except tk.TclError:
            return
        n_pending = 0
        try:
            while True:
                self._realtime_ui_queue.get_nowait()
                n_pending += 1
        except queue.Empty:
            pass
        if n_pending > 0:
            try:
                self._redraw_realtime_graphs_on_main_thread()
            except Exception as e:
                fc_log_exc("threshold", "Erro ao redesenhar gráficos (poll tempo real)", e)
        if getattr(self, "is_test_running", False):
            try:
                self.after(25, self._realtime_queue_poll_tick)
            except tk.TclError:
                pass

    def _redraw_realtime_graphs_on_main_thread(self):
        """Lê buffers com lock e atualiza ambos os canvas no thread principal."""
        with self._realtime_plot_lock:
            if not hasattr(self, 'realtime_points'):
                return
            rp = {k: list(v) for k, v in self.realtime_points.items()}
            rc = list(getattr(self, 'realtime_calculated_data', []))

        if not rp.get('freqs'):
            return

        if hasattr(self, 'current_test_id') and self.current_test_id:
            base_colors = ['#1f77b4', '#d62728', '#2ca02c', '#ff7f0e', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
            color_index = self.current_test_id % len(base_colors)
            realtime_color = base_colors[color_index]
        else:
            realtime_color = '#1f77b4'

        def get_data_for_graph_type(graph_type):
            graph_type = self._normalize_graph_type(graph_type)
            if len(rc) < len(rp['freqs']):
                if "RSSI" in graph_type:
                    return rp['rssis']
                return rp['powers']
            graph_type_mapping = {
                "Module Power": 'module_power',
                "Module Power (dBm)": 'module_power',
                "Module RSSI": 'rssi',
                "Module RSSI (dBm)": 'rssi',
                "Irradiated Power": 'irradiated_power',
                "Irradiated Power (dBm)": 'irradiated_power',
                "Backscatter": 'backscatter',
                "Backscatter (dBm)": 'backscatter',
                "Power on Tag Forward (dBm)": 'power_on_tag_forward',
                "Power on Tag Reversed (dBm)": 'power_on_tag_reversed',
                "Conversion Loss (dBm)": 'conversion_loss',
                "Max FCC Link Forward (m)": 'max_fcc_link_forward',
                "Max FCC Link Reversed (m)": 'max_fcc_link_reversed',
                "RCS": 'rcs',
                "RCS (dBm²)": 'rcs'
            }
            key = graph_type_mapping.get(graph_type, 'module_power')
            try:
                return [data.get(key, 0) for data in rc]
            except Exception as e:
                fc_log_exc("threshold", f"Real-time: erro ao extrair dados para {graph_type}", e)
                if "RSSI" in graph_type:
                    return rp['rssis']
                return rp['powers']

        freqs = rp['freqs']

        if self.fig1 and self.fig1.axes and self.canvas1:
            self.ax1 = self.fig1.axes[0]
            for line in self.ax1.lines[:]:
                if hasattr(line, '_realtime_test') and line._realtime_test:
                    line.remove()
            data1 = get_data_for_graph_type(self.graph1_var.get())
            plot_realtime_segments_connected(self.ax1, freqs, data1, realtime_color, self.license_limits)
            self.ax1.grid(True, alpha=0.3)
            self.ax1.set_xlabel('')
            self.ax1.set_ylabel('')
            self._apply_threshold_autoscale_y(self.ax1)
            self.canvas1.draw()
            self.canvas1.flush_events()

        if self.fig2 and self.fig2.axes and self.canvas2:
            self.ax2 = self.fig2.axes[0]
            for line in self.ax2.lines[:]:
                if hasattr(line, '_realtime_test') and line._realtime_test:
                    line.remove()
            data2 = get_data_for_graph_type(self.graph2_var.get())
            plot_realtime_segments_connected(self.ax2, freqs, data2, realtime_color, self.license_limits)
            self.ax2.grid(True, alpha=0.3)
            self.ax2.set_xlabel('')
            self.ax2.set_ylabel('')
            self._apply_threshold_autoscale_y(self.ax2)
            self.canvas2.draw()
            self.canvas2.flush_events()

        self.update_idletasks()
        gc1 = getattr(self, 'graph1_container', None)
        gc2 = getattr(self, 'graph2_container', None)
        if gc1 is not None:
            gc1.update_idletasks()
        if gc2 is not None:
            gc2.update_idletasks()
        try:
            self.update()
        except tk.TclError:
            pass

    def add_realtime_point(self, freq, power, rssi):
        """Adiciona um ponto de threshold em tempo real; redesenho ocorre no thread principal."""
        try:
            if not self._validate_frequency_against_license(freq):
                fc_log("threshold", f"Real-time: Frequência {freq} MHz em faixa proibida - ignorando ponto")
                return

            MAX_REALTIME_POINTS = 1000

            with self._realtime_plot_lock:
                if not hasattr(self, 'realtime_points'):
                    self.realtime_points = {'freqs': [], 'powers': [], 'rssis': [], 'timestamps': []}

                current_time = time.time()
                if not hasattr(self, 'test_start_time'):
                    self.test_start_time = current_time
                elapsed_time = current_time - self.test_start_time

                self.realtime_points['freqs'].append(freq)
                self.realtime_points['powers'].append(power)
                self.realtime_points['rssis'].append(rssi)
                self.realtime_points['timestamps'].append(elapsed_time)

                if len(self.realtime_points['freqs']) > MAX_REALTIME_POINTS:
                    for key in self.realtime_points:
                        self.realtime_points[key] = self.realtime_points[key][-MAX_REALTIME_POINTS:]
                    fc_log("threshold", f"Limpeza automática: mantendo últimos {MAX_REALTIME_POINTS} pontos em tempo real")

                try:
                    attenuator = self._parse_attenuator_db_for_test()
                    distance = self._parse_float(self.distance_var.get(), default=1.0) if hasattr(self, 'distance_var') else 1.0
                    basic_result = {
                        'freq_mhz': freq,
                        'threshold_power': power,
                        'threshold_rssi': rssi,
                        'module_power': power,
                        'rssi': rssi
                    }
                    calculated_result = apply_corrections_to_result(
                        basic_result,
                        attenuator,
                        distance,
                        max_link_forward_eirp_dbm=self._max_link_forward_eirp_dbm_for_active_license(),
                    )
                    if not hasattr(self, 'realtime_calculated_data'):
                        self.realtime_calculated_data = []
                    self.realtime_calculated_data.append(calculated_result)
                    if len(self.realtime_calculated_data) > MAX_REALTIME_POINTS:
                        self.realtime_calculated_data = self.realtime_calculated_data[-MAX_REALTIME_POINTS:]
                except Exception as e:
                    fc_log_exc("threshold", "Erro ao calcular dados derivados", e)

            self._request_realtime_redraw()
            fc_log("threshold", f"Plotado: {freq}MHz, Power: {power}dBm, RSSI: {rssi:.2f}dBm")
        except Exception as e:
            fc_log_exc("threshold", "Erro ao plotar em tempo real", e)
            traceback.print_exc()

    def setup_ui(self):
        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 EQUILIBRADA
        if screen_width < 1366:  # Laptop pequeno
            sidebar_min = 210
            main_min = 600
            self.small_screen = True
        elif screen_width < 1920:  # Laptop médio
            sidebar_min = 230
            main_min = 700
            self.small_screen = False
        else:  # Monitor grande
            sidebar_min = 250
            main_min = 800
            self.small_screen = False
        
        self.grid_columnconfigure(0, weight=0, minsize=sidebar_min)
        self.grid_columnconfigure(1, weight=3, minsize=main_min)  # Mais peso para a área principal
        self.grid_rowconfigure(0, weight=1)
        self.setup_sidebar()
        self.setup_main_area()
        
    def _load_previous_state(self):
        """Carrega o estado anterior do programa"""
        try:
            state = self.state_persistence.load_state()
            
            # Carrega configurações da interface
            if 'interface_config' in state:
                config = state['interface_config']
                
                # CORREÇÃO: Garante que os valores sejam sempre válidos
                min_freq = config.get('min_freq', '840')
                max_freq = config.get('max_freq', '960')
                max_power = config.get('max_power', '25')
                freq_step = config.get('freq_step', '5')
                power_step = _normalize_threshold_power_step_str(config.get('power_step', ''))
                distance = config.get('distance', '1.0')
                attenuator = config.get('attenuator', '0.0')
                description = config.get('description', '')
                graph1_type_raw = config.get('graph1_type', 'Module Power (dBm)')
                graph2_type_raw = config.get('graph2_type', 'Module Power (dBm)')
                
                # Normaliza valores salvos para o idioma atual
                def normalize_saved_graph_type(saved_value):
                    # Se já está traduzido, retorna como está
                    graph_type_values = self._get_graph_type_values()
                    if saved_value in graph_type_values:
                        return saved_value
                    # Tenta mapear de inglês para traduzido
                    reverse_map = {v: k for k, v in self._get_graph_type_key_map().items()}
                    return reverse_map.get(saved_value, graph_type_values[0] if graph_type_values else t('threshold.graph_type_module_power'))
                
                graph1_type = normalize_saved_graph_type(graph1_type_raw)
                graph2_type = normalize_saved_graph_type(graph2_type_raw)
                
                # Valida e aplica apenas valores válidos
                if min_freq and min_freq.strip():
                    self.min_freq_var.set(min_freq)
                if max_freq and max_freq.strip():
                    self.max_freq_var.set(max_freq)
                if max_power and max_power.strip():
                    self.max_power_var.set(max_power)
                if freq_step and freq_step.strip():
                    self.freq_step_var.set(_normalize_threshold_freq_step_str(freq_step))
                self.power_step_var.set(power_step)
                if distance and distance.strip():
                    self.distance_var.set(distance)
                if attenuator and attenuator.strip():
                    self.attenuator_var.set(attenuator)
                if description and description.strip():
                    self.description_input_var.set(description)
                # CORREÇÃO: Valida se os tipos de gráfico existem antes de aplicar (aceita traduzidos e em inglês)
                valid_graph_types_translated = self._get_graph_type_values()
                valid_graph_types_english = ["Module Power (dBm)", "Module RSSI (dBm)", 
                                           "Irradiated Power (dBm)", "Backscatter (dBm)", 
                                           "Power on Tag Forward (dBm)", "Power on Tag Reversed (dBm)", 
                                           "Conversion Loss (dBm)", "Max FCC Link Forward (m)", "Max FCC Link Reversed (m)",
                                           "RCS (dBm²)"]
                all_valid_types = valid_graph_types_translated + valid_graph_types_english
                
                if graph1_type and graph1_type.strip() and graph1_type in all_valid_types:
                    self.graph1_var.set(graph1_type)
                else:
                    fc_log("threshold", f"Tipo de gráfico 1 inválido: '{graph1_type}' - usando padrão")
                    self.graph1_var.set(t('threshold.graph_type_module_power'))
                    
                if graph2_type and graph2_type.strip() and graph2_type in all_valid_types:
                    self.graph2_var.set(graph2_type)
                else:
                    fc_log("threshold", f"Tipo de gráfico 2 inválido: '{graph2_type}' - usando padrão")
                    self.graph2_var.set(t('threshold.graph_type_module_power'))

                self._suppress_project_field_trace = True
                try:
                    self.project_name_var.set(config.get("project_name", ""))
                    self.client_name_var.set(config.get("client_name", ""))
                finally:
                    self._suppress_project_field_trace = False
            
            # CORREÇÃO: Carrega estado dos testes selecionados do arquivo de estado
            if 'selected_tests' in state:
                selected_test_ids = state['selected_tests']
                fc_log("threshold", f"Carregando estado anterior: {len(selected_test_ids)} testes marcados")
                
                # Aplica o estado dos testes selecionados ao banco de dados
                if selected_test_ids:
                    # Primeiro, desmarca todos os testes
                    for test in self.test_history:
                        test['show_in_graphs'] = False
                    
                    # Depois, marca apenas os testes que estavam selecionados
                    for test_id in selected_test_ids:
                        test = next((t for t in self.test_history if t.get('id') == test_id), None)
                        if test:
                            test['show_in_graphs'] = True
                            fc_log("threshold", f"Teste ID {test_id} restaurado como marcado")
                        else:
                            fc_log("threshold", f"Teste ID {test_id} não encontrado no histórico atual")
                    
                    # Salva as alterações no banco de dados
                    for test in self.test_history:
                        self.database.update_test_visibility(test['id'], test['show_in_graphs'])
                    
                    fc_log("threshold", f"Estado dos testes selecionados restaurado: {len(selected_test_ids)} testes marcados")
                else:
                    fc_log("threshold", "Nenhum teste estava marcado na sessão anterior")
            else:
                fc_log("threshold", "Nenhum estado de testes selecionados encontrado")
            
            fc_log("threshold", "Estado anterior carregado com sucesso")
            
        except Exception as e:
            fc_log_exc("threshold", "Erro ao carregar estado anterior", e)
    
    def _on_project_or_client_field_changed(self, *_args):
        """Persiste alterações nos campos Nome/Cliente do projeto (threshold_state.json)."""
        if getattr(self, "_suppress_project_field_trace", False):
            return
        self._mark_state_changed()

    def _mark_state_changed(self):
        """Marca que o estado foi alterado e agenda salvamento automático"""
        self.state_changed = True
        
        # Agenda salvamento automático após 5 segundos de inatividade
        if hasattr(self, '_save_timer'):
            self.after_cancel(self._save_timer)
        
        self._save_timer = self.after(5000, lambda: self._auto_save_state())
    
    def _auto_save_state(self):
        """Salva o estado automaticamente se houver mudanças"""
        if self.state_changed:
            fc_log("threshold", "Salvamento automático do estado...")
            self._save_current_state()
    
    def _save_current_state(self):
        """Salva o estado atual do programa"""
        try:
            state = {
                "interface_config": {
                    "min_freq": self.min_freq_var.get(),
                    "max_freq": self.max_freq_var.get(),
                    "max_power": self.max_power_var.get(),
                    "freq_step": self.freq_step_var.get(),
                    "power_step": self.power_step_var.get(),
                    "distance": self.distance_var.get(),
                    "attenuator": self.attenuator_var.get(),
                    "description": self.description_input_var.get(),
                    "project_name": self.project_name_var.get(),
                    "client_name": self.client_name_var.get(),
                    "graph1_type": self._normalize_graph_type(self.graph1_var.get()),
                    "graph2_type": self._normalize_graph_type(self.graph2_var.get())
                },
                "tags": self.database.get_all_tags(),
                "test_history": self.database.get_test_history(),
                "projects": self.database.get_projects(),
                "selected_tags": list(self.selected_tags),
                "selected_tests": [test['id'] for test in self.test_history if test.get('show_in_graphs', False)],
                "graph_settings": {
                    "show_average": False,  # Será implementado posteriormente
                    "auto_scale": True
                },
                "last_session": {
                    "timestamp": datetime.now().isoformat(),
                    "version": "1.0"
                }
            }
            
            success = self.state_persistence.save_state(state)
            if success:
                self.state_changed = False
                fc_log("threshold", "Estado salvo com sucesso")
            else:
                fc_log("threshold", "Erro ao salvar estado (save_state retornou falha)")
                
        except Exception as e:
            fc_log_exc("threshold", "Erro ao salvar estado", e)
    
    def _validate_frequency_against_license(self, freq):
        """Valida APENAS a frequência contra faixas excluídas da licença"""
        fc_log("threshold", f"VALIDATION: frequência {freq} MHz")
        fc_log("threshold", f"VALIDATION: license_limits = {self.license_limits}")
        
        if not self.license_limits.get('is_licensed', False):
            fc_log("threshold", f"VALIDATION: sem licença ativa - permitindo {freq} MHz")
            return True  # Se não há licença, permite (modo browser)
        
        freq_ranges = self.license_limits.get('freq_ranges', [])
        excluded_ranges = self.license_limits.get('excluded_ranges', [])
        
        fc_log("threshold", f"VALIDATION: freq_ranges = {freq_ranges}")
        fc_log("threshold", f"VALIDATION: excluded_ranges = {excluded_ranges}")
        
        if freq_ranges:
            # Verifica se está em alguma faixa permitida
            for range_min, range_max in freq_ranges:
                fc_log("threshold", f"VALIDATION: faixa {range_min}-{range_max} MHz")
                if range_min <= freq <= range_max:
                    fc_log("threshold", f"VALIDATION: {freq} MHz na faixa {range_min}-{range_max} MHz")
                    # Verifica se NÃO está em uma faixa excluída
                    for excl_min, excl_max in excluded_ranges:
                        fc_log("threshold", f"VALIDATION: faixa excluída {excl_min}-{excl_max} MHz")
                        if excl_min <= freq <= excl_max:
                            fc_log("threshold", f"Frequência {freq} MHz na faixa EXCLUÍDA {excl_min}-{excl_max} MHz")
                            return False
                    # Se chegou aqui, está em faixa permitida e não excluída
                    fc_log("threshold", f"Frequência {freq} MHz aceita na faixa {range_min}-{range_max} MHz")
                    return True
            # Se chegou aqui, não está em nenhuma faixa permitida
            fc_log("threshold", f"Frequência {freq} MHz fora das faixas permitidas")
            return False
        else:
            # Fallback: validação simples (sem faixas excluídas)
            min_freq = self.license_limits.get('min_freq', 800)
            max_freq = self.license_limits.get('max_freq', 1000)
            fc_log("threshold", f"VALIDATION: validação simples {min_freq}-{max_freq} MHz")
            return min_freq <= freq <= max_freq
    
    def _show_realtime_legend(self):
        """Legendas removidas - método mantido para compatibilidade mas não exibe nada"""
        # Legendas foram removidas conforme solicitação do usuário
        pass
    
    def _ask_yes_no(self, title, message):
        """Cria um diálogo customizado com botões Yes/No traduzidos"""
        dialog = tk.Toplevel(self)
        dialog.title(title)
        dialog.transient(self)
        dialog.grab_set()
        
        # Centraliza a janela
        dialog.update_idletasks()
        x = (dialog.winfo_screenwidth() // 2) - (dialog.winfo_width() // 2)
        y = (dialog.winfo_screenheight() // 2) - (dialog.winfo_height() // 2)
        dialog.geometry(f"+{x}+{y}")
        
        self.response = None
        
        # Frame principal
        main_frame = ttk.Frame(dialog, padding="20")
        main_frame.pack(fill="both", expand=True)
        
        # Ícone e mensagem
        content_frame = ttk.Frame(main_frame)
        content_frame.pack(fill="both", expand=True)
        
        # Ícone de interrogação (pode usar um Label com texto ou imagem)
        icon_label = ttk.Label(content_frame, text="?", font=("Arial", 24, "bold"), foreground="blue")
        icon_label.pack(side="left", padx=(0, 15))
        
        # Mensagem
        msg_label = ttk.Label(content_frame, text=message, justify="left", wraplength=400)
        msg_label.pack(side="left", fill="both", expand=True)
        
        # Botões
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill="x", pady=(20, 0))
        
        yes_button = ttk.Button(button_frame, text=t('threshold.yes'), 
                               command=lambda: self._dialog_response(dialog, True),
                               width=10)
        yes_button.pack(side="right", padx=(10, 0))
        
        no_button = ttk.Button(button_frame, text=t('threshold.no'),
                              command=lambda: self._dialog_response(dialog, False),
                              width=10)
        no_button.pack(side="right")
        
        # Bind Enter para Yes, Escape para No
        dialog.bind("<Return>", lambda e: self._dialog_response(dialog, True))
        dialog.bind("<Escape>", lambda e: self._dialog_response(dialog, False))
        
        # Foca no botão Yes
        yes_button.focus_set()
        
        # Aguarda a resposta
        dialog.wait_window()
        
        return self.response
    
    def _dialog_response(self, dialog, response):
        """Callback para os botões do diálogo"""
        self.response = response
        dialog.destroy()
    
    def setup_sidebar(self):
        sidebar = tk.Frame(self, bg='#f8f8f8', relief='raised', bd=1)
        sidebar.grid(row=0, column=0, sticky='nswe', padx=5, pady=5)
        sidebar.grid_columnconfigure(0, weight=1)
        sidebar.grid_rowconfigure(6, weight=1)  # Aumentado para acomodar histórico de testes e segurança
        # Ajusta largura do sidebar baseado no tamanho da tela - LARGURA EQUILIBRADA
        if hasattr(self, 'small_screen') and self.small_screen:
            sidebar_width = max(210, int(210 * self.dpi_scale))
        else:
            sidebar_width = max(230, int(230 * self.dpi_scale))
        sidebar.configure(width=sidebar_width)
        sidebar.grid_propagate(False)
        
        # --- Sistema de Segurança ---
        safety_frame = tk.LabelFrame(sidebar, text=t('threshold.safety_system'), padx=5, pady=5, font=("Helvetica", 10, "bold"))
        safety_frame.grid(row=0, column=0, sticky='ew', pady=10, padx=5)
        safety_frame.columnconfigure(3, weight=1)
        self._widget_refs['safety_frame'] = safety_frame

        self.safety_status_label = tk.Label(safety_frame, text="", fg="green", font=("Helvetica", 9, "bold"))
        self.safety_status_label.grid(row=0, column=0, columnspan=5, sticky='w', padx=5)
        
        # Mostrar limites da licença
        self.license_info_label = tk.Label(safety_frame, text="", fg="blue", font=("Helvetica", 8))
        self.license_info_label.grid(row=2, column=0, columnspan=5, sticky='w', padx=5)

        # Indicação de calibração ativa (integração com módulo Calibration)
        self.calibration_status_label = tk.Label(safety_frame, text="", fg="purple", font=("Helvetica", 8, "bold"))
        self.calibration_status_label.grid(row=3, column=0, columnspan=5, sticky='w', padx=5, pady=(2, 0))
        
        temp_label = tk.Label(safety_frame, text=t('threshold.temperature'), font=("Helvetica", 9))
        temp_label.grid(row=1, column=0, sticky='w', padx=5)
        self._widget_refs['temperature_label_text'] = temp_label
        self.temperature_label = tk.Label(safety_frame, text="--°C", fg="gray", font=("Helvetica", 10, "bold"))
        self.temperature_label.grid(row=1, column=1, sticky='w')
        
        vswr_text_label = tk.Label(safety_frame, text=t('threshold.vswr'), font=("Helvetica", 9))
        vswr_text_label.grid(row=1, column=2, sticky='w', padx=(15, 5))
        self._widget_refs['vswr_label_text'] = vswr_text_label
        self.vswr_label = tk.Label(safety_frame, text="--", fg="gray", font=("Helvetica", 10, "bold"))
        self.vswr_label.grid(row=1, column=3, sticky='w')
        self.vswr_status_label = tk.Label(safety_frame, text="", fg="gray", font=("Helvetica", 8, "bold"))
        self.vswr_status_label.grid(row=1, column=4, sticky='w', padx=5)
        
        # --- Mensagem de Modo Browser ---
        self.browser_frame = tk.Frame(sidebar, bg='#f0f8ff')
        self.browser_frame.grid(row=1, column=0, sticky='ew', pady=(5, 5), padx=5)
        
        # Bullet point azul
        bullet_label = tk.Label(self.browser_frame, text="•", fg="blue", font=("Helvetica", 10), bg='#f0f8ff')
        bullet_label.pack(side='left', anchor='w')
        
        # Texto "modo browser"
        self.browser_mode_label = tk.Label(self.browser_frame, text=t('threshold.browser_mode'), fg="blue", font=("Helvetica", 10), bg='#f0f8ff')
        self.browser_mode_label.pack(side='left', anchor='w')
        self._widget_refs['browser_mode_label'] = self.browser_mode_label
        
        # --- Ações ---
        action_frame = tk.Frame(sidebar, bg='#f8f8f8')
        action_frame.grid(row=2, column=0, sticky='ew', pady=10, padx=5)
        
        # Adiciona o texto padrão como primeira opção na lista
        select_action_text = t('threshold.select_an_action')
        simplified_actions = [select_action_text, t('threshold.register_new_tags'), t('threshold.import_tag_json'), t('threshold.select_all_tags'), t('threshold.deselect_all_tags'), t('threshold.erase_selected_tags'), t('threshold.save_selected_tags')]
        self._simplified_actions_ref = simplified_actions  # Armazena para atualizar no refresh_language
        dropdown_width = 21 if hasattr(self, 'small_screen') and self.small_screen else 23
        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
        # Define valor padrão traduzido
        self.action_var.set(t('threshold.select_an_action'))
        
        # --- Tags Registradas com Checkboxes ---
        tags_frame = tk.LabelFrame(sidebar, text=t('threshold.registered_tags'), labelanchor="n", padx=5, pady=5)
        self._widget_refs['tags_frame'] = tags_frame
        tags_frame.grid(row=3, column=0, sticky='nsew', pady=10, padx=5)
        tags_frame.grid_rowconfigure(1, weight=1)
        tags_frame.grid_columnconfigure(0, weight=1)
        

        
        # Treeview com checkboxes - altura ajustada para telas pequenas
        tree_height = 6 if hasattr(self, 'small_screen') and self.small_screen else 8
        self.tags_tree = ttk.Treeview(tags_frame, columns=('select', 'tag'), show='headings', height=tree_height)
        self.tags_tree.heading('select', text='')
        self.tags_tree.heading('tag', text='')
        
        self.tags_tree.column('select', width=30, stretch=False, anchor='center')
        tag_column_width = 200 if hasattr(self, 'small_screen') and self.small_screen else 220
        self.tags_tree.column('tag', width=tag_column_width)
        
        self.tags_tree.grid(row=1, column=0, sticky='nsew')
        self.tags_tree.bind('<Button-1>', self.on_tree_click)
        self.tags_tree.bind('<Button-3>', self.show_context_menu)
        
        # Scrollbar para o treeview
        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')
        
        # --- Projeto ---
        project_frame = tk.LabelFrame(sidebar, text=t('threshold.project'), labelanchor="n", padx=5, pady=5)
        project_frame.grid(row=4, column=0, sticky='ew', pady=10, padx=5)
        project_frame.columnconfigure(1, weight=1)
        self._widget_refs['project_frame'] = project_frame
        
        project_content = tk.Frame(project_frame, bg='#f8f8f8')
        project_content.pack(fill='x', padx=5, pady=5)
        project_content.columnconfigure(1, weight=1)
        
        project_name_label = tk.Label(project_content, text=t('threshold.project_name'), font=("Segoe UI", 9), bg='#f8f8f8')
        project_name_label.grid(row=0, column=0, sticky='w', pady=2)
        self._widget_refs['project_name_label'] = project_name_label
        self.project_name_entry = tk.Entry(project_content, textvariable=self.project_name_var, font=("Segoe UI", 9))
        self.project_name_entry.grid(row=0, column=1, sticky='ew', padx=5)
        self.project_name_var.trace_add("write", self._on_project_or_client_field_changed)

        client_label = tk.Label(project_content, text=t('threshold.client'), font=("Segoe UI", 9), bg='#f8f8f8')
        client_label.grid(row=1, column=0, sticky='w', pady=2)
        self._widget_refs['client_label'] = client_label
        self.client_name_entry = tk.Entry(project_content, textvariable=self.client_name_var, font=("Segoe UI", 9))
        self.client_name_entry.grid(row=1, column=1, sticky='ew', padx=5)
        self.client_name_var.trace_add("write", self._on_project_or_client_field_changed)

        project_button_frame = ttk.Frame(project_content)
        project_button_frame.grid(row=2, column=0, columnspan=2, pady=10, sticky='ew')
        project_button_frame.columnconfigure((0, 1), weight=1)

        self.save_project_button = ttk.Button(project_button_frame, text=t('threshold.save'), command=self.save_project)
        self.save_project_button.grid(row=0, column=0, sticky='ew', padx=2, pady=1)
        self._widget_refs['save_project_button'] = self.save_project_button
        self.delete_project_button = ttk.Button(project_button_frame, text=t('threshold.clear'), command=self.delete_project)
        self.delete_project_button.grid(row=0, column=1, sticky='ew', padx=2, pady=1)
        self._widget_refs['delete_project_button'] = self.delete_project_button
        import_project_button = ttk.Button(project_button_frame, text=t('threshold.import'), command=self.import_project)
        import_project_button.grid(row=1, column=0, columnspan=2, sticky='ew', pady=1)
        self._widget_refs['import_project_button'] = import_project_button

        # --- Parâmetros de Teste ---
        params_frame = tk.LabelFrame(sidebar, text=t('threshold.test_config'), labelanchor="n", padx=5, pady=5)
        params_frame.grid(row=5, column=0, sticky='nsew', pady=10, padx=5)
        params_frame.columnconfigure(1, weight=1)
        self._widget_refs['params_frame'] = params_frame

        freq_label = tk.Label(params_frame, text=t('threshold.freq_mhz'), font=("Helvetica", 9))
        freq_label.grid(row=0, column=0, sticky='w', pady=1)
        self._widget_refs['freq_label'] = freq_label
        freq_input_frame = tk.Frame(params_frame)
        freq_input_frame.grid(row=0, column=1, sticky='ew', padx=5)

        self.min_freq_entry = tk.Entry(freq_input_frame, textvariable=self.min_freq_var, width=6, font=("Helvetica", 9))
        self.min_freq_entry.pack(side='left')

        to_label = tk.Label(freq_input_frame, text=t('threshold.to'), font=("Helvetica", 9))
        to_label.pack(side='left', padx=2)
        self._widget_refs['to_label'] = to_label

        self.max_freq_entry = tk.Entry(freq_input_frame, textvariable=self.max_freq_var, width=6, font=("Helvetica", 9))
        self.max_freq_entry.pack(side='left')

        
        # NOTA: Validação de frequências é feita em tempo real pelos campos Entry
        # Os usuários podem inserir qualquer valor dentro da banda da licença
        
        power_label = tk.Label(params_frame, text=t('threshold.power_dbm'), font=("Helvetica", 9))
        power_label.grid(row=1, column=0, sticky='w', pady=1)
        self._widget_refs['power_label'] = power_label
        self.max_power_entry = tk.Entry(params_frame, textvariable=self.max_power_var, width=10, font=("Helvetica", 9))
        self.max_power_entry.grid(row=1, column=1, sticky='ew', padx=5)
        # NOTA: Validação de potência será feita apenas quando "Start Test" for clicado
        # Os usuários podem digitar livremente sem interrupções
        
        freq_step_label = tk.Label(params_frame, text=t('threshold.freq_step'), font=("Helvetica", 9))
        freq_step_label.grid(row=2, column=0, sticky='w', pady=1)
        self._widget_refs['freq_step_label'] = freq_step_label
        self.freq_step_combobox = ttk.Combobox(params_frame, textvariable=self.freq_step_var, values=["1", "5", "10"], state="readonly", width=10)
        self.freq_step_combobox.grid(row=2, column=1, sticky='ew', padx=5)
        self.freq_step_combobox.bind('<<ComboboxSelected>>', self.on_frequency_changed)
        
        power_step_label = tk.Label(params_frame, text=t('threshold.power_step'), font=("Helvetica", 9))
        power_step_label.grid(row=3, column=0, sticky='w', pady=1)
        self._widget_refs['power_step_label'] = power_step_label
        self.power_step_combobox = ttk.Combobox(params_frame, textvariable=self.power_step_var, values=["0.5", "1.0"], state="readonly", width=10)
        self.power_step_combobox.grid(row=3, column=1, sticky='ew', padx=5)
        
        distance_label = tk.Label(params_frame, text=t('threshold.distance'), font=("Helvetica", 9))
        distance_label.grid(row=4, column=0, sticky='w', pady=1)
        self._widget_refs['distance_label'] = distance_label
        self.distance_entry = tk.Entry(params_frame, textvariable=self.distance_var, width=10)
        self.distance_entry.grid(row=4, column=1, sticky='ew', padx=5)
        
        attenuator_label = tk.Label(params_frame, text=t('threshold.attenuator'), font=("Helvetica", 9))
        attenuator_label.grid(row=5, column=0, sticky='w', pady=1)
        self._widget_refs['attenuator_label'] = attenuator_label
        self.attenuator_entry = tk.Entry(params_frame, textvariable=self.attenuator_var, width=10)
        self.attenuator_entry.grid(row=5, column=1, sticky='ew', padx=5)
        
        description_label = tk.Label(params_frame, text=t('threshold.description'), font=("Helvetica", 9))
        description_label.grid(row=6, column=0, sticky='w', pady=1)
        self._widget_refs['description_label'] = description_label
        self.description_entry = tk.Entry(params_frame, textvariable=self.description_input_var, font=("Helvetica", 9))
        self.description_entry.grid(row=6, column=1, sticky='ew', padx=5)
        
        # Botão único Testar / Cancelar Teste (alterna conforme estado do teste)
        self.start_button = ttk.Button(params_frame, text=t('threshold.start_test'), command=self._on_test_button_click)
        self.start_button.grid(row=7, column=0, columnspan=2, sticky='ew', pady=10)
        self._widget_refs['start_button'] = self.start_button
        
        # Botão para mostrar tabela de resultados
        self.show_table_button = ttk.Button(params_frame, text=t('threshold.show_table'), command=self.show_selected_test_table)
        self.show_table_button.grid(row=8, column=0, columnspan=2, sticky='ew', pady=5)
        self._widget_refs['show_table_button'] = self.show_table_button
        
        # Botão para gerar relatório PDF dos testes selecionados
        self.pdf_report_button = ttk.Button(params_frame, text=t('threshold.pdf_report'), command=self.generate_pdf_report)
        self.pdf_report_button.grid(row=9, column=0, columnspan=2, sticky='ew', pady=5)
        self._widget_refs['pdf_report_button'] = self.pdf_report_button
        

        
        # Botão VSWR removido (desnecessário)
    
    def _update_calibration_status_label(self):
        """
        Atualiza o texto do label de calibração no sidebar do Threshold,
        indicando se existe ou não uma calibração ativa no sistema.
        """
        try:
            from .calibration_service import has_active_calibration, get_active_profile
        except ImportError:
            if hasattr(self, 'calibration_status_label'):
                self.calibration_status_label.config(text=f"{t('threshold.calibration_status')} {t('threshold.calibration_none')}")
            return

        if not hasattr(self, 'calibration_status_label'):
            return

        if not has_active_calibration():
            text = f"{t('threshold.calibration_status')} {t('threshold.calibration_none')}"
        else:
            profile = get_active_profile() or {}
            name = profile.get("profile_name") or profile.get("name") or ""
            if not name:
                freq_min = profile.get("freq_min_mhz", "")
                freq_max = profile.get("freq_max_mhz", "")
                step = profile.get("freq_step_mhz", "")
                name = f"{freq_min}-{freq_max}MHz / step {step}MHz"
            text = f"{t('threshold.calibration_status')} {name}"

        self.calibration_status_label.config(text=text)

    def _apply_calibration_distance_and_attenuator_if_active(self):
        """
        Com calibração ativa: preenche Distância e Atenuador a partir do perfil e bloqueia ambos.
        Sem calibração: desbloqueia as duas entradas (valores mantidos).
        """
        try:
            from .calibration_service import has_active_calibration, get_active_profile
        except ImportError:
            if hasattr(self, 'distance_entry'):
                self.distance_entry.config(state='normal')
            if hasattr(self, 'attenuator_entry'):
                self.attenuator_entry.config(state='normal')
            return
        if not hasattr(self, 'distance_entry') or not hasattr(self, 'distance_var'):
            return
        if not hasattr(self, 'attenuator_entry') or not hasattr(self, 'attenuator_var'):
            return
        if has_active_calibration():
            profile = get_active_profile() or {}
            dist = profile.get("distance_m")
            if dist is not None:
                self.distance_var.set(str(dist))
            self.distance_entry.config(state='disabled')
            att = profile.get("attenuator_db")
            if att is not None:
                self.attenuator_var.set(str(att))
            self.attenuator_entry.config(state='disabled')
        else:
            self.distance_entry.config(state='normal')
            self.attenuator_entry.config(state='normal')

    def _schedule_calibration_status_refresh(self):
        """
        Agenda uma atualização periódica do status de calibração.
        Isso garante que, ao ativar/desativar a calibração em outro módulo,
        o indicador do Threshold seja atualizado automaticamente.
        """
        # Atualiza o label agora
        try:
            self._update_calibration_status_label()
        except Exception:
            pass
        # Preenche e bloqueia/desbloqueia Distância e Atenuador conforme calibração ativa
        try:
            self._apply_calibration_distance_and_attenuator_if_active()
        except Exception:
            pass

        # Agenda próxima atualização (a cada 2 segundos, leve para UI)
        try:
            if hasattr(self, "_calibration_status_job") and self._calibration_status_job:
                self.after_cancel(self._calibration_status_job)
        except Exception:
            pass
        self._calibration_status_job = self.after(2000, self._schedule_calibration_status_refresh)
    def generate_pdf_report(self):
        """Gera relatório PDF com diálogo para salvar arquivo - apenas testes selecionados"""
        try:
            # Verifica se há dados para relatório
            if not hasattr(self, 'database') or not self.database:
                messagebox.showwarning(t('threshold.warning_no_tags_selected'), t('threshold.warning_db_unavailable'), parent=self)
                return
            
            # Obtém apenas os testes selecionados
            selected_ids = self.get_selected_test_ids()
            if not selected_ids:
                messagebox.showwarning(t('threshold.warning_no_test_selected_report'), t('threshold.warning_no_test_selected_report_msg'), parent=self)
                return
            
            # Carrega apenas os testes selecionados
            history_data = []
            for test_id in selected_ids:
                test_data = self.database.get_test_by_id(test_id)
                if test_data:
                    history_data.append(test_data)
            
            if not history_data:
                messagebox.showwarning(t('threshold.warning_no_tags_selected'), t('threshold.warning_no_valid_test'), parent=self)
                return
            
            # Gera nome do arquivo com data e hora
            from datetime import datetime
            now = datetime.now()
            filename = f"Threshold Reporte Selecionados {now.strftime('%d.%m.%Y_%H.%M.%S')}.pdf"
            
            # Diálogo para salvar arquivo
            from tkinter import filedialog
            filepath = filedialog.asksaveasfilename(
                defaultextension=".pdf",
                filetypes=[(t('fastthreshold.pdf_files'), "*.pdf")],
                initialfile=filename,
                title=t('threshold.save_report_title'),
                parent=self
            )
            
            if not filepath:
                return  # Usuário cancelou
            
            # Mostra mensagem de progresso
            fc_log("threshold", 'Gerando relatório PDF...')
            self.update()
            
            # Gera o PDF com dados selecionados
            result = self._generate_pdf_with_selected_tests(filepath, history_data)
            
            if result['success']:
                messagebox.showinfo(t('threshold.success'), t('threshold.success_report_generated').format(filepath=filepath, count=len(history_data)), parent=self)
                
                # Abre o PDF automaticamente (fallbacks se startfile falhar)
                open_path_with_default_app(filepath, log_tag="threshold")
            else:
                error_msg = result.get('error', 'Erro desconhecido')
                messagebox.showerror(t('threshold.error'), t('threshold.error_generating_pdf').format(error=error_msg), parent=self)
                
        except Exception as e:
            fc_log("threshold", f'Erro ao gerar relatório PDF: {str(e)}')
            import traceback
            traceback.print_exc()
            messagebox.showerror(t('threshold.error'), t('threshold.error_unexpected_report').format(error=str(e)), parent=self)

    def get_selected_test_ids(self):
        """Retorna uma lista com os IDs dos testes selecionados no histórico"""
        if not hasattr(self, 'test_history'):
            return []
        
        selected_ids = []
        for test in self.test_history:
            if test.get('show_in_graphs', False):
                test_id = test.get('id')
                if test_id is not None:
                    selected_ids.append(test_id)
        
        fc_log("threshold", f'[DATA] Testes selecionados para relatório: {selected_ids}')
        return selected_ids

    def _generate_pdf_with_selected_tests(self, filepath, selected_tests):
        """Gera PDF usando ReportLab com dados selecionados"""
        try:
            fc_log("threshold", f'[INFO] Iniciando geração de PDF para {len(selected_tests)} testes...')           
            # Importa bibliotecas necessárias
            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
            from .pdf_pagination_utils import (
                cond_page_break_before_chart_section,
                cond_page_break_before_large_figure,
                cond_page_break_before_footer_block,
            )
            from reportlab.lib import colors
            from reportlab.lib.enums import TA_CENTER, TA_LEFT
            import tempfile
            import base64
            import os
            
            fc_log("threshold", '[OK] Importações do ReportLab concluídas com sucesso')          
            # Função para obter informações do sistema da licença ativa
            def _get_system_info():
                """Obtém informações do sistema da licença ativa"""
                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(getattr(self, 'com_port', 4))
                        # Mantém a versão do software (4.0.0) sem sobrescrever com nome do módulo
                        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(getattr(self, 'com_port', 4))
                        # Mantém a versão do software (4.0.0) sem sobrescrever com nome do módulo
                        return system_info
                    except Exception as fallback_error:
                        fc_log("threshold", f'[AVISO] Erro no fallback de informações do sistema: {fallback_error}')                   
                    # Fallback final: informações básicas
                    from datetime import datetime
                    return {
                        'software': '4.0.0',
                        'hardware': 'N/A',
                        'firmware': 'N/A',
                        'serial_number': 'N/A',
                        'license': 'N/A',
                        'generated_at': datetime.now().strftime('%d/%m/%Y %H:%M:%S')
                    }
                    
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro geral ao obter informações do sistema: {e}')
                    from datetime import datetime
                    return {
                        'software': '4.0.0',
                        'hardware': 'N/A',
                        'firmware': 'N/A',
                        'serial_number': 'N/A',
                        'license': 'N/A',
                        'generated_at': datetime.now().strftime('%d/%m/%Y %H:%M:%S')
                    }
            
            # Cria o documento PDF
            doc = SimpleDocTemplate(filepath, pagesize=A4, 
                                  rightMargin=72, leftMargin=72, 
                                  topMargin=72, bottomMargin=18)
            
            # Estilos
            styles = getSampleStyleSheet()
            title_style = ParagraphStyle(
                'CustomTitle',
                parent=styles['Heading1'],
                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')
            )
            
            # Conteúdo do documento
            story = []
            
            # 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):
                    # Mantém proporção circular (1:1) - logo redondo
                    logo_size = 1.5*inch
                    logo_img = Image(logo_path, width=logo_size, height=logo_size)
                    story.append(logo_img)
                    story.append(Spacer(1, 10))
                    fc_log("threshold", 'Logo Fasttag adicionado ao PDF (proporção circular)')
                else:
                    fc_log("threshold", 'Logo Fasttag não encontrado')
            except Exception as e:
                fc_log("threshold", f'Erro ao adicionar logo: {e}')
            
            # Título
            story.append(Paragraph(t('pdf.report_title_threshold'), title_style))
            story.append(Spacer(1, 20))
            
            # Informações do sistema
            sysinfo = _get_system_info()
            story.append(Paragraph(t('pdf.system_info'), heading_style))
            
            info_text = f"""
            <b>{t('pdf.software')}</b> {sysinfo.get('software', '4.0.0')}<br/>
            <b>{t('pdf.hardware')}</b> {sysinfo.get('hardware', 'N/A')}<br/>
            <b>{t('pdf.firmware')}</b> {sysinfo.get('firmware', 'N/A')}<br/>
            <b>{t('pdf.serial_number')}</b> {sysinfo.get('serial_number', 'N/A')}<br/>
            <b>{t('pdf.license')}</b> {sysinfo.get('license', 'N/A')}<br/>
            <b>{t('pdf.generated_at')}</b> {sysinfo.get('generated_at', 'N/A')}
            """
            story.append(Paragraph(info_text, styles['Normal']))
            story.append(Spacer(1, 30))
            
            # Descrição do teste
            story.append(Paragraph(t('pdf.test_description_title'), heading_style))
            test_description = t('pdf.test_description_text')
            story.append(Paragraph(test_description, styles['Normal']))
            story.append(Spacer(1, 20))
            
            # Configuração do teste
            story.append(Paragraph(t('pdf.test_configuration'), heading_style))
            
            # Obtém configuração do primeiro teste como referência
            if selected_tests:
                first_test = selected_tests[0]
                config_text = f"""
                <b>{t('pdf.min_frequency')}</b> {getattr(self, 'min_freq_var', tk.StringVar(value='840')).get()} MHz<br/>
                <b>{t('pdf.max_frequency')}</b> {getattr(self, 'max_freq_var', tk.StringVar(value='960')).get()} MHz<br/>
                <b>{t('pdf.max_power')}</b> {getattr(self, 'max_power_var', tk.StringVar(value='25')).get()} dBm<br/>
                <b>{t('pdf.freq_step')}</b> {getattr(self, 'freq_step_var', tk.StringVar(value='1')).get()} MHz<br/>
                <b>{t('pdf.power_step')}</b> {getattr(self, 'power_step_var', tk.StringVar(value='0.5')).get()} dBm<br/>
                <b>{t('pdf.distance')}</b> {getattr(self, 'distance_var', tk.StringVar(value='1.0')).get()} m<br/>
                <b>{t('pdf.attenuator')}</b> {getattr(self, 'attenuator_var', tk.StringVar(value='0.0')).get()} dB<br/>
                """
                story.append(Paragraph(config_text, styles['Normal']))
                story.append(Spacer(1, 30))
            
            # Tabela de testes - formato exato da interface
            story.append(Paragraph(t('pdf.test_summary'), heading_style))
            
            # Cabeçalhos da tabela - exatamente como na interface
            headers = [t('pdf.plot'), t('pdf.test_name'), t('pdf.epc'), t('pdf.attenuator_col'), t('pdf.distance_col'), t('pdf.date_time'), t('pdf.duration')]
            table_data = [headers]
            
            # Dados dos testes selecionados
            for test in selected_tests:
                # Plot (sempre selecionado se está no relatório)
                plot = '☑'
                
                # Nome do teste
                test_name = test.get('description', f"T{test.get('id', 'N/A')}")
                
                # EPC
                epc = test.get('epc', '-')
                
                # Atenuador
                attenuator = test.get('attenuator', '-')
                if attenuator is not None and attenuator != '-':
                    try:
                        attenuator = f"{float(attenuator):.1f}"
                    except (ValueError, TypeError):
                        attenuator = str(attenuator)
                
                # Distância
                distance = test.get('distance', '-')
                if distance is not None and distance != '-':
                    try:
                        distance = f"{float(distance):.1f}"
                    except (ValueError, TypeError):
                        distance = str(distance)
                
                # Data/Hora - formata conforme idioma
                date_time_raw = test.get('date_time', '-')
                if date_time_raw != '-' and isinstance(date_time_raw, str) and date_time_raw:
                    try:
                        from datetime import datetime
                        # Tenta parsear diferentes formatos
                        try:
                            date_time_dt = datetime.strptime(date_time_raw, '%d/%m/%Y %H:%M:%S')
                        except:
                            try:
                                date_time_dt = datetime.strptime(date_time_raw, '%Y-%m-%d %H:%M:%S')
                            except:
                                date_time_dt = datetime.fromisoformat(date_time_raw.replace('Z', '+00:00'))
                        
                        # Usa o tradutor global para formatar data conforme idioma
                        translator = _get_translator()
                        if translator.get_language() == 'en':
                            date_time = date_time_dt.strftime('%m/%d/%y %H:%M:%S')
                        else:
                            date_time = date_time_dt.strftime('%d/%m/%Y %H:%M:%S')
                    except:
                        date_time = date_time_raw
                else:
                    date_time = date_time_raw
                
                # Tempo (duração do teste) - campo correto
                duration = test.get('test_duration_seconds', test.get('duration', test.get('test_duration', '-')))
                if duration is not None and duration != '-':
                    try:
                        duration = f"{float(duration):.1f}s"
                    except (ValueError, TypeError):
                        duration = str(duration) + "s" if str(duration) != '-' else '-'
                
                table_data.append([
                    plot,
                    test_name,
                    epc,
                    str(attenuator),
                    str(distance),
                    date_time,
                    str(duration)
                ])
            
            # Cria a tabela com colunas ajustadas para o formato da interface
            test_table = Table(
                table_data,
                colWidths=[0.5*inch, 1.5*inch, 2*inch, 0.8*inch, 0.8*inch, 1.5*inch, 0.8*inch],
                repeatRows=1,
            )
            test_table.setStyle(TableStyle([
                ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#007bff')),
                ('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
                ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
                ('ALIGN', (2, 1), (2, -1), 'LEFT'),  # EPC alinhado à esquerda
                ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
                ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
                ('FONTSIZE', (0, 0), (-1, -1), 9),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 8),
                ('TOPPADDING', (0, 0), (-1, -1), 8),
                ('GRID', (0, 0), (-1, -1), 1, colors.black),
                ('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f8f9fa')])
            ]))
            
            story.append(test_table)
            story.append(Spacer(1, 16))
            
            # Gráficos — quebra só se não couber a secção no resto da página
            story.append(cond_page_break_before_chart_section())
            story.append(Paragraph(t('pdf.test_charts'), heading_style))
            
            # Lista de todos os tipos de gráficos disponíveis
            graph_types = [
                "Module Power (dBm)",
                "Module RSSI (dBm)", 
                "Irradiated Power (dBm)",
                "Backscatter (dBm)",
                "Power on Tag Forward (dBm)",
                "Power on Tag Reversed (dBm)",
                "Conversion Loss (dBm)",
                "Max FCC Link Forward (m)",
                "Max FCC Link Reversed (m)",
                "RCS (dBm²)"
            ]
            
            # Lista para rastrear arquivos temporários
            temp_files = []
            
            # Gera gráfico para cada tipo
            for graph_type in graph_types:
                try:
                    fc_log("threshold", f'[INFO] Gerando gráfico: {graph_type}')
                    story.append(cond_page_break_before_large_figure(4.0))
                    # Gera imagem do gráfico
                    chart_image = self._generate_threshold_chart_image(selected_tests, graph_type)
                    if chart_image:
                        # Salva a imagem temporariamente - CORRIGIDO: não deleta automaticamente
                        with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
                            tmp_file.write(base64.b64decode(chart_image))
                            tmp_path = tmp_file.name
                            temp_files.append(tmp_path)  # Adiciona à lista para limpeza posterior
                        
                        try:
                            # Verifica se o arquivo foi criado corretamente
                            if os.path.exists(tmp_path) and os.path.getsize(tmp_path) > 0:
                                # Adiciona título do gráfico
                                story.append(Paragraph(f"Threshold - {graph_type}", heading_style))
                                
                                # Adiciona a imagem ao PDF
                                img = Image(tmp_path, width=7*inch, height=4*inch)
                                story.append(img)
                                story.append(Spacer(1, 20))
                                
                                fc_log("threshold", f'[OK] Gráfico {graph_type} adicionado ao PDF')
                            else:
                                fc_log("threshold", f'[ERRO] Arquivo temporário inválido para {graph_type}')
                                story.append(Paragraph(f"Threshold - {graph_type} {t('pdf.chart_error')}", heading_style))
                                story.append(Paragraph(t('pdf.chart_unavailable'), styles['Normal']))
                                story.append(Spacer(1, 20))
                            
                        finally:
                            # Arquivo temporário será removido após o PDF ser gerado
                            pass
                    else:
                        fc_log("threshold", f'[AVISO] Não foi possível gerar gráfico para: {graph_type}')
                        # Adiciona mensagem quando não há dados
                        story.append(Paragraph(f"Threshold - {graph_type}", heading_style))
                        story.append(Paragraph(f"<i>{t('pdf.no_data_available')}</i>", styles['Normal']))
                        story.append(Spacer(1, 20))
                        
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao gerar gráfico {graph_type}: {e}')
                    import traceback
                    traceback.print_exc()
                    continue
            
            # Rodapé do relatório (evita página quase vazia antes do texto final)
            story.append(cond_page_break_before_footer_block())
            story.append(Spacer(1, 12))
            story.append(Paragraph(t('pdf.additional_info'), heading_style))
            
            footer_text = t('pdf.footer_text')
            story.append(Paragraph(footer_text, styles['Normal']))
            
            from datetime import datetime
            # 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")
            story.append(Paragraph(f"<b>{t('pdf.document_generated_at')}</b> {timestamp}", styles['Normal']))
            
            # Gera o PDF
            doc.build(story)
            
            # Limpa arquivos temporários após gerar o PDF
            for temp_file in temp_files:
                try:
                    if os.path.exists(temp_file):
                        os.unlink(temp_file)
                        fc_log("threshold", f'🗑️ Arquivo temporário removido: {temp_file}')
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao remover arquivo temporário {temp_file}: {e}')
            fc_log("threshold", f'[OK] PDF gerado com sucesso: {filepath}')
            return {'success': True, 'filepath': filepath}
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao gerar PDF: {e}')
            import traceback
            traceback.print_exc()
            return {'success': False, 'error': str(e)}

    def _generate_threshold_chart_image(self, selected_tests, graph_type):
        """Gera imagem do gráfico para um tipo específico com todos os testes selecionados"""
        try:
            import matplotlib.pyplot as plt
            import matplotlib
            matplotlib.use('Agg')  # Backend sem GUI
            import io
            import base64
            
            fc_log("threshold", f'[INFO] Gerando gráfico {graph_type} para {len(selected_tests)} testes...')            
            # Configuração da figura
            plt.figure(figsize=(12, 6))
            plt.style.use('default')
            
            # Cores para diferentes testes
            colors = ['#007bff', '#28a745', '#dc3545', '#ffc107', '#17a2b8', '#6f42c1', '#fd7e14', '#20c997']
            
            # Mapeamento de tipos de gráfico para dados
            data_mapping = {
                "Module Power (dBm)": "module_power",
                "Module RSSI (dBm)": "module_rssi", 
                "Irradiated Power (dBm)": "irradiated_power",
                "Backscatter (dBm)": "backscatter",
                "Power on Tag Forward (dBm)": "power_tag_forward",
                "Power on Tag Reversed (dBm)": "power_tag_reverse",
                "Conversion Loss (dBm)": "conversion_loss",
                "Max FCC Link Forward (m)": "link_forward",
                "Max FCC Link Reversed (m)": "link_reverse",
                "RCS (dBm²)": "rcs"
            }
            
            data_key = data_mapping.get(graph_type, "module_power")
            has_data = False
            plotted_freq_min = None
            plotted_freq_max = None
            
            # Plota cada teste selecionado
            for test_idx, test in enumerate(selected_tests):
                try:
                    fc_log("threshold", f"[INFO] Processando teste {test.get('id', 'N/A')} para {graph_type}")
                    
                    # Obtém dados do teste - estrutura correta
                    results = test.get('results', [])
                    
                    if not results:
                        fc_log("threshold", f"   [AVISO] Teste {test.get('id', 'N/A')} sem dados de resultados")
                        continue
                    
                    # Extrai frequências e valores dos resultados
                    frequencies = []
                    values = []
                    
                    fc_log("threshold", f'   [INFO] Analisando {len(results)} resultados...')
                    
                    for result_idx, result in enumerate(results):
                        if isinstance(result, dict):
                            
                            # Tenta diferentes campos para frequência - CORRIGIDO para freq_mhz
                            freq = result.get('freq_mhz', result.get('frequency', result.get('freq', result.get('f', None))))
                            if freq is None:
                                # Se não tem frequência, pula
                                continue
                                
                            try:
                                freq_val = float(freq)
                                frequencies.append(freq_val)
                                
                                # Obtém o valor baseado no tipo de gráfico - CHAVES CORRETAS
                                if data_key == "module_power":
                                    value = result.get('module_power', result.get('threshold_power', 25))
                                elif data_key == "module_rssi":
                                    value = result.get('rssi', result.get('threshold_rssi', -50))
                                elif data_key == "irradiated_power":
                                    value = result.get('irradiated_power', 25)
                                elif data_key == "backscatter":
                                    value = result.get('backscatter', -30)
                                elif data_key == "power_tag_forward":
                                    value = result.get('power_on_tag_forward', 25)
                                elif data_key == "power_tag_reverse":
                                    value = result.get('power_on_tag_reversed', 25)
                                elif data_key == "conversion_loss":
                                    value = result.get('conversion_loss', 0)
                                elif data_key == "link_forward":
                                    value = result.get('max_fcc_link_forward', 1.0)
                                elif data_key == "link_reverse":
                                    value = result.get('max_fcc_link_reversed', 1.0)
                                elif data_key == "rcs":
                                    value = result.get('rcs', -10)
                                else:
                                    value = result.get('threshold_power', 25)
                                
                                if value is not None:
                                    try:
                                        values.append(float(value))
                                    except (ValueError, TypeError):
                                        values.append(25.0)  # Valor padrão
                                        fc_log("threshold", f'   [AVISO] DEBUG: Valor inválido convertido para padrão: {value}')
                            except (ValueError, TypeError) as e:
                                fc_log("threshold", f'   [AVISO] DEBUG: Frequência inválida ignorada: {freq} - {e}')
                                continue
                        else:
                            fc_log("threshold", f'   [AVISO] DEBUG: Resultado {result_idx + 1} não é dict: {type(result)}')
                            continue
                    
                    fc_log("threshold", f'   Frequências: {(len(frequencies) if frequencies else 0)}')
                    fc_log("threshold", f'   Valores ({data_key}): {(len(values) if values else 0)}')
                    
                    # Verifica se tem dados básicos
                    if not frequencies:
                        fc_log("threshold", f"   [AVISO] Teste {test.get('id', 'N/A')} sem frequências válidas")
                        continue
                    
                    # Verifica se tamanhos são compatíveis
                    if len(frequencies) != len(values):
                        fc_log("threshold", f"   [AVISO] Teste {test.get('id', 'N/A')} tamanhos incompatíveis: freq={len(frequencies)}, values={len(values)}")
                        # Ajusta para o menor tamanho
                        min_size = min(len(frequencies), len(values))
                        frequencies = frequencies[:min_size]
                        values = values[:min_size]
                        fc_log("threshold", f'   [OK] Ajustado para {min_size} pontos')
                    
                    # Filtra dados válidos
                    valid_freqs = []
                    valid_values = []
                    
                    for freq, value in zip(frequencies, values):
                        if freq is not None and value is not None and freq > 0:
                            valid_freqs.append(freq)
                            valid_values.append(value)
                    
                    if not valid_freqs:
                        fc_log("threshold", f"   [AVISO] Teste {test.get('id', 'N/A')} sem dados válidos após filtro")
                        continue
                    
                    # Cor para este teste
                    color = colors[test_idx % len(colors)]
                    
                    # Label para legenda (não usado, mas mantido para compatibilidade)
                    test_label = f"{t('threshold.test')} {test.get('id', 'N/A')}"
                    if test.get('description'):
                        desc = test.get('description', '')[:20]
                        test_label += f" - {desc}"
                    
                    # Plota o gráfico (sem legenda)
                    plt.plot(valid_freqs, valid_values, 'o-', 
                            color=color, linewidth=2, markersize=4, 
                            markerfacecolor=color, markeredgecolor='white', 
                            markeredgewidth=1)

                    local_min = min(valid_freqs)
                    local_max = max(valid_freqs)
                    if plotted_freq_min is None or local_min < plotted_freq_min:
                        plotted_freq_min = local_min
                    if plotted_freq_max is None or local_max > plotted_freq_max:
                        plotted_freq_max = local_max

                    has_data = True
                    fc_log("threshold", f"   [OK] Teste {test.get('id', 'N/A')} plotado com {len(valid_freqs)} pontos")
                    
                except Exception as e:
                    fc_log("threshold", f"   [ERRO] Erro ao plotar teste {test.get('id', 'N/A')}: {e}")
                    import traceback
                    traceback.print_exc()
                    continue
            
            if not has_data:
                fc_log("threshold", f'[AVISO] Nenhum dado válido encontrado para {graph_type}')
                plt.close()
                return None
            
            # Configurações do gráfico
            plt.title(f'Threshold - {graph_type}', fontsize=14, fontweight='bold', color='#2c3e50')
            plt.xlabel('', fontsize=12)
            plt.ylabel('')  # Label do eixo Y removido
            
            # Configurações dos eixos
            plt.grid(True, alpha=0.3, linestyle='--')

            # Ajusta eixo X pelo intervalo real dos dados do(s) teste(s)
            if plotted_freq_min is not None and plotted_freq_max is not None:
                if plotted_freq_min == plotted_freq_max:
                    margin = max(0.5, plotted_freq_min * 0.002)
                else:
                    margin = max(0.5, (plotted_freq_max - plotted_freq_min) * 0.08)

                x_min = plotted_freq_min - margin
                x_max = plotted_freq_max + margin
                plt.xlim(x_min, x_max)

                # Faixas regulatórias apenas se sobrepõem ao intervalo mostrado.
                regulatory_bands = [
                    (865.0, 868.0, t('pdf.etsi_band')),
                    (902.0, 907.0, t('pdf.anatel_band_1')),
                    (915.5, 928.0, t('pdf.anatel_band_2')),
                ]
                for band_min, band_max, band_label in regulatory_bands:
                    if not (band_max < x_min or band_min > x_max):
                        plt.axvspan(band_min, band_max, color='lightblue', alpha=0.2, label=band_label)

            # Legendas removidas - não exibe mais legendas nos gráficos
            
            # Configurações de layout
            plt.tight_layout(pad=1.5)
            
            # Salva como string base64
            buffer = io.BytesIO()
            plt.savefig(buffer, format='png', dpi=150, facecolor='white', 
                       edgecolor='none', bbox_inches='tight')
            buffer.seek(0)
            
            # Converte para base64
            image_base64 = base64.b64encode(buffer.getvalue()).decode()
            buffer.close()
            plt.close()
            
            fc_log("threshold", f'[OK] Gráfico {graph_type} gerado com sucesso')
            return image_base64
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao gerar gráfico {graph_type}: {e}')
            import traceback
            traceback.print_exc()
            return None
        



    def setup_main_area(self):
        main_area = tk.Frame(self, bg='white')
        main_area.grid(row=0, column=1, sticky='nsew', padx=5, pady=5)
        main_area.grid_columnconfigure(0, weight=1)  # Gráficos (espaço completo)
        

        
        # DISTRIBUIÇÃO VERTICAL: Gráfico 1 (30%), Gráfico 2 (30%), Histórico (40%)
        main_area.grid_rowconfigure(0, weight=3)  # Gráfico 1: 30%
        main_area.grid_rowconfigure(1, weight=3)  # Gráfico 2: 30%
        main_area.grid_rowconfigure(2, weight=4)  # Histórico: 40%
        main_area.grid_propagate(False)
        
        # --- GRÁFICO 1 (30% do espaço vertical) ---
        graph1_frame = tk.Frame(main_area, bg='white')
        graph1_frame.grid(row=0, column=0, sticky='nsew', pady=5)
        graph1_frame.grid_columnconfigure(0, weight=1)
        graph1_frame.grid_rowconfigure(1, weight=1, minsize=140)
        # Permite que o grid gerencie o tamanho para distribuição correta
        graph1_frame.grid_propagate(True)
        
        # Gráfico 1 - Dropdown e Container
        graph1_dropdown_frame = tk.Frame(graph1_frame, bg='white')
        graph1_dropdown_frame.grid(row=0, column=0, sticky='ew', pady=0)
        graph1_dropdown_frame.grid_columnconfigure(0, weight=1)
        
        graph1_dropdown_frame = tk.Frame(graph1_frame, bg='white')
        graph1_dropdown_frame.grid(row=0, column=0, sticky='ew', pady=0)
        graph1_dropdown_frame.grid_columnconfigure(0, weight=1)
        
        # CORREÇÃO: Não sobrescreve o valor se já foi carregado da persistência
        if not hasattr(self, 'graph1_var'):
            self.graph1_var = tk.StringVar(value=t('threshold.graph_type_module_power'))
        
        # NOVO: Preserva o valor salvo se existir e normaliza para o idioma atual
        saved_graph1_value_raw = self.graph1_var.get() if hasattr(self, 'graph1_var') else t('threshold.graph_type_module_power')
        
        # Normaliza valor salvo para o idioma atual
        def normalize_saved_value(value):
            graph_type_values = self._get_graph_type_values()
            if value in graph_type_values:
                return value
            reverse_map = {v: k for k, v in self._get_graph_type_key_map().items()}
            return reverse_map.get(value, graph_type_values[0] if graph_type_values else t('threshold.graph_type_module_power'))
        
        saved_graph1_value = normalize_saved_value(saved_graph1_value_raw)
        
        graph_combobox_width = 22 if hasattr(self, 'small_screen') and self.small_screen else 25
        graph_type_values = self._get_graph_type_values()
        graph1_combobox = ttk.Combobox(graph1_dropdown_frame, textvariable=self.graph1_var, 
                                      values=graph_type_values, 
                                      state="readonly", width=graph_combobox_width)
        graph1_combobox.grid(row=0, column=0, sticky='w', padx=10, pady=0)
        self._widget_refs['graph1_combobox'] = graph1_combobox
        
        # Define o valor traduzido
        self.graph1_var.set(saved_graph1_value)
        
        graph1_combobox.bind('<<ComboboxSelected>>', self.on_graph1_type_changed)
        
        # Container para toolbar do gráfico 1 (lado direito)
        self.graph1_toolbar_container = tk.Frame(graph1_dropdown_frame, bg='white')
        self.graph1_toolbar_container.grid(row=0, column=1, sticky='e', padx=5, pady=0)
        
        # Container do gráfico 1: preenche a linha do grid (evita célula ~0px e canvas minúsculo)
        self.graph1_container = tk.Frame(graph1_frame, bg='white')
        self.graph1_container.grid(row=1, column=0, sticky='nsew', padx=0, pady=0)
        
        # --- GRÁFICO 2 (30% do espaço vertical) ---
        graph2_frame = tk.Frame(main_area, bg='white')
        graph2_frame.grid(row=1, column=0, sticky='nsew', pady=5)
        graph2_frame.grid_columnconfigure(0, weight=1)
        graph2_frame.grid_rowconfigure(1, weight=1, minsize=140)
        # Permite que o grid gerencie o tamanho para distribuição correta
        graph2_frame.grid_propagate(True)
        
        graph2_dropdown_frame = tk.Frame(graph2_frame, bg='white')
        graph2_dropdown_frame.grid(row=0, column=0, sticky='ew', pady=0)
        graph2_dropdown_frame.grid_columnconfigure(0, weight=1)
        
        # CORREÇÃO: Não sobrescreve o valor se já foi carregado da persistência
        if not hasattr(self, 'graph2_var'):
            self.graph2_var = tk.StringVar(value=t('threshold.graph_type_module_power'))
        
        # NOVO: Preserva o valor salvo se existir e normaliza para o idioma atual
        saved_graph2_value_raw = self.graph2_var.get() if hasattr(self, 'graph2_var') else t('threshold.graph_type_module_power')
        
        # Normaliza valor salvo para o idioma atual
        saved_graph2_value = normalize_saved_value(saved_graph2_value_raw)
        
        graph_type_values = self._get_graph_type_values()
        graph2_combobox = ttk.Combobox(graph2_dropdown_frame, textvariable=self.graph2_var, 
                                      values=graph_type_values, 
                                      state="readonly", width=graph_combobox_width)
        graph2_combobox.grid(row=0, column=0, sticky='w', padx=10, pady=0)
        self._widget_refs['graph2_combobox'] = graph2_combobox
        
        # Define o valor traduzido
        self.graph2_var.set(saved_graph2_value)
        
        graph2_combobox.bind('<<ComboboxSelected>>', self.on_graph2_type_changed)
        
        # Container para toolbar do gráfico 2 (lado direito)
        self.graph2_toolbar_container = tk.Frame(graph2_dropdown_frame, bg='white')
        self.graph2_toolbar_container.grid(row=0, column=1, sticky='e', padx=5, pady=0)
        
        # Container do gráfico 2: preenche a linha do grid
        self.graph2_container = tk.Frame(graph2_frame, bg='white')
        self.graph2_container.grid(row=1, column=0, sticky='nsew', padx=0, pady=0)

        # --- HISTÓRICO DE TESTES (40% do espaço vertical) ---
        data_frame = tk.LabelFrame(main_area, text=t('threshold.test_history'), labelanchor="n", bg='white')
        self._widget_refs['data_frame'] = data_frame
        data_frame.grid(row=2, column=0, sticky='nsew', pady=(5, 5))
        data_frame.grid_columnconfigure(0, weight=1); data_frame.grid_rowconfigure(1, weight=1)
        table_dropdown_frame = tk.Frame(data_frame, bg='white')
        table_dropdown_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
        table_dropdown_frame.grid_columnconfigure(0, weight=0)  # Não expande a coluna do dropdown
        table_dropdown_frame.grid_columnconfigure(1, weight=0)  # Não expande a coluna do contador
        

        
        self.table_action_var = tk.StringVar(value=t('threshold.select_an_action'))
        table_action_values = [
            t('threshold.table_select_all'),
            t('threshold.table_deselect_all'),
            t('threshold.table_statistical'),
            t('threshold.table_delete_selected'),
            t('threshold.table_export_excel')
        ]
        table_action_combobox = ttk.Combobox(table_dropdown_frame, textvariable=self.table_action_var, 
                                             values=table_action_values, state="readonly", width=28)
        table_action_combobox.grid(row=0, column=0, sticky='w', padx=5)
        table_action_combobox.bind('<<ComboboxSelected>>', self.on_table_action_selected)
        self._widget_refs['table_action_combobox'] = table_action_combobox
        
        # NOVO: Label do contador de testes no histórico
        self.history_counter_label = tk.Label(table_dropdown_frame, text=t('threshold.tests_count_zero'), 
                                            font=("Arial", 9), fg="gray", bg='white')
        self.history_counter_label.grid(row=0, column=1, sticky='w', padx=(5, 5))
        
        table_frame = tk.Frame(data_frame, bg='white')
        table_frame.grid(row=1, column=0, sticky='nsew', padx=5, pady=5)
        table_frame.grid_columnconfigure(0, weight=1); table_frame.grid_rowconfigure(0, weight=1)
        columns = ('Plot', 'Nome do Teste', 'EPC', 'Calibration', 'Atenuador', 'Distância', 'Date Time', 'Tempo')
        self.tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=4, selectmode="none")
        
        # Cabeçalhos clicáveis com indicadores de ordenação e edição
        self.tree.heading('Plot', text=f"{t('threshold.plot')} ↕", command=lambda: self.sort_treeview('Plot'), anchor='w')
        self.tree.heading('Nome do Teste', text=f"{t('threshold.test_name')} ↕", command=lambda: self.sort_treeview('Nome do Teste'), anchor='w')
        self.tree.heading('EPC', text=f"{t('threshold.epc')} ↕", command=lambda: self.sort_treeview('EPC'), anchor='w')
        self.tree.heading('Calibration', text=f"{t('threshold.calibration_file_col')} ↕", command=lambda: self.sort_treeview('Calibration'), anchor='w')
        self.tree.heading('Atenuador', text=f"{t('threshold.attenuator_col')} ↕", command=lambda: self.sort_treeview('Atenuador'), anchor='w')
        self.tree.heading('Distância', text=f"{t('threshold.distance_col')} ↕", command=lambda: self.sort_treeview('Distância'), anchor='w')
        self.tree.heading('Date Time', text=f"{t('threshold.date_time')} ↕", command=lambda: self.sort_treeview('Date Time'), anchor='w')
        self.tree.heading('Tempo', text=f"{t('threshold.time')} ↕", command=lambda: self.sort_treeview('Tempo'), anchor='w')
        
        self.tree.column('Plot', width=50, anchor='w'); self.tree.column('Nome do Teste', width=180, anchor='w'); self.tree.column('EPC', width=180, anchor='w'); self.tree.column('Calibration', width=140, anchor='w'); self.tree.column('Atenuador', width=80, anchor='w'); self.tree.column('Distância', width=80, anchor='w'); self.tree.column('Date Time', width=120, anchor='w'); self.tree.column('Tempo', width=80, anchor='w')
        tree_scrollbar = ttk.Scrollbar(table_frame, orient='vertical', command=self.tree.yview)
        self.tree.configure(yscrollcommand=tree_scrollbar.set)
        self.tree.grid(row=0, column=0, sticky='nsew'); tree_scrollbar.grid(row=0, column=1, sticky='ns')
        
        # Bind para cliques na tabela principal (apenas checkbox)
        self.tree.bind('<Button-1>', self.on_main_tree_click)
        self.tree.bind('<Button-3>', self.show_history_context_menu)  # Menu de contexto
        # Bind para duplo-clique para edição in-line da descrição
        self.tree.bind('<Double-1>', self.on_tree_double_click)

    def on_action_selected(self, event):
        selected_action = self.action_var.get()
        
        # Ignora se for o texto padrão (não é uma ação válida)
        if selected_action == t('threshold.select_an_action') or selected_action == "":
            return
        
        # Compara com valores traduzidos e originais para compatibilidade
        register_text = t('threshold.register_new_tags')
        import_text = t('threshold.import_tag_json')
        select_all_text = t('threshold.select_all_tags')
        deselect_all_text = t('threshold.deselect_all_tags')
        erase_text = t('threshold.erase_selected_tags')
        save_text = t('threshold.save_selected_tags')
        
        if selected_action == register_text or selected_action == "+ Register New Tags":
            if not HARDWARE_AVAILABLE:
                messagebox.showerror(t('threshold.error_hardware'), t('threshold.error_hardware_not_detected'))
                return
            popup = RegisterTagsPopup(self, self.database, self.perform_tag_scan, self.license_limits)
            self.wait_window(popup)
        elif selected_action == import_text or selected_action == "+ Import Tag from file JSON":
            self.import_tags_from_file()
        elif selected_action == select_all_text or selected_action == "+ Select All Tags":
            self.select_all_tags()
        elif selected_action == deselect_all_text or selected_action == "+ Deselect All Tags":
            self.deselect_all_tags()
        elif selected_action == erase_text or selected_action == "- Erase Selected Tags":
            self.erase_selected_tags()
        elif selected_action == save_text or selected_action == "> Save Selected Tags (JSON)":
            self.save_selected_tags_json()
        
        # Reseta para valor padrão traduzido
        self.action_var.set(t('threshold.select_an_action'))

    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('threshold.select_json_import'),
            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 = []
        existing_tags_to_select = []  # NOVO: Tags que já existem e serão selecionadas

        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                data = json.load(f)
                
                # DEBUG: Mostra o conteúdo do arquivo para diagnóstico
                fc_log("threshold", f'🔍 DEBUG: Conteúdo do arquivo JSON: {data}')             
                # Verifica se é uma lista de tags ou um objeto com tags
                if isinstance(data, list):
                    tags_data = data
                    fc_log("threshold", f'🔍 DEBUG: Arquivo contém lista direta com {len(tags_data)} itens')
                elif isinstance(data, dict) and 'tags' in data:
                    tags_data = data['tags']
                    fc_log("threshold", f"🔍 DEBUG: Arquivo contém objeto com propriedade 'tags' com {len(tags_data)} itens")
                else:
                    fc_log("threshold", f'🔍 DEBUG: Formato não reconhecido. Tipo: {type(data)}, Conteúdo: {data}')
                    messagebox.showerror(t('threshold.error'), t('threshold.error_invalid_json').format(type=type(data).__name__, content=str(data)[:200]))
                    return
                
                for i, tag_data in enumerate(tags_data):
                    fc_log("threshold", f'🔍 DEBUG: Processando tag {i + 1}: {tag_data}')                  
                    if not isinstance(tag_data, dict):
                        fc_log("threshold", f'❌ Erro: Tag deve ser um objeto JSON: {tag_data}')
                        error_count += 1
                        continue
                    
                    # CORREÇÃO: Aceita tanto 'epc' quanto 'EPC' (case insensitive)
                    epc = tag_data.get('epc', tag_data.get('EPC', '')).strip()
                    # CORREÇÃO: Aceita 'name', 'apelido', 'Name', 'Apelido' (case insensitive)
                    name = tag_data.get('name', tag_data.get('apelido', tag_data.get('Name', tag_data.get('Apelido', '')))).strip()
                    
                    fc_log("threshold", f"🔍 DEBUG: EPC extraído: '{epc}', Nome extraído: '{name}'")                
                    # CORREÇÃO: Permite tags sem nome (nome vazio)
                    if not epc:
                        fc_log("threshold", f"❌ Erro: Tag deve ter 'epc': {tag_data}")
                        error_count += 1
                        continue
                    
                    # Se não tem nome, usa o EPC como nome
                    if not name:
                        name = epc
                        fc_log("threshold", f"🔍 DEBUG: Nome vazio, usando EPC como nome: '{name}'")                  
                    # MODIFICADO: Se a tag já existe, adiciona para seleção automática
                    if self.database.get_tag_by_epc(epc):
                        skipped_count += 1
                        existing_tags_to_select.append(epc)  # Marca para seleção
                    else:
                        tags_to_import.append((epc, name))

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

        # Se há tags para importar, mostra popup para editar nomes
        if tags_to_import:
            self.show_import_edit_popup(tags_to_import, imported_count, skipped_count, error_count, existing_tags_to_select)
        else:
            # Nenhuma tag nova para importar, mas pode haver tags existentes para selecionar
            if existing_tags_to_select:
                # NOVO: Seleciona automaticamente as tags que já existem
                for epc in existing_tags_to_select:
                    self.selected_tags.add(epc)
                self.update_tags_list()
                
                summary_message = f"Importação Concluída!\n\n" \
                                  f"Tags novas importadas: {imported_count}\n" \
                                  f"Tags já existentes (selecionadas): {skipped_count}\n" \
                                  f"Linhas com erro no formato: {error_count}\n\n" \
                                  f"✅ {len(existing_tags_to_select)} tag(s) já existente(s) foi(ram) automaticamente selecionada(s) para uso!"
            else:
                summary_message = f"Importação Concluída!\n\n" \
                                  f"Tags importadas com sucesso: {imported_count}\n" \
                                  f"Tags já existentes: {skipped_count}\n" \
                                  f"Linhas com erro no formato: {error_count}"
            
            messagebox.showinfo(t('threshold.info_import_summary'), summary_message, parent=self)

    def show_import_edit_popup(self, tags_to_import, imported_count, skipped_count, error_count, existing_tags_to_select=[]):
        """Mostra popup para editar nomes das tags antes de importar"""
        popup = tk.Toplevel(self)
        popup.title(t('threshold.edit_tag_names_title'))
        popup.geometry("500x400")
        popup.transient(self)
        popup.grab_set()
        
        # Frame principal
        main_frame = ttk.Frame(popup, padding="10")
        main_frame.pack(fill="both", expand=True)
        
        ttk.Label(main_frame, text=t('threshold.edit_tag_names_prompt'), 
                 font=("Helvetica", 10, "bold")).pack(pady=(0, 10))
        
        # Frame para a tabela
        table_frame = ttk.Frame(main_frame)
        table_frame.pack(fill="both", expand=True)
        
        # Tabela para editar nomes
        columns = ('epc', 'name')
        tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=10)
        
        tree.heading('epc', text='EPC')
        tree.heading('name', text='Nome da Tag')
        
        tree.column('epc', width=200)
        tree.column('name', width=200)
        
        # Adiciona as tags na tabela
        for epc, name in tags_to_import:
            tree.insert('', 'end', values=(epc, name))
        
        # Scrollbar
        scrollbar = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
        tree.configure(yscrollcommand=scrollbar.set)
        
        tree.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        
        # Bind para editar nomes com duplo clique
        def edit_name(event):
            row_id = tree.identify_row(event.y)
            column = tree.identify_column(event.x)
            
            if not row_id or column != '#2':  # Apenas coluna do nome
                return
                
            current_values = list(tree.item(row_id, 'values'))
            epc = current_values[0]
            
            # Popup para editar nome
            edit_popup = tk.Toplevel(popup)
            edit_popup.title(t('threshold.edit_name_title'))
            edit_popup.geometry("300x120")
            edit_popup.transient(popup)
            edit_popup.grab_set()
            
            edit_frame = ttk.Frame(edit_popup, padding="20")
            edit_frame.pack(fill="both", expand=True)
            
            ttk.Label(edit_frame, text=f"{t('threshold.epc_label')} {epc}").pack()
            name_var = tk.StringVar(value=current_values[1])
            name_entry = ttk.Entry(edit_frame, textvariable=name_var, width=30)
            name_entry.pack(pady=5)
            name_entry.focus()
            
            def save_name():
                new_name = name_var.get().strip()
                current_values[1] = new_name
                tree.item(row_id, values=current_values)
                edit_popup.destroy()
            
            def cancel():
                edit_popup.destroy()
            
            button_frame = ttk.Frame(edit_frame)
            button_frame.pack(pady=(10, 0))
            ttk.Button(button_frame, text=t('threshold.save'), command=save_name).pack(side="left", padx=5)
            ttk.Button(button_frame, text=t('threshold.cancel'), command=cancel).pack(side="left", padx=5)
            
            name_entry.bind('<Return>', lambda e: save_name())
            name_entry.bind('<Escape>', lambda e: cancel())
        
        tree.bind('<Double-1>', edit_name)
        
        # Botões
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill="x", pady=(10, 0))
        
        def import_tags():
            # Importa as tags com os nomes editados
            final_imported = 0
            imported_epcs = []  # Lista para armazenar EPCs das tags importadas
            
            for item_id in tree.get_children():
                values = tree.item(item_id, 'values')
                epc, name = values[0], values[1].strip()
                
                # Se não tem nome, fica vazio (nulo)
                if not name:
                    name = ""
                
                if self.database.add_tag(epc, name, 0.0):
                    final_imported += 1
                    imported_epcs.append(epc)  # Adiciona EPC à lista de importadas
            
            # SELECIONA AUTOMATICAMENTE AS TAGS RECÉM-IMPORTADAS
            for epc in imported_epcs:
                self.selected_tags.add(epc)
            
            # NOVO: Também seleciona as tags que já existiam
            for epc in existing_tags_to_select:
                self.selected_tags.add(epc)
            
            # ATUALIZA A INTERFACE PARA MOSTRAR AS TAGS SELECIONADAS
            self.update_tags_list()
            
            total_selected = final_imported + len(existing_tags_to_select)
            summary_message = f"Importação Concluída!\n\n" \
                              f"Tags novas importadas: {final_imported}\n" \
                              f"Tags já existentes (selecionadas): {skipped_count}\n" \
                              f"Linhas com erro no formato: {error_count}\n\n" \
                              f"✅ {total_selected} tag(s) selecionada(s) e pronta(s) para uso!"
            
            messagebox.showinfo(t('threshold.info_import_summary'), summary_message, parent=self)
            popup.destroy()
        
        def cancel():
            """Fecha popup sem importar"""
            popup.destroy()
        
        ttk.Button(button_frame, text=t('threshold.import_tags'), command=import_tags).pack(side="left")
        ttk.Button(button_frame, text=t('threshold.cancel'), command=cancel).pack(side="right")
    
    def perform_tag_scan(self, power_dbm):
        with self.com_port_lock:
            if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) == 0:
                try:
                    # Configura potência com validação rigorosa
                    if not self._execute_rfid_command_with_validation(
                        RFID_CMD_SET_TXPOWER,
                        bytes([0, 0]) + int(power_dbm * 100).to_bytes(2, 'big') * 2,
                        6,
                        f"Configuração de potência {power_dbm}dBm para scan de tags"
                    ):
                        fc_log("threshold", f'[ERRO] Falha ao configurar potência para scan de tags')
                        return []
                    
                    # Delay adaptativo já aplicado pela validação de comandos
                    fc_log("threshold", f'[OK] Potência configurada para scan: {power_dbm}dBm')
                    found_tags = self._scan_for_tags_internal(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 _extract_epc_from_inv_response(self, raw_data: bytes, output_len_value: int):
        """Extrai EPC de resposta INV_TAG com tamanho variável (compatível com FastSurance)."""
        try:
            if not raw_data or output_len_value <= 5:
                return None
            epc_bytes = raw_data[2:output_len_value - 3]
            if not epc_bytes:
                return None
            return epc_bytes.hex().upper()
        except Exception:
            return None

    def _scan_for_tags_internal(self, duration_sec=1.0):
        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:
                try:
                    # Extrai EPC em tamanho variável (igual ao FastSurance)
                    epc = self._extract_epc_from_inv_response(out_buf.raw, out_len.value)
                    if not epc:
                        continue
                    
                    # Extrai RSSI usando posição relativa (como outros módulos)
                    rssi_bytes = out_buf.raw[out_len.value - 3:out_len.value - 1]
                    
                    # Usa o método dos outros módulos (struct.unpack)
                    rssi_raw = struct.unpack('>h', rssi_bytes)[0]
                    
                    # Aplica a mesma divisão do FastChecker: cast para int primeiro, depois divide por 10
                    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): 
                    continue
            # Delay adaptativo baseado na estabilização do hardware
            self._adaptive_delay_for_scan_operation()
        return list(tags_found.values())
        
    def update_tags_list(self):
        fc_log("threshold", f'🔍 DEBUG: update_tags_list chamada')
        # Limpa o treeview
        for item in self.tags_tree.get_children():
            self.tags_tree.delete(item)
        
        self.tags_data = []
        tags_from_db = self.database.get_tags()
        fc_log("threshold", f'🔍 DEBUG: Tags carregadas do banco: {len(tags_from_db)}')      
        if not tags_from_db:
            # Insere mensagem quando não há tags
            fc_log("threshold", f'🔍 DEBUG: Nenhuma tag no banco - mostrando mensagem')
            self.tags_tree.insert('', 'end', values=('', t('threshold.no_tags_registered')), tags=('empty',))
            return
        
        for tag in tags_from_db:
            epc = tag.get('epc', 'N/A')
            name = tag.get('name', '')
            fc_log("threshold", f'🔍 DEBUG: Adicionando tag à lista: {epc}')         
            # Lógica de exibição: mostra apenas o EPC na lista principal
            # O apelido é opcional e não aparece aqui para evitar duplicação
            display_tag = epc
            
            # Verifica se a tag está selecionada
            is_selected = epc in self.selected_tags
            checkbox = '☑' if is_selected else '☐'
            
            # Insere no treeview (apenas checkbox e tag)
            item_id = self.tags_tree.insert('', 'end', values=(checkbox, display_tag))
            
            # Armazena os dados da tag (EPC e nome para o banco)
            self.tags_data.append({'epc': epc, 'name': name, 'item_id': item_id})
        
        # Marca que o estado foi alterado
        self._mark_state_changed()

    def on_tree_click(self, event):
        """Callback para cliques no treeview"""
        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
                self.toggle_tag_selection(item)
    
    def toggle_tag_selection(self, item_id):
        """Alterna a seleção de uma tag"""
        values = self.tags_tree.item(item_id, 'values')
        no_tags_text = t('threshold.no_tags_registered')
        if not values or values[1] == no_tags_text or values[1] == '(Nenhuma tag registrada)':  # Item vazio (suporta valores antigos)
            return
        
        # Encontra o EPC da tag baseado no item_id
        epc = None
        for tag in self.tags_data:
            if tag.get('item_id') == item_id:
                epc = tag['epc']
                break
        
        if not epc:
            return
        
        current_checkbox = values[0]
        
        if current_checkbox == '☐':
            # Seleciona a tag
            self.selected_tags.add(epc)
            self.tags_tree.set(item_id, 'select', '☑')
        else:
            # Deseleciona a tag
            self.selected_tags.discard(epc)
            self.tags_tree.set(item_id, 'select', '☐')
    
    def select_all_tags(self):
        """Seleciona todas as tags"""
        self.selected_tags.clear()
        no_tags_text = t('threshold.no_tags_registered')
        for item in self.tags_tree.get_children():
            values = self.tags_tree.item(item, 'values')
            if values and values[1] != no_tags_text and values[1] != '(Nenhuma tag registrada)':  # Não é item vazio (suporta valores antigos)
                # Encontra o EPC baseado no item_id
                for tag in self.tags_data:
                    if tag.get('item_id') == item:
                        self.selected_tags.add(tag['epc'])
                        break
                self.tags_tree.set(item, 'select', '☑')
    
    def deselect_all_tags(self):
        """Deseleciona todas as tags"""
        self.selected_tags.clear()
        no_tags_text = t('threshold.no_tags_registered')
        for item in self.tags_tree.get_children():
            values = self.tags_tree.item(item, 'values')
            if values and values[1] != no_tags_text and values[1] != '(Nenhuma tag registrada)':  # Não é item vazio (suporta valores antigos)
                self.tags_tree.set(item, 'select', '☐')
    
    def erase_selected_tags(self):
        """Remove as tags selecionadas do banco de dados"""
        selected_tags = list(self.selected_tags)
        if not selected_tags:
            messagebox.showwarning(t('threshold.warning_no_selection'), t('threshold.warning_no_selection_msg'))
            return
        

        
        # Remove as tags do banco de dados
        removed_count = 0
        for epc in selected_tags:
            if self.database.delete_tag(epc):
                removed_count += 1
        
        # Limpa a seleção e atualiza a lista
        self.selected_tags.clear()
        self.update_tags_list()
        
        # Marca que o estado foi alterado
        self._mark_state_changed()
        
        # Mostra resultado
        count = len(selected_tags)
        if removed_count == count:
            messagebox.showinfo(t('threshold.success'), t('threshold.success_tags_removed').format(count=removed_count))
        else:
            messagebox.showwarning(t('threshold.warning_some_tags_not_removed'), t('threshold.warning_some_tags_not_removed_msg').format(removed=removed_count, total=count))
    
    def save_selected_tags_json(self):
        """Salva as tags selecionadas em arquivo JSON"""
        selected_tags = list(self.selected_tags)
        if not selected_tags:
            messagebox.showwarning(t('threshold.warning_no_selection'), t('threshold.warning_no_selection_save'))
            return
        
        # Abre diálogo para salvar arquivo
        filepath = filedialog.asksaveasfilename(
            title=t('threshold.save_tags_title'),
            defaultextension=".json",
            filetypes=(("JSON files", "*.json"), ("Todos os arquivos", "*.*"))
        )
        
        if not filepath:  # Usuário cancelou
            return
        
        try:
            # Prepara dados para JSON
            tags_data = []
            for epc in selected_tags:
                # Encontra o nome da tag no banco de dados
                tag_name = ""
                for tag in self.tags_data:
                    if tag['epc'] == epc:
                        tag_name = tag['name']
                        break
                
                # CORREÇÃO: Garante que sempre há um nome válido
                if not tag_name:
                    tag_name = epc
                
                tags_data.append({
                    "epc": epc,
                    "name": tag_name
                })
            
            # CORREÇÃO: Salva com estrutura mais robusta
            export_data = {
                "version": "1.0",
                "export_date": time.strftime("%Y-%m-%d %H:%M:%S"),
                "tags": tags_data
            }
            
            # Salva em formato JSON
            with open(filepath, 'w', encoding='utf-8') as f:
                json.dump(export_data, f, indent=4, ensure_ascii=False)
            
            messagebox.showinfo(t('threshold.success'), t('threshold.success_tags_saved').format(count=len(selected_tags), filepath=filepath))
            
        except Exception as e:
            messagebox.showerror(t('threshold.error'), t('threshold.error_saving_file').format(error=str(e)))
    
    def get_selected_tags(self):
        """Retorna as tags selecionadas"""
        return list(self.selected_tags)
    
    def show_history_context_menu(self, event):
        """Menu de contexto para a tabela de histórico"""
        item = self.tree.identify_row(event.y)
        if not item:
            return
        
        test = self._test_for_history_tree_item(item)
        
        if not test:
            return
        
        # Cria menu de contexto
        context_menu = tk.Menu(self, tearoff=0)
        context_menu.add_command(label="✏️ Editar Descrição", 
                               command=lambda: self.edit_test_description_inline(item))
        context_menu.add_separator()
        context_menu.add_command(label="[DATA] Selecionar para Tabela", 
                               command=lambda: self._select_test_from_menu(test, item))
        context_menu.add_command(label="[INFO] Ver Tabela Diretamente", 
                               command=lambda: self._show_table_directly(test))
        
        # Mostra o menu
        context_menu.post(event.x_root, event.y_root)
    
    def _select_test_from_menu(self, test, item):
        """Seleciona teste a partir do menu de contexto"""
        self.selected_test_for_table = test
        self._highlight_selected_test(item)
        
        # Habilita o botão de mostrar tabela
        if hasattr(self, 'show_table_button'):
            self.show_table_button.config(state='normal')
        
        fc_log("threshold", f"[OK] Teste selecionado via menu: {test.get('description', 'N/A')}")
        messagebox.showinfo(t('threshold.info_test_selected'), 
                          f"{t('threshold.test_selected')}: {test.get('description', 'N/A')}\n\n"
                          f"{t('threshold.test_selected_msg')}")
    
    def _show_table_directly(self, test):
        """Mostra a tabela diretamente a partir do menu"""
        fc_log("threshold", f"[INFO] DEBUG: Iniciando _show_table_directly para teste: {test.get('description', 'N/A')}")
        
        # Verifica se o teste tem resultados
        results = test.get('results', [])
        fc_log("threshold", f'[INFO] DEBUG: Resultados encontrados: {(len(results) if results else 0)}')
        
        if not results:
            messagebox.showwarning(t('threshold.no_results'), 
                                 t('threshold.no_results_msg').format(test_desc=test.get('description', 'N/A')))
            return
        
        # Verifica se o teste tem tag_info
        tag_info = test.get('tag_info', {})
        if not tag_info:
            tag_info = {
                'name': test.get('epc', 'N/A'),
                'epc': test.get('epc', 'N/A')
            }
        
        fc_log("threshold", f'[INFO] DEBUG: Tag info: {tag_info}')
        fc_log("threshold", f"[DATA] Mostrando tabela diretamente: {test.get('description', 'N/A')}")
        
        try:
            self.show_test_table(results, tag_info, test.get('description', ''))
            fc_log("threshold", f'[OK] Tabela mostrada com sucesso!')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao mostrar tabela: {e}')
            traceback.print_exc()
            messagebox.showerror(t('threshold.error'), t('threshold.error_showing_table').format(error=str(e)))

    def show_context_menu(self, event):
        item = self.tags_tree.identify_row(event.y)
        if not item:
            return
        
        values = self.tags_tree.item(item, 'values')
        no_tags_text = t('threshold.no_tags_registered')
        if not values or values[1] == no_tags_text or values[1] == '(Nenhuma tag registrada)':  # Item vazio (suporta valores antigos)
            return
        
        # Encontra os dados da tag baseado no item_id
        tag_data = None
        for tag in self.tags_data:
            if tag.get('item_id') == item:
                tag_data = tag
                break
        
        if not tag_data:
            return
        
        context_menu = tk.Menu(self, tearoff=0)
        context_menu.add_command(label="Rename", command=lambda: self.rename_tag(tag_data))
        context_menu.add_command(label="Export Tag", command=lambda: self.export_tag(tag_data))
        context_menu.add_command(label="Delete", command=lambda: self.delete_tag(tag_data))
        context_menu.post(event.x_root, event.y_root)

    def rename_tag(self, tag_data):
        popup = RenamePopup(self, self.database, tag_epc=tag_data['epc'], current_name=tag_data['name'])
        self.wait_window(popup)
        self.update_tags_list()
        self._mark_state_changed()

    def delete_tag(self, tag_data):
        popup = DeleteConfirmationPopup(self, epc=tag_data['epc'])
        self.wait_window(popup)
        if hasattr(popup, 'confirmed') and popup.confirmed:
            success = self.database.delete_tag(tag_data['epc'])
            if success:
                self.update_tags_list()
                self._mark_state_changed()
                messagebox.showinfo(t('threshold.success'), t('threshold.success_tag_deleted'))
            else:
                messagebox.showerror(t('threshold.error'), t('threshold.error_deleting_tag'))
    
    def export_tag(self, tag_data):
        ExportPopup(self, tag_data)

    # --- MÉTODOS PARA A SEÇÃO PROJETO (A SEREM IMPLEMENTADOS) ---
    def save_project(self):
        """Salva o projeto atual com todos os tags e testes - VERSÃO COM ESCOLHA DE LOCAL"""
        project_name = self.project_name_var.get().strip()
        client_name = self.client_name_var.get().strip()
        
        if not project_name:
            messagebox.showwarning(t('threshold.warning_project_name'), t('threshold.warning_project_name_msg'))
            return
        
        fc_log("threshold", f"[SAVE] Salvando projeto: '{project_name}' (Cliente: '{client_name}')")
        
        # Coleta todos os tags registrados
        all_tags = self.database.get_all_tags()
        fc_log("threshold", f'[DATA] Tags encontradas: {len(all_tags)}')
        
        # Coleta todos os testes do histórico
        all_tests = self.database.get_test_history()
        fc_log("threshold", f'[INFO] Testes encontrados: {len(all_tests)}')
        
        # Coleta dados do projeto atual
        project_data = {
            "name": project_name,
            "client": client_name,
            "date_created": datetime.now().isoformat(),
            "tags": all_tags,
            "test_history": all_tests,
            "settings": {
                "min_freq": self.min_freq_var.get(),
                "max_freq": self.max_freq_var.get(),
                "max_power": self.max_power_var.get(),
                "freq_step": self.freq_step_var.get(),
                "power_step": self.power_step_var.get(),
                "distance": self.distance_var.get(),
                "attenuator": self.attenuator_var.get()
            }
        }
        
        # Cria nome padrão do arquivo
        timestamp = datetime.now().strftime('%d.%m.%y_%H.%M.%S')
        default_filename = f"Threshold {timestamp}.json"
        
        # Dialog para escolher onde salvar
        filename = filedialog.asksaveasfilename(
            title=t('threshold.save_project_title'),
            defaultextension=".json",
            filetypes=[
                (t('export_popup.filetype_json'), "*.json"),
                (t('export_popup.filetype_all'), "*.*"),
            ],
            initialfile=default_filename,
            parent=self,
        )
        
        if not filename:
            fc_log("threshold", '[ERRO] Usuário cancelou o salvamento')
            return
        
        try:
            # Salva o projeto como arquivo JSON
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(project_data, f, indent=4, ensure_ascii=False)
            
            # Também salva no banco de dados interno (backup)
            self.database.save_project(project_name, project_data)
            
            self._mark_state_changed()
            
            messagebox.showinfo(t('threshold.success_project_saved'), 
                              f"{t('threshold.project')} '{project_name}' {t('threshold.saved')}!\n\n"
                              f"📁 {t('threshold.file')}: {filename}\n"
                              f"[DATA] {t('threshold.tags')}: {len(all_tags)}\n"
                              f"[INFO] {t('threshold.tests')}: {len(all_tests)}")
            fc_log("threshold", f'[OK] Projeto salvo: {filename}')
            fc_log("threshold", f'[OK] Backup no banco interno criado')
            
        except Exception as e:
            messagebox.showerror(t('threshold.error'), t('threshold.error_saving_project').format(error=str(e)))
            fc_log("threshold", f'[ERRO] Erro ao salvar projeto: {e}')

    def _clear_test_configuration_fields(self):
        """Esvazia os campos do bloco Configuração do Teste (frequências, potência, distância, atenuador, descrição)."""
        if hasattr(self, 'min_freq_var'):
            self.min_freq_var.set("")
        if hasattr(self, 'max_freq_var'):
            self.max_freq_var.set("")
        if hasattr(self, 'max_power_var'):
            self.max_power_var.set("")
        if hasattr(self, 'freq_step_var'):
            self.freq_step_var.set("1")
        if hasattr(self, 'power_step_var'):
            self.power_step_var.set("0.5")
        if hasattr(self, 'distance_var'):
            self.distance_var.set("")
        if hasattr(self, 'attenuator_var'):
            self.attenuator_var.set("")
        if hasattr(self, 'description_input_var'):
            self.description_input_var.set("")
        try:
            self._apply_calibration_distance_and_attenuator_if_active()
        except Exception:
            pass

    def delete_project(self):
        """Deleta o projeto atual e limpa todos os dados"""
        fc_log("threshold", f'[INFO] Threshold: delete_project chamado - license_limits: {self.license_limits}')
        project_name = self.project_name_var.get().strip()
        if not project_name:
            messagebox.showwarning(t('threshold.warning_no_project_selected_delete'), t('threshold.warning_no_project_selected_delete_msg'))
            return
        
        try:
            fc_log("threshold", f"🗑️ Deletando projeto: '{project_name}'")        
            # Deleta o projeto do banco
            if self.database.delete_project(project_name):
                fc_log("threshold", f'[OK] Projeto deletado do banco')
                
                # Limpa todos os tags
                tags_cleared = self.database.clear_all_tags()
                fc_log("threshold", f'[DATA] Tags limpas: {tags_cleared}')
                
                # Limpa todo o histórico de testes
                tests_cleared = self.database.clear_test_history()
                fc_log("threshold", f'[INFO] Testes limpos: {tests_cleared}')
                
                # Limpa os campos da interface
                self.project_name_var.set("")
                self.client_name_var.set("")
                
                # Limpa Test Configuration (mesmo estado inicial dos campos de texto)
                self._clear_test_configuration_fields()
                
                # Atualiza a interface
                self.update_tags_list()
                self.update_history_tree()
                self.update_graphs_from_history()
                
                self._mark_state_changed()
                
                messagebox.showinfo(t('threshold.success_project_deleted'), 
                                  f"{t('threshold.project')} '{project_name}' {t('threshold.deleted')}!\n\n"
                                  f"[DATA] {t('threshold.tags')}: {t('threshold.cleared')}\n"
                                  f"[INFO] {t('threshold.tests')}: {t('threshold.cleared')}\n"
                                  f"{t('threshold.test_config_cleared_line')}")
                fc_log("threshold", f'[OK] Projeto completamente limpo')
            else:
                messagebox.showerror(t('threshold.error'), t('threshold.error_deleting_project'))
                fc_log("threshold", f'[ERRO] Erro ao deletar projeto')
        except Exception as e:
            messagebox.showerror(t('threshold.error'), t('threshold.error_deleting_project_exception').format(error=str(e)))
            fc_log("threshold", f'[ERRO] Erro ao deletar projeto: {e}')

    def import_project(self):
        """Importa um projeto de arquivo - VERSÃO MELHORADA"""
        filename = filedialog.askopenfilename(
            title=t('threshold.import_project_title'),
            filetypes=[
                (t('export_popup.filetype_json'), "*.json"),
                (t('export_popup.filetype_all'), "*.*"),
            ],
            parent=self,
        )
        
        if not filename:
            return
        
        fc_log("threshold", f'📂 Importando projeto de: {filename}')      
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                project_data = json.load(f)
            
            # Valida estrutura do projeto
            if not isinstance(project_data, dict) or 'name' not in project_data:
                messagebox.showerror(t('threshold.error_invalid_file'), t('threshold.error_invalid_file_msg'))
                return
            
            project_name = project_data['name']
            client_name = project_data.get('client', '')
            date_created = project_data.get('date_created', 'Data desconhecida')
            
            fc_log("threshold", f"[DATA] Projeto: '{project_name}' (Cliente: '{client_name}')")
            fc_log("threshold", f'📅 Criado: {date_created}')          
            # Mostra informações detalhadas do projeto
            tags_count = len(project_data.get('tags', []))
            tests_count = len(project_data.get('test_history', []))
            
            info_msg = f"[DATA] PROJETO ENCONTRADO\n\n"
            info_msg += f"Nome: {project_name}\n"
            info_msg += f"Cliente: {client_name or 'Não especificado'}\n"
            info_msg += f"Criado: {date_created}\n"
            info_msg += f"Tags: {tags_count}\n"
            info_msg += f"Testes: {tests_count}\n\n"
            info_msg += f"📁 Arquivo: {filename}\n\n"
            info_msg += f"Deseja importar este projeto?\n"
            info_msg += f"(Esta ação irá ADICIONAR aos dados atuais)"
            
            # Confirma importação
            if not messagebox.askyesno(t('threshold.confirm_import'), info_msg):
                return
            
            # NOVA ABORDAGEM: ADICIONA aos dados existentes (não limpa)
            fc_log("threshold", '[INFO] Importando projeto - ADICIONANDO aos dados existentes')
            
            # Importa tags (evita duplicatas)
            tags_imported = 0
            imported_epcs = []  # Lista para armazenar EPCs das tags importadas
            existing_tags = self.database.get_all_tags()
            existing_epcs = [tag['epc'] for tag in existing_tags]
            
            if 'tags' in project_data:
                for tag in project_data['tags']:
                    # Verifica se a tag já existe
                    if tag['epc'] not in existing_epcs:
                        if self.database.add_tag(tag['epc'], tag['name'], tag.get('initial_rssi', 0)):
                            tags_imported += 1
                            imported_epcs.append(tag['epc'])
                            fc_log("threshold", f"[OK] Tag importada: {tag['name']} ({tag['epc']})")
                    else:
                        fc_log("threshold", f"[AVISO] Tag já existe: {tag['name']} ({tag['epc']})")
                fc_log("threshold", f'[DATA] Tags importadas: {tags_imported}')
            
            # Importa testes (evita duplicatas)
            tests_imported = 0
            if 'test_history' in project_data:
                fc_log("threshold", f"[INFO] Processando {len(project_data['test_history'])} testes do projeto...")
                for i, test in enumerate(project_data['test_history']):
                    fc_log("threshold", f"[INFO] Processando teste {i + 1}/{len(project_data['test_history'])}: {test.get('description', 'Sem descrição')}")
                    # Adiciona ao banco de dados (a função add_test_to_history já verifica duplicatas)
                    if self.database.add_test_to_history(test):
                        tests_imported += 1
                        fc_log("threshold", f"[OK] Teste importado: {test.get('description', 'Sem descrição')}")
                    else:
                        fc_log("threshold", f"[AVISO] Teste não importado (possível duplicata): {test.get('description', 'Sem descrição')}")
                fc_log("threshold", f"[INFO] Testes importados: {tests_imported}/{len(project_data['test_history'])}")
            
            # Atualiza configurações se disponíveis
            if 'settings' in project_data:
                settings = project_data['settings']
                self.min_freq_var.set(settings.get('min_freq', '935'))
                self.max_freq_var.set(settings.get('max_freq', '940'))
                self.max_power_var.set(settings.get('max_power', '25'))
                self.freq_step_var.set(
                    _normalize_threshold_freq_step_str(str(settings.get("freq_step", "5")))
                )
                self.power_step_var.set(
                    _normalize_threshold_power_step_str(str(settings.get("power_step", "")))
                )
                self.distance_var.set(settings.get('distance', '0.2'))
                self.attenuator_var.set(settings.get('attenuator', '0.0'))
                fc_log("threshold", f'⚙️ Configurações importadas')       
            # Atualiza nome do projeto
            self.project_name_var.set(project_name)
            self.client_name_var.set(client_name)
            
            # SELECIONA AUTOMATICAMENTE AS TAGS RECÉM-IMPORTADAS
            for epc in imported_epcs:
                self.selected_tags.add(epc)
            
            # Atualiza interface
            self.update_tags_list()
            self.update_history_tree()
            self.update_graphs_from_history()
            
            self._mark_state_changed()
            
            messagebox.showinfo(t('threshold.success_project_imported'), 
                              t('threshold.success_project_imported_msg').format(
                                  project_name=project_name,
                                  client_name=client_name or t('threshold.not_specified'),
                                  tags_imported=tags_imported,
                                  tests_imported=tests_imported,
                                  filename=filename))
            fc_log("threshold", f'[OK] Projeto importado com sucesso')
            
        except FileNotFoundError:
            messagebox.showerror(t('threshold.error'), t('threshold.error_file_not_found'))
            fc_log("threshold", f'[ERRO] Arquivo não encontrado: {filename}')
        except json.JSONDecodeError:
            messagebox.showerror(t('threshold.error'), t('threshold.error_invalid_json_file'))
            fc_log("threshold", f'[ERRO] JSON inválido: {filename}')
        except Exception as e:
            messagebox.showerror(t('threshold.error'), t('threshold.error_importing_project').format(error=str(e)))
            fc_log("threshold", f'[ERRO] Erro ao importar: {e}')
    
    def export_project(self):
        """Exporta o projeto atual para arquivo JSON"""
        project_name = self.project_name_var.get().strip()
        
        if not project_name:
            messagebox.showwarning(t('threshold.warning_no_project_selected_export'), t('threshold.warning_no_project_selected_export_msg'))
            return
        
        # Busca o projeto no banco de dados
        project_data = self.database.get_project_by_name(project_name)
        
        if not project_data:
            messagebox.showerror(t('threshold.error'), t('threshold.error_project_not_found').format(name=project_name))
            return
        
        # Cria nome do arquivo
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        default_filename = f"Threshold_{project_name}_{timestamp}.json"
        
        # Dialog para salvar arquivo
        filename = filedialog.asksaveasfilename(
            title=t('threshold.export_project_title'),
            defaultextension=".json",
            filetypes=[
                (t('export_popup.filetype_json'), "*.json"),
                (t('export_popup.filetype_all'), "*.*"),
            ],
            initialfile=default_filename,
            parent=self,
        )
        
        if not filename:
            return
        
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(project_data, f, indent=4, ensure_ascii=False)
            
            messagebox.showinfo(t('threshold.success_project_exported'), 
                              t('threshold.success_project_exported_msg').format(
                                  project_name=project_name,
                                  filename=filename,
                                  tags_count=len(project_data.get('tags', [])),
                                  tests_count=len(project_data.get('test_history', []))))
            fc_log("threshold", f'[OK] Projeto exportado: {filename}')
            
        except Exception as e:
            messagebox.showerror(t('threshold.error'), t('threshold.error_exporting_project').format(error=str(e)))
            fc_log("threshold", f'[ERRO] Erro ao exportar: {e}')
    
    # --- MÉTODOS DOS GRÁFICOS ---
    def initialize_graphs(self):
        """Inicializa os gráficos vazios - SEM PLOTAGEM"""
        if not MATPLOTLIB_OK:
            return
        
        # Cria os gráficos vazios (sem dados)
        self.create_empty_graph1()
        self.create_empty_graph2()
        
        # CORREÇÃO: Bind para atualizar gráficos quando o módulo é mostrado
        self.bind('<Map>', self._on_module_shown)
        fc_log("threshold", '[INFO] Threshold: Event bind <Map> configurado para atualizar gráficos')    

    
    def _on_module_shown(self, event):
        """Atualiza gráficos quando o módulo é mostrado/mapeado"""
        try:
            fc_log("threshold", '🔄 Threshold: Módulo foi mostrado - atualizando gráficos')
            self.after(100, self.update_graphs_from_history)
        except Exception as e:
            fc_log("threshold", f'⚠️ Erro ao atualizar gráficos ao mostrar módulo: {e}')
    
    def _force_graph_redraw(self):
        """Força redesenho completo dos gráficos - simula clique no botão home"""
        try:
            fc_log("threshold", '🔄 Threshold: Forçando redesenho completo dos gráficos...')
            
            # Atualiza gráficos do histórico (imediato: o código abaixo usa canvas1/2 na sequência)
            self.update_graphs_from_history(immediate=True)
            
            # Força redraw dos canvas com autoscale (como botão home faz)
            if hasattr(self, 'canvas1') and self.canvas1:
                if hasattr(self, 'ax1') and self.ax1:
                    self.ax1.relim()
                    self.ax1.autoscale_view()
                self.canvas1.draw()
                self.canvas1.flush_events()
                fc_log("threshold", '✅ Canvas 1 redesenhado')
            
            if hasattr(self, 'canvas2') and self.canvas2:
                if hasattr(self, 'ax2') and self.ax2:
                    self.ax2.relim()
                    self.ax2.autoscale_view()
                self.canvas2.draw()
                self.canvas2.flush_events()
                fc_log("threshold", '✅ Canvas 2 redesenhado')
                
        except Exception as e:
            fc_log("threshold", f'⚠️ Erro ao forçar redesenho dos gráficos: {e}')
    
    def get_y_data_for_type(self, graph_type):
        """Retorna os dados Y baseados no tipo de gráfico selecionado"""
        mapping = {
            "Module Power (dBm)": "module_power",
            "Module RSSI (dBm)": "module_rssi",
            "Irradiated Power (dBm)": "irradiated_power",
            "Backscatter (dBm)": "backscatter",
            "Power on Tag Forward (dBm)": "power_on_tag_forward",
            "Power on Tag Reversed (dBm)": "power_on_tag_reversed",
            "Conversion Loss (dBm)": "conversion_loss",
            "Max FCC Link Forward (m)": "link_forward",
            "Max FCC Link Reversed (m)": "link_reverse",
            "RCS (dBm²)": "rcs"
        }
        
        data_key = mapping.get(graph_type, "module_power")
        return self.graph_data.get(data_key, [])
    

    def _cancel_pending_graph_refresh(self):
        jid = getattr(self, '_graph_refresh_after_id', None)
        if jid is not None:
            try:
                self.after_cancel(jid)
            except Exception:
                pass
            self._graph_refresh_after_id = None

    def _teardown_mpl_graph(self, which: int):
        """Libera figura Matplotlib, desliga callbacks e destrói widgets (evita vazamento e callbacks órfãos)."""
        if not MATPLOTLIB_OK:
            return
        cid_attr = '_graph1_motion_cid' if which == 1 else '_graph2_motion_cid'
        canvas_attr = 'canvas1' if which == 1 else 'canvas2'
        fig_attr = 'fig1' if which == 1 else 'fig2'
        cont_attr = 'graph1_container' if which == 1 else 'graph2_container'
        tb_attr = 'graph1_toolbar_container' if which == 1 else 'graph2_toolbar_container'
        cid = getattr(self, cid_attr, None)
        canvas = getattr(self, canvas_attr, None)
        if cid is not None and canvas is not None:
            try:
                canvas.mpl_disconnect(cid)
            except Exception:
                pass
        setattr(self, cid_attr, None)
        fig = getattr(self, fig_attr, None)
        if fig is not None and plt_mpl is not None:
            try:
                plt_mpl.close(fig)
            except Exception:
                pass
        setattr(self, fig_attr, None)
        setattr(self, canvas_attr, None)
        if which == 1:
            if hasattr(self, 'pan_active_graph1'):
                self.pan_active_graph1 = False
            if getattr(self, 'pan_btn_graph1', None) is not None:
                try:
                    if self.pan_btn_graph1.winfo_exists():
                        self.pan_btn_graph1.config(relief=tk.RAISED, bg='SystemButtonFace')
                except tk.TclError:
                    pass
        else:
            if hasattr(self, 'pan_active_graph2'):
                self.pan_active_graph2 = False
            if getattr(self, 'pan_btn_graph2', None) is not None:
                try:
                    if self.pan_btn_graph2.winfo_exists():
                        self.pan_btn_graph2.config(relief=tk.RAISED, bg='SystemButtonFace')
                except tk.TclError:
                    pass
        for attr in (cont_attr, tb_attr):
            fr = getattr(self, attr, None)
            if fr is None:
                continue
            for w in list(fr.winfo_children()):
                try:
                    w.destroy()
                except tk.TclError:
                    pass

    def _flush_graph_refresh(self):
        self._graph_refresh_after_id = None
        try:
            if not self.winfo_exists():
                return
        except tk.TclError:
            return
        try:
            self._update_graphs_from_history_impl()
        except Exception as e:
            fc_log_exc("threshold", "Erro ao atualizar gráficos do histórico", e)

    
    def create_empty_graph1(self):
        """Cria o gráfico 1 VAZIO - TAMANHO FIXO"""
        if not MATPLOTLIB_OK:
            return
        
        self._teardown_mpl_graph(1)
        
        # Cria nova figura com tamanho FIXO
        self.fig1 = Figure(figsize=(6, 2.5), dpi=100)
        self.ax1 = self.fig1.add_subplot(111)
        
        # GRÁFICO VAZIO - SEM PLOTAGEM MAS COM POSIÇÃO FIXA
        self.ax1.grid(True, alpha=0.3)
        
        # Define limites fixos do eixo X - sempre 800-1000 MHz (independente da licença)
        self.ax1.set_xlim(800, 1000)
        
        # Define labels dos eixos
        self.ax1.set_xlabel('')
        self.ax1.set_ylabel('')  # Label do eixo Y removido
        
        # Define limites fixos do eixo Y baseado no tipo de gráfico
        graph_type = self.graph1_var.get()
        if "Module Power" in graph_type:
            self.ax1.set_ylim(0, 30)
        elif "RSSI" in graph_type:
            self.ax1.set_ylim(-80, -20)
        elif "Irradiated Power" in graph_type:
            self.ax1.set_ylim(-30, 30)
        elif "Backscatter" in graph_type:
            self.ax1.set_ylim(-80, -20)
        elif "Power on Tag Forward" in graph_type:
            self.ax1.set_ylim(-50, 30)
        elif "Power on Tag Reversed" in graph_type:
            self.ax1.set_ylim(-80, -20)
        elif "Conversion Loss" in graph_type:
            self.ax1.set_ylim(-40, 0)
        elif "Max FCC Link Forward" in graph_type:
            self.ax1.set_ylim(0, 30)
        elif "Max FCC Link Reversed" in graph_type:
            self.ax1.set_ylim(0, 50)
        elif "RCS" in graph_type:
            self.ax1.set_ylim(-80, -20)  # RCS em dBm²
        else:
            self.ax1.set_ylim(0, 30)  # Padrão
        
        # NOVO: Adiciona faixas azuis de frequência (como no Antenna Check)
        self.ax1.axvspan(865, 868, color='lightblue', alpha=0.3)
        self.ax1.axvspan(902, 907, color='lightblue', alpha=0.3)
        self.ax1.axvspan(915.5, 928, color='lightblue', alpha=0.3)
        
        # Cria tooltip annotation para o gráfico 1
        self.annot1 = self.ax1.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.annot1.set_visible(False)
        
        # Gráfico vazio sem texto indicativo
        
        # Cria o canvas com tamanho fixo
        self.canvas1 = FigureCanvasTkAgg(self.fig1, self.graph1_container)
        self.canvas1.draw()
        self.canvas1.get_tk_widget().pack(fill="both", expand=True)
        
        # Controles de zoom na parte superior esquerda do gráfico 1
        self._setup_zoom_controls_graph1(self.graph1_container)
        
        # Configura interatividade do tooltip
        self._graph1_motion_cid = self.canvas1.mpl_connect('motion_notify_event', self._on_hover_graph1)
        
        # SALVA OS LIMITES ORIGINAIS para reset zoom
        self.original_xlim_graph1 = self.ax1.get_xlim()
        self.original_ylim_graph1 = self.ax1.get_ylim()
        fc_log("threshold", f'[INFO] DEBUG: Limites originais salvos para Graph 1: X={self.original_xlim_graph1}, Y={self.original_ylim_graph1}')
        
        # Força redimensionamento para manter posição fixa
        self.graph1_container.update_idletasks()
        

    
    def create_empty_graph2(self):
        """Cria o gráfico 2 VAZIO - TAMANHO FIXO"""
        if not MATPLOTLIB_OK:
            return
        
        self._teardown_mpl_graph(2)
        
        # Cria nova figura com tamanho FIXO
        self.fig2 = Figure(figsize=(6, 2.5), dpi=100)
        self.ax2 = self.fig2.add_subplot(111)
        
        # GRÁFICO VAZIO - SEM PLOTAGEM MAS COM POSIÇÃO FIXA
        self.ax2.grid(True, alpha=0.3)
        
        # Define labels dos eixos
        self.ax2.set_xlabel('')
        self.ax2.set_ylabel('')  # Label do eixo Y removido
        graph_type = self.graph2_var.get()
        
        # Define limites fixos do eixo X - sempre 800-1000 MHz (independente da licença)
        self.ax2.set_xlim(800, 1000)
        # ANTI-FLASH: Escala fixa para Power on Tag Forward
        self.ax2.set_ylim(-80, 20)  # Escala fixa para Power on Tag Forward
        
        # NOVO: Adiciona faixas azuis de frequência (como no Antenna Check)
        self.ax2.axvspan(865, 868, color='lightblue', alpha=0.3)
        self.ax2.axvspan(902, 907, color='lightblue', alpha=0.3)
        self.ax2.axvspan(915.5, 928, color='lightblue', alpha=0.3)
        
        # Cria tooltip annotation para o gráfico 2
        self.annot2 = self.ax2.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.annot2.set_visible(False)
        
        # Gráfico vazio sem texto indicativo
        
        # Cria o canvas com tamanho fixo
        self.canvas2 = FigureCanvasTkAgg(self.fig2, self.graph2_container)
        self.canvas2.draw()
        self.canvas2.get_tk_widget().pack(fill="both", expand=True)
        
        # Controles de zoom na parte superior esquerda do gráfico 2
        self._setup_zoom_controls_graph2(self.graph2_container)
        
        # Configura interatividade do tooltip
        self._graph2_motion_cid = self.canvas2.mpl_connect('motion_notify_event', self._on_hover_graph2)
        
        # SALVA OS LIMITES ORIGINAIS para reset zoom
        self.original_xlim_graph2 = self.ax2.get_xlim()
        self.original_ylim_graph2 = self.ax2.get_ylim()
        fc_log("threshold", f'[INFO] DEBUG: Limites originais salvos para Graph 2: X={self.original_xlim_graph2}, Y={self.original_ylim_graph2}')
        
        # Força redimensionamento para manter posição fixa
        self.graph2_container.update_idletasks()
        

    
    def on_graph1_type_changed(self, event=None):
        """Callback quando o tipo do gráfico 1 é alterado"""
        fc_log("threshold", f'Dropdown Gráfico 1 mudou para: {self.graph1_var.get()}')
        # Atualiza gráficos com dados do histórico
        self.update_graphs_from_history()
        # Persistência imediata para não perder seleção ao trocar de aba
        try:
            if hasattr(self, '_save_current_state'):
                self._save_current_state()
            elif hasattr(self, 'state_persistence'):
                # Fallback: salva usando a API direta se existir
                self.state_persistence.save_state({
                    "interface_config": {
                        "graph1_type": self._normalize_graph_type(self.graph1_var.get()),
                        "graph2_type": self._normalize_graph_type(getattr(self, 'graph2_var', tk.StringVar(value=t('threshold.graph_type_module_power'))).get())
                    }
                })
        except Exception as e:
            fc_log("threshold", f'[AVISO] Falha ao salvar seleção do gráfico 1: {e}')  
    def on_graph2_type_changed(self, event=None):
        """Callback quando o tipo do gráfico 2 é alterado"""
        fc_log("threshold", f'Dropdown Gráfico 2 mudou para: {self.graph2_var.get()}')
        # Atualiza gráficos com dados do histórico
        self.update_graphs_from_history()
        # Persistência imediata para não perder seleção ao trocar de aba
        try:
            if hasattr(self, '_save_current_state'):
                self._save_current_state()
            elif hasattr(self, 'state_persistence'):
                # Fallback: salva usando a API direta se existir
                self.state_persistence.save_state({
                    "interface_config": {
                        "graph1_type": self._normalize_graph_type(getattr(self, 'graph1_var', tk.StringVar(value=t('threshold.graph_type_module_power'))).get()),
                        "graph2_type": self._normalize_graph_type(self.graph2_var.get())
                    }
                })
        except Exception as e:
            fc_log("threshold", f'[AVISO] Falha ao salvar seleção do gráfico 2: {e}')  
    def update_graphs_from_frequency_settings(self):
        """Atualiza os gráficos quando as configurações de frequência mudam"""
        # Verifica se há testes selecionados no histórico
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        
        if selected_tests:
            # Se há testes selecionados, atualiza com dados do histórico
            self.update_graphs_from_history()
        else:
            # Se não há testes selecionados, cria gráficos vazios
            self.create_empty_graph1()
            self.create_empty_graph2()
    
    def on_frequency_changed(self, event=None):
        """Callback quando as configurações de frequência são alteradas"""
        # Cancela a atualização anterior se ainda não foi executada
        if hasattr(self, '_freq_update_after_id'):
            self.after_cancel(self._freq_update_after_id)
        
        # Agenda uma nova atualização após 2 segundos de inatividade
        self._freq_update_after_id = self.after(2000, lambda: self.update_graphs_from_frequency_settings())
        
        # Marca que o estado foi alterado
        self._mark_state_changed()
    
    def on_frequency_max_changed(self, event=None):
        """Callback específico para frequência máxima - sem atualização automática"""
        # Só valida o valor, não atualiza gráficos automaticamente
        try:
            value = self.max_freq_var.get()
            if value:
                float(value)  # Valida se é número
                # Só atualiza se o valor for válido e diferente do anterior
                if hasattr(self, '_last_max_freq'):
                    if value != self._last_max_freq:
                        self._last_max_freq = value
                        # Agenda atualização com delay maior
                        if hasattr(self, '_max_freq_update_after_id'):
                            self.after_cancel(self._max_freq_update_after_id)
                        self._max_freq_update_after_id = self.after(3000, lambda: self.update_graphs_from_frequency_settings())
                        # Marca que o estado foi alterado
                        self._mark_state_changed()
                else:
                    self._last_max_freq = value
        except ValueError:
            pass  # Ignora valores inválidos durante digitação
    
    def _reset_test_state(self):
        """Reseta todas as variáveis de estado do teste para garantir início limpo"""
        fc_log("threshold", '[INFO] Resetando estado do teste...')
        
        # Reseta variáveis de controle de teste
        if hasattr(self, 'test_in_progress'):
            self.test_in_progress = False
        if hasattr(self, 'global_test_cancelled'):
            self.global_test_cancelled = False
        
        # CORREÇÃO: NÃO limpa current_test_id aqui para manter cor consistente
        # O current_test_id será limpo apenas quando um novo teste começar
        # if hasattr(self, 'current_test_id'):
        #     self.current_test_id = None
        if hasattr(self, 'current_test_description'):
            self.current_test_description = ""
        
        # Reseta variáveis de progresso
        if hasattr(self, 'measurement_count'):
            self.measurement_count = 0
        
        # Reseta variáveis de threshold
        if hasattr(self, 'previous_threshold'):
            self.previous_threshold = None
        
        # Reseta variáveis de frequência e potência
        if hasattr(self, 'current_freq_index'):
            self.current_freq_index = 0
        if hasattr(self, 'current_power_index'):
            self.current_power_index = 0
        
        # Reseta variáveis de resultado
        if hasattr(self, 'threshold_results'):
            self.threshold_results = []
        
        # CORREÇÃO CRÍTICA: Limpa dados de tempo real para evitar acúmulo
        if hasattr(self, 'realtime_points'):
            self.realtime_points = {'freqs': [], 'powers': [], 'rssis': [], 'timestamps': []}
            fc_log("threshold", '🧹 Dados de tempo real limpos no reset')      
        if hasattr(self, 'realtime_calculated_data'):
            self.realtime_calculated_data = []
            fc_log("threshold", '🧹 Dados calculados de tempo real limpos no reset')      
        # Limpa buffer de tempo real
        if hasattr(self, '_rt_buffer'):
            self._rt_buffer = []
            fc_log("threshold", '🧹 Buffer de tempo real limpo no reset')      
        fc_log("threshold", '[OK] Estado do teste resetado com sucesso')

    def _on_test_button_click(self):
        """Um único botão: se teste em execução cancela, senão inicia o teste."""
        if getattr(self, 'test_in_progress', False):
            self.cancel_test_action()
        else:
            self.start_test_action()

    def _set_test_button_idle(self):
        """Restaura o botão para estado Iniciar Teste (após fim ou cancelamento)."""
        if hasattr(self, 'start_button') and self.start_button.winfo_exists():
            self.start_button.config(state="normal", text=t('threshold.start_test'), command=self._on_test_button_click)

    def _set_test_button_running(self):
        """Configura o botão como Cancelar Teste (teste em execução)."""
        if hasattr(self, 'start_button') and self.start_button.winfo_exists():
            self.start_button.config(state="normal", text=t('threshold.cancel_test'), command=self._on_test_button_click)

    def start_test_action(self):
        """Executa o teste de threshold com Module Power e RSSI"""
        # Bloqueio em modo browser: não iniciar teste sem licença
        if not self.license_limits.get('is_licensed', False):
            messagebox.showwarning(t('threshold.warning_browser_mode'), t('threshold.warning_browser_mode_msg'))
            return
        # CORREÇÃO: Limpa current_test_id apenas quando um novo teste começar
        if hasattr(self, 'current_test_id'):
            self.current_test_id = None
            fc_log("threshold", '[INFO] Limpando current_test_id para novo teste')
        
        # Reseta o estado do teste para garantir início limpo
        self._reset_test_state()

        # NOVO: zera buffer de realtime para conectar pontos entre updates
        self._rt_buffer = []
        
        selected_tags = self.get_selected_tags()
        if not selected_tags:
            messagebox.showwarning(t('threshold.warning_no_selection_test'), t('threshold.warning_no_selection_test_msg'))
            return
        
        if len(selected_tags) > 1:
            messagebox.showwarning(t('threshold.warning_multiple_tags'), t('threshold.warning_multiple_tags_msg'))
            return
        
        # Obtém a tag selecionada
        selected_epc = list(selected_tags)[0]
        selected_tag_info = None
        for tag in self.tags_data:
            if tag['epc'] == selected_epc:
                selected_tag_info = tag
                break
        
        if not selected_tag_info:
            messagebox.showerror(t('threshold.error'), t('threshold.error_tag_not_found'))
            return
        
        # Valida parâmetros
        try:
            min_freq = self._parse_float(self.min_freq_var.get())
            max_freq = self._parse_float(self.max_freq_var.get())
            freq_step = self._parse_float(self.freq_step_var.get())
            if abs(freq_step - 0.5) < 1e-9:
                freq_step = 1.0
                self.freq_step_var.set("1")
            max_power = self._parse_float(self.max_power_var.get())
            power_step = self._parse_float(self.power_step_var.get())
            distance = self._parse_float(self.distance_var.get())
            attenuator = self._parse_attenuator_db_for_test()
        except ValueError:
            messagebox.showerror(t('threshold.error'), t('threshold.error_invalid_parameters'))
            return
        
        # VALIDAÇÃO DE FREQUÊNCIAS - LIMITES DA LICENÇA
        min_allowed = self.license_limits.get('min_freq', 800)
        max_allowed = self.license_limits.get('max_freq', 1000)
        
        # Valida frequência mínima
        if min_freq < min_allowed:
            messagebox.showerror(t('threshold.error_license_limit'), 
                               t('threshold.error_min_freq_below').format(freq=min_freq, min_allowed=min_allowed, max_allowed=max_allowed))
            return
        
        if min_freq > max_allowed:
            messagebox.showerror(t('threshold.error_license_limit'), 
                               t('threshold.error_min_freq_above').format(freq=min_freq, min_allowed=min_allowed, max_allowed=max_allowed))
            return
        
        # Valida frequência máxima
        if max_freq > max_allowed:
            messagebox.showerror(t('threshold.error_license_limit'), 
                               t('threshold.error_max_freq_above').format(freq=max_freq, min_allowed=min_allowed, max_allowed=max_allowed))
            return
        
        if max_freq < min_allowed:
            messagebox.showerror(t('threshold.error_license_limit'), 
                               t('threshold.error_max_freq_below').format(freq=max_freq, min_allowed=min_allowed, max_allowed=max_allowed))
            return
        
        # Valida lógica das frequências (mínima deve ser menor que máxima)
        if min_freq >= max_freq:
            messagebox.showerror(t('threshold.error_invalid_value'), 
                               t('threshold.error_freq_min_greater_max').format(min=min_freq, max=max_freq))
            return
        
        # VALIDAÇÃO DE POTÊNCIA - LIMITES DA LICENÇA
        min_power_allowed = self.license_limits.get('min_power', 5)
        max_power_allowed = self.license_limits.get('max_power', 25)
        
        # Valida potência máxima
        if max_power > max_power_allowed:
            messagebox.showerror(t('threshold.error_license_limit'), 
                               t('threshold.error_max_power_above').format(power=max_power, max_allowed=max_power_allowed))
            return
        
        if max_power < min_power_allowed:
            messagebox.showerror(t('threshold.error_license_limit'), 
                               t('threshold.error_max_power_below').format(power=max_power, min_allowed=min_power_allowed, max_allowed=max_power_allowed))
            return
        
        # VALIDAÇÃO OBRIGATÓRIA DA DESCRIÇÃO
        description = self.description_input_var.get().strip()
        if not description:
            messagebox.showerror(t('threshold.error_description_required'), 
                               t('threshold.error_description_required_msg'))
            return
        
        # VERIFICAÇÃO DE DESCRIÇÃO DUPLICADA
        existing_descriptions = [test.get('description', '') for test in self.test_history]
        if description in existing_descriptions:
            messagebox.showerror(t('threshold.error_description_duplicate'), 
                               t('threshold.error_description_duplicate_msg').format(description=description))
            return
        
        # Confirma início do teste
        tag_name = selected_tag_info['name'] or selected_tag_info['epc']
        confirm_message = (
            f"{t('threshold.confirm_start_title')}\n\n"
            f"Tag: {tag_name}\n"
            f"{t('threshold.epc_label')} {selected_epc}\n\n"
            f"Parâmetros:\n"
            f"  Frequência: {min_freq} - {max_freq} MHz (passo: {freq_step})\n"
            f"  Potência: {max_power} - 5 dBm (passo: {power_step}) - DECRESCENTE\n"
            f"  Distância: {distance} m\n"
            f"  Atenuador: {attenuator} dB\n"
            f"  Descrição: {description}\n\n"
            f"{t('threshold.confirm_start_msg')}"
        )
        

        
        # CORREÇÃO FINAL: Executa verificação pré-teste de segurança (EXATAMENTE como FastSurance)
        fc_log("threshold", '[LOCK] INICIANDO VERIFICAÇÃO PRÉ-TESTE DE SEGURANÇA...')     
        # Desabilita botão de start durante verificação
        self.start_button.config(state="disabled", text=t('threshold.verifying'))
        
        # Atualiza status de segurança
        if hasattr(self, 'safety_status_label') and self.safety_status_label:
            self.safety_status_label.config(text=t('threshold.verifying'), fg="orange")
        
        # Executa verificação em thread separada; resultado via queue para atualizar UI na main thread
        self._verification_start_time = time.time()
        verification_thread = threading.Thread(
            target=self._pre_test_safety_verification,
            args=(selected_tag_info, min_freq, max_freq, freq_step, max_power, power_step, distance, attenuator),
            daemon=True
        )
        verification_thread.start()
        self.after(200, self._poll_verification_result)

    VERIFICATION_TIMEOUT_SEC = 25

    def _poll_verification_result(self):
        """Na main thread: lê resultado da verificação na queue ou aplica timeout."""
        try:
            result = self._verification_result_queue.get_nowait()
        except queue.Empty:
            if time.time() - getattr(self, '_verification_start_time', 0) >= self.VERIFICATION_TIMEOUT_SEC:
                self._verification_failed(
                    "Verificação de segurança demorou demasiado. Verifique a ligação do hardware (porta COM, leitor RFID)."
                )
                return
            self.after(200, self._poll_verification_result)
            return
        kind = result[0]
        if kind == 'failed':
            self._verification_failed(result[1])
        else:
            # Só inicia o teste se o utilizador ainda estiver no módulo Threshold
            if self.app_shell and getattr(self.app_shell, 'current_screen_frame', None) is self:
                self._start_test_after_verification(*result[1])
            else:
                # Mudou de aba durante a verificação: restaura o botão e não inicia o teste
                try:
                    self._set_test_button_idle()
                    if hasattr(self, 'safety_status_label') and self.safety_status_label:
                        self.safety_status_label.config(text=t('threshold.verification_done_switch_back'), fg="orange")
                except Exception:
                    pass

    def cancel_test_action(self):
        """Cancela o teste em execução e limpa os gráficos"""
        if not hasattr(self, 'test_in_progress') or not self.test_in_progress:
            messagebox.showinfo(t('threshold.info_no_test_cancel'), t('threshold.info_no_test_cancel_msg'))
            return
        

        
        fc_log("threshold", '🛑 Cancelando teste em execução...')    
        # MARCA CANCELAMENTO GLOBAL PARA INTERROMPER EXECUÇÃO
        self.global_test_cancelled = True
        
        # FORÇA PARADA IMEDIATA DO TESTE E THREAD
        fc_log("threshold", '🛑 FORÇANDO PARADA IMEDIATA DO TESTE E THREAD...')     
        # Para o teste imediatamente
        self.test_in_progress = False
        self.is_test_running = False
        
        # FORÇA PARADA DA THREAD DE TESTE
        if self.active_test_thread and self.active_test_thread.is_alive():
            fc_log("threshold", '🛑 Forçando parada da thread de teste...')         
            # Marca cancelamento várias vezes para garantir
            self.global_test_cancelled = True
            
            # Aguarda um pouco para a thread processar o cancelamento
            self.active_test_thread.join(timeout=0.1)  # Timeout menor
            
            if self.active_test_thread.is_alive():
                fc_log("threshold", '[AVISO] Thread não parou naturalmente - forçando terminação')             
                # Força cancelamento mais agressivo
                self.global_test_cancelled = True
                self.test_in_progress = False
                self.is_test_running = False
                
                # Tenta novamente com timeout maior
                self.active_test_thread.join(timeout=1.0)
                
                if self.active_test_thread.is_alive():
                    fc_log("threshold", '🛑 THREAD AINDA ATIVA - implementando terminação forçada')
                    # Marca como daemon para terminar com o programa principal
                    self.active_test_thread.daemon = True
        
        # Marca o teste como cancelado (já feito acima, mas garantindo novamente)
        self.test_in_progress = False
        self.is_test_running = False
        
        # Fecha janela de progresso se existir
        try:
            if hasattr(self, '_progress_window') and self._progress_window is not None:
                if self._progress_window.winfo_exists():
                    fc_log("threshold", '🛑 Fechando janela de progresso ativa...')
                    self._progress_window.destroy()
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro ao fechar janela de progresso: {e}')
        finally:
            try:
                self._progress_window = None
            except Exception:
                pass

        fc_log("threshold", f'🛑 Estado do teste após cancelamento: test_in_progress={self.test_in_progress}, is_test_running={self.is_test_running}, global_test_cancelled={self.global_test_cancelled}')     
        # CORREÇÃO: Para o sistema de segurança após cancelar o teste
        if hasattr(self, '_stop_safety_monitoring'):
            try:
                self._stop_safety_monitoring()
                fc_log("threshold", '[OK] Sistema de segurança parado após cancelamento')
            except Exception as e:
                fc_log("threshold", f'[AVISO] Erro ao parar sistema de segurança: {e}')        
        # Limpa dados de tempo real
        if hasattr(self, 'realtime_points'):
            self.realtime_points = {'freqs': [], 'powers': [], 'rssis': [], 'timestamps': []}
            fc_log("threshold", '🧹 Dados de tempo real limpos')      
        # Limpa dados calculados
        if hasattr(self, 'realtime_calculated_data'):
            self.realtime_calculated_data = []
            fc_log("threshold", '🧹 Dados calculados limpos')      
        # CORREÇÃO: FORÇAR FECHAMENTO IMEDIATO DA JANELA DE REALTIME
        if hasattr(self, 'realtime_window') and self.realtime_window is not None:
            try:
                fc_log("threshold", '🛑 FORÇANDO FECHAMENTO IMEDIATO da janela de realtime...')             
                # Cancela TODOS os agendamentos possíveis em TODOS os widgets da janela
                def cancel_all_afters_recursive(widget):
                    """Cancela recursivamente todos os afters em todos os widgets"""
                    try:
                        # Cancela o after específico no widget atual
                        if hasattr(self, 'realtime_refresh_after_id') and self.realtime_refresh_after_id:
                            try:
                                widget.after_cancel(self.realtime_refresh_after_id)
                                fc_log("threshold", f'🛑 Cancelado agendamento no widget {type(widget).__name__}')
                            except:
                                pass
                        
                        # Recursivamente cancela em todos os filhos
                        for child in widget.winfo_children():
                            cancel_all_afters_recursive(child)
                    except:
                        pass
                
                try:
                    cancel_all_afters_recursive(self.realtime_window)
                except:
                    pass
                
                # Cancela agendamento no self também
                if hasattr(self, 'realtime_refresh_after_id') and self.realtime_refresh_after_id is not None:
                    try:
                        self.after_cancel(self.realtime_refresh_after_id)
                        fc_log("threshold", '🛑 Cancelado agendamento no self')
                    except:
                        pass
                
                # MEDIDA EXTRA: Cancela TODOS os afters pendentes possíveis
                # (caso haja algum ID que não estamos rastreando)
                try:
                    # Tenta cancelar uma faixa de IDs possíveis
                    for i in range(1, 1000):  # Cancela até 1000 possíveis IDs
                        try:
                            self.after_cancel(f"after#{i}")
                        except:
                            pass
                    fc_log("threshold", '🛑 Varredura de cancelamento de afters executada')
                except:
                    pass
                
                # Desabilita atualização automática
                if hasattr(self, 'realtime_auto_refresh_var') and self.realtime_auto_refresh_var is not None:
                    try:
                        self.realtime_auto_refresh_var.set(False)
                        fc_log("threshold", '🛑 Atualização automática desabilitada')
                    except:
                        pass
                
                # FORÇA FECHAMENTO IMEDIATO
                try:
                    self.realtime_window.grab_release()  # Libera o grab modal
                except:
                    pass
                
                try:
                    self.realtime_window.destroy()       # Fecha a janela
                    fc_log("threshold", '[OK] Janela de realtime DESTRUÍDA')
                except:
                    pass
                
                # Limpa TODAS as referências
                self.realtime_window = None
                self.realtime_auto_refresh_var = None
                self.realtime_refresh_after_id = None
                
                fc_log("threshold", '[OK] FECHAMENTO FORÇADO da janela de realtime CONCLUÍDO')               
            except Exception as e:
                fc_log("threshold", f'[AVISO] Erro durante fechamento forçado: {e}')
                # Força limpeza mesmo com erro
                self.realtime_window = None
                self.realtime_auto_refresh_var = None
                self.realtime_refresh_after_id = None
        
        # Limpa gráficos (remove linhas de teste atual)
        self._clear_realtime_graphs()
        
        # Atualiza interface
        self.update_graphs_from_history()
        
        # Botão único: volta para Iniciar Teste
        self._set_test_button_idle()
        
        # Notifica fim do teste
        self._notify_test_end()
        
        # Reseta o estado do teste após cancelamento
        self._reset_test_state()
        
        messagebox.showinfo(t('threshold.cancel_success_title'), t('threshold.cancel_success_msg'))
        
        self.after(100, self._set_test_button_idle)
        
        fc_log("threshold", '[OK] Teste cancelado - módulo pronto para novo teste')    
    def _clear_realtime_graphs(self):
        """Remove linhas de teste em tempo real dos gráficos"""
        try:
            # Limpa gráfico 1
            if hasattr(self, 'fig1') and self.fig1 and self.fig1.axes:
                self.ax1 = self.fig1.axes[0]
                # Remove linhas de teste atual
                for line in self.ax1.lines[:]:
                    if hasattr(line, '_realtime_test') and line._realtime_test:
                        line.remove()
                self.canvas1.draw()
                fc_log("threshold", '🧹 Gráfico 1 limpo')         
            # Limpa gráfico 2
            if hasattr(self, 'fig2') and self.fig2 and self.fig2.axes:
                self.ax2 = self.fig2.axes[0]
                # Remove linhas de teste atual
                for line in self.ax2.lines[:]:
                    if hasattr(line, '_realtime_test') and line._realtime_test:
                        line.remove()
                self.canvas2.draw()
                fc_log("threshold", '🧹 Gráfico 2 limpo')             
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro ao limpar gráficos: {e}')    
    def _sleep_with_cancellation_check(self, seconds):
        """Executa sleep com verificação de cancelamento a cada 0.1 segundos"""
        if seconds <= 0:
            return
        
        # Divide o sleep em pequenos intervalos para verificação de cancelamento
        interval = 0.1
        remaining = seconds
        
        while remaining > 0:
            if self.global_test_cancelled:
                fc_log("threshold", '🛑 Sleep interrompido por cancelamento')
                return
            time.sleep(min(interval, remaining))
            remaining -= interval

    def _adaptive_sleep_with_stability_check(self, base_delay: float, operation_type: str = "default"):
        """Implementa sleep adaptativo baseado na estabilidade do hardware.
        
        ARGS:
            base_delay: Delay base para a operação
            operation_type: Tipo de operação (power, frequency, scan, etc.)
        """
        try:
            # CORREÇÃO: Delay fixo simples para evitar travamento
            if operation_type == "power":
                delay = base_delay * 1.5  # Potência precisa de mais tempo
            elif operation_type == "frequency":
                delay = base_delay * 1.2  # Frequência precisa de tempo médio
            elif operation_type == "scan":
                delay = base_delay * 0.8  # Scan pode ser mais rápido
            else:
                delay = base_delay
            
            # Aplica delay fixo
            self._sleep_with_cancellation_check(delay)
                
        except Exception as e:
            # Fallback para delay padrão em caso de erro
            self._sleep_with_cancellation_check(base_delay)
    
    def _increase_power_and_retry_rssi(self, freq, power, epc, max_power=25.0):
        """Aumenta potência em 1.0 dBm e repete medição RSSI quando há falha
        
        OBJETIVO: Implementar aumento automático de potência em caso de falha RSSI
        MÉTODO: Incrementa potência em 1.0 dBm até conseguir leitura ou atingir limite
        RETORNO: RSSI válido ou None se não conseguir mesmo com potência máxima
        """
        if not HARDWARE_AVAILABLE:
            return None
        
        # OTIMIZAÇÃO #4: Constantes locais otimizadas
        POWER_INCREASE_STEP = 1.0
        POWER_INCREASE_MAX_ATTEMPTS = 3  # Reduzido de 10 para 3 tentativas
        POWER_INCREASE_DELAY = 0.1
        
        current_power = power
        attempt = 0
        
        while attempt < POWER_INCREASE_MAX_ATTEMPTS and current_power <= max_power:
            if self.global_test_cancelled:
                return None
            
            attempt += 1
            fc_log("threshold", f'[INFO] Aumento de potência - Tentativa {attempt}: {current_power:.1f} dBm')            
            # PROTEÇÃO DE POTÊNCIA REFLETIDA: Verifica DURANTE o teste
            fc_log("threshold", f'[INFO] Threshold (Durante Teste): Verificando potência refletida em {current_power:.1f} dBm...')
            reflected_power = self._get_vswr_worker(current_power)
            if reflected_power is not None:
                fc_log("threshold", f'[INFO] Threshold (Durante Teste): Potência Refletida = {reflected_power:.1f} dBm (Limite: {REFLECTED_POWER_LIMIT} dBm)')
                if reflected_power > REFLECTED_POWER_LIMIT:
                    error_msg = (f"TESTE INTERROMPIDO!\n\n"
                               f"Potência Refletida ({reflected_power:.1f} dBm) excedeu o limite de {REFLECTED_POWER_LIMIT} dBm "
                               f"durante o teste em {current_power:.1f} dBm.\n\n"
                               f"Verifique o sistema irradiante (antena).")
                    fc_log("threshold", f'[ERRO] Threshold: {error_msg}')
                    self.global_test_cancelled = True
                    self._stopped_by_reflected_protection = True
                    return None
                else:
                    fc_log("threshold", f'[OK] Threshold (Durante Teste): Potência Refletida OK em {current_power:.1f} dBm')            
            # Tenta leitura RSSI com potência atual (sem recursão)
            rssi_result = self._check_rssi_stability_no_power_increase(freq, current_power, epc)
            
            if rssi_result is not None:
                fc_log("threshold", f'[OK] RSSI obtido com sucesso: {rssi_result:.1f} dBm (potência: {current_power:.1f} dBm)')
                return rssi_result
            
            # Aumenta potência para próxima tentativa
            current_power += POWER_INCREASE_STEP
            
            if current_power > max_power:
                fc_log("threshold", f'[AVISO] Limite máximo de potência atingido: {max_power} dBm')
                break
            
            # Aguarda estabilização após aumento de potência
            time.sleep(POWER_INCREASE_DELAY)
        
        fc_log("threshold", f'[ERRO] Falha na leitura RSSI mesmo com aumento de potência até {current_power:.1f} dBm')
        return None

    def _check_rssi_stability_no_power_increase(self, freq, power, epc):
        """Verifica estabilidade do RSSI sem aumento de potência (para evitar recursão)
        
        OBJETIVO: Garantir que o RSSI está estável antes de aceitar como válido
        MÉTODO: Múltiplas leituras com verificação de variação (sem aumento de potência)
        RETORNO: RSSI estável ou None se não conseguir estabilizar
        """
        if not HARDWARE_AVAILABLE:
            return None
        
        try:
            output_buffer = ctypes.create_string_buffer(256)
            output_len = ctypes.c_uint(0)
            
            # Configura potência e frequência com validação rigorosa
            if not self._execute_rfid_command_with_validation(
                RFID_CMD_SET_TXPOWER,
                bytes([0x00, 0x00]) + int(power * 100).to_bytes(2, 'big') * 2,
                6,
                f"Configuração de potência {power}dBm para estabilização RSSI"
            ):
                fc_log("threshold", '[ERRO] Falha ao configurar potência para estabilização RSSI')
                return None
            
            # CORREÇÃO: Não reconfigura frequência - já foi configurada pela função principal
            
            rssi_readings = []
            stable_count = 0
            
            # MODIFICAÇÃO 3: Loop com timeout de 2.0 segundos (aumentado de 0.5s para maior persistência)
            TIMEOUT_SEGUNDOS = 2.0
            start_time = time.time()
            attempt = 0
            
            while time.time() - start_time < TIMEOUT_SEGUNDOS:
                if self.global_test_cancelled:
                    return None
                
                attempt += 1
                
                # Executa inventário para leitura RSSI
                inv_tag_input_data = bytes([0x00, 0x64])  # 100ms de inventário
                result = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, 
                                              ctypes.c_char_p(inv_tag_input_data), 2, 
                                              output_buffer, ctypes.byref(output_len))
                
                if result == 0:
                    # Extrai EPC e verifica se é a tag esperada
                    detected_epc = self._extract_epc_from_inv_response(output_buffer.raw, output_len.value)
                    if not detected_epc:
                        continue
                    
                    if epc and epc.upper() == detected_epc:
                        # Extrai RSSI
                        rssi_bytes = output_buffer.raw[output_len.value - 3:output_len.value - 1]
                        rssi_raw = struct.unpack('>h', rssi_bytes)[0]
                        rssi_value = float(int(rssi_raw)) / 10.0
                        
                        rssi_readings.append(rssi_value)
                        fc_log("threshold", f'[RF] Estabilização RSSI - Tentativa {attempt}: {rssi_value:.1f}dBm')                       
                        # Verifica estabilidade após ter leituras suficientes
                        if len(rssi_readings) >= RSSI_STABILITY_READINGS:
                            # Calcula variação entre as últimas leituras
                            recent_readings = rssi_readings[-RSSI_STABILITY_READINGS:]
                            rssi_variation = max(recent_readings) - min(recent_readings)
                            
                            fc_log("threshold", f'[INFO] Variação RSSI: {rssi_variation:.2f}dBm (limite: {RSSI_STABILITY_THRESHOLD:.1f}dBm)')                           
                            if rssi_variation <= RSSI_STABILITY_THRESHOLD:
                                stable_count += 1
                                if stable_count >= 1:  # OTIMIZAÇÃO #2: Aceita 1 leitura estável (reduzido de 2)
                                    # Calcula média das leituras estáveis
                                    stable_readings = rssi_readings[-RSSI_STABILITY_READINGS:]
                                    avg_rssi = sum(stable_readings) / len(stable_readings)
                                    
                                    # Remove outliers antes da média final
                                    filtered_readings = [r for r in stable_readings 
                                                        if abs(r - avg_rssi) <= RSSI_OUTLIER_THRESHOLD]
                                    
                                    if filtered_readings:
                                        final_rssi = sum(filtered_readings) / len(filtered_readings)
                                        fc_log("threshold", f'[OK] RSSI estabilizado: {final_rssi:.1f}dBm (média de {len(filtered_readings)} leituras)')
                                        return final_rssi
                                    else:
                                        fc_log("threshold", f'[AVISO] Todas as leituras consideradas outliers')
                                        return avg_rssi  # Usa média original se todas forem outliers
                            else:
                                stable_count = 0  # Reset contador se não estável
                        else:
                            stable_count = 0  # Reset contador se não tem leituras suficientes
                    else:
                        fc_log("threshold", f'[AVISO] Tag diferente detectada durante estabilização: {detected_epc}')               
                # Aguarda estabilização antes da próxima tentativa
                time.sleep(RSSI_STABILITY_DELAY)
            
            fc_log("threshold", f'[AVISO] Timeout na estabilização RSSI após {TIMEOUT_SEGUNDOS}s')
            return None
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro na estabilização RSSI: {e}')
            return None

    def _check_rssi_stability(self, freq, power, epc):
        """Verifica estabilidade do RSSI antes de aceitar medição
        
        OBJETIVO: Garantir que o RSSI está estável antes de aceitar como válido
        MÉTODO: Múltiplas leituras com verificação de variação
        RETORNO: RSSI estável ou None se não conseguir estabilizar
        """
        if not HARDWARE_AVAILABLE:
            return None
        
        try:
            output_buffer = ctypes.create_string_buffer(256)
            output_len = ctypes.c_uint(0)
            
            # Configura potência e frequência com validação rigorosa
            if not self._execute_rfid_command_with_validation(
                RFID_CMD_SET_TXPOWER,
                bytes([0x00, 0x00]) + int(power * 100).to_bytes(2, 'big') * 2,
                6,
                f"Configuração de potência {power}dBm para estabilização RSSI"
            ):
                fc_log("threshold", '[ERRO] Falha ao configurar potência para estabilização RSSI')
                return None
            
            # CORREÇÃO: Não reconfigura frequência - já foi configurada pela função principal
            
            rssi_readings = []
            stable_count = 0
            
            # MODIFICAÇÃO 3: Loop com timeout de 2.0 segundos (aumentado de 0.5s para maior persistência)
            TIMEOUT_SEGUNDOS = 2.0
            start_time = time.time()
            attempt = 0
            
            while time.time() - start_time < TIMEOUT_SEGUNDOS:
                if self.global_test_cancelled:
                    return None
                
                attempt += 1
                
                # Executa inventário para leitura RSSI
                inv_tag_input_data = bytes([0x00, 0x64])  # 100ms de inventário (como EPC_RSSI)
                result = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, 
                                              ctypes.c_char_p(inv_tag_input_data), 2, 
                                              output_buffer, ctypes.byref(output_len))
                
                if result == 0:
                    # Extrai EPC e verifica se é a tag esperada
                    detected_epc = self._extract_epc_from_inv_response(output_buffer.raw, output_len.value)
                    if not detected_epc:
                        continue
                    
                    if epc and epc.upper() == detected_epc:
                        # Extrai RSSI
                        rssi_bytes = output_buffer.raw[output_len.value - 3:output_len.value - 1]
                        rssi_raw = struct.unpack('>h', rssi_bytes)[0]
                        rssi_value = float(int(rssi_raw)) / 10.0
                        
                        rssi_readings.append(rssi_value)
                        fc_log("threshold", f'[RF] Estabilização RSSI - Tentativa {attempt}: {rssi_value:.1f}dBm')                       
                        # Verifica estabilidade após ter leituras suficientes
                        if len(rssi_readings) >= RSSI_STABILITY_READINGS:
                            # Calcula variação entre as últimas leituras
                            recent_readings = rssi_readings[-RSSI_STABILITY_READINGS:]
                            rssi_variation = max(recent_readings) - min(recent_readings)
                            
                            fc_log("threshold", f'[INFO] Variação RSSI: {rssi_variation:.2f}dBm (limite: {RSSI_STABILITY_THRESHOLD:.1f}dBm)')                           
                            if rssi_variation <= RSSI_STABILITY_THRESHOLD:
                                stable_count += 1
                                if stable_count >= 1:  # OTIMIZAÇÃO #2: Aceita 1 leitura estável (reduzido de 2)
                                    # Calcula média das leituras estáveis
                                    stable_readings = rssi_readings[-RSSI_STABILITY_READINGS:]
                                    avg_rssi = sum(stable_readings) / len(stable_readings)
                                    
                                    # Remove outliers antes da média final
                                    filtered_readings = [r for r in stable_readings 
                                                        if abs(r - avg_rssi) <= RSSI_OUTLIER_THRESHOLD]
                                    
                                    if filtered_readings:
                                        final_rssi = sum(filtered_readings) / len(filtered_readings)
                                        fc_log("threshold", f'[OK] RSSI estabilizado: {final_rssi:.1f}dBm (média de {len(filtered_readings)} leituras)')
                                        return final_rssi
                                    else:
                                        fc_log("threshold", f'[AVISO] Todas as leituras consideradas outliers')
                                        return avg_rssi  # Usa média original se todas forem outliers
                            else:
                                stable_count = 0  # Reset contador se não estável
                        else:
                            stable_count = 0  # Reset contador se não tem leituras suficientes
                    else:
                        fc_log("threshold", f'[AVISO] Tag diferente detectada durante estabilização: {detected_epc}')
                        # NOVO: Continua tentando até encontrar a tag correta (como EPC_RSSI)
                
                # Aguarda estabilização antes da próxima tentativa
                self._sleep_with_cancellation_check(RSSI_STABILITY_DELAY)
                if self.global_test_cancelled:
                    return None
            
            # Se chegou aqui, não conseguiu estabilizar
            if rssi_readings:
                # Retorna média das leituras como fallback
                avg_rssi = sum(rssi_readings) / len(rssi_readings)
                fc_log("threshold", f'[AVISO] RSSI não estabilizou - usando média: {avg_rssi:.1f}dBm')
                return avg_rssi
            
            fc_log("threshold", '[ERRO] Não foi possível obter leituras RSSI para estabilização')
            return None
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro na verificação de estabilidade RSSI: {e}')
            return None

    def _apply_power_and_frequency(self, freq: float, power: float) -> bool:
        """Configura potência e frequência DIRETAMENTE como FastSurance - COM validação de faixas excluídas!
        
        OTIMIZAÇÃO CRÍTICA: Método direto e rápido para máxima performance
        """
        try:
            # CORREÇÃO CRÍTICA: Valida frequência contra faixas excluídas ANTES de configurar hardware
            if not self._validate_frequency_against_license(freq):
                fc_log("threshold", f'[ERRO] Threshold: Frequência {freq} MHz está em faixa proibida - NÃO configurando hardware')
                return False
            
            output_buffer = ctypes.create_string_buffer(64)
            output_len = ctypes.c_uint(0)

            # MÉTODO DIRETO DO FASTSURANCE - SEM RETRIES DESNECESSÁRIOS
            
            # 1. Configura potência diretamente
            power_val = int(power * 100)
            power_data = bytes([0x00, 0x00]) + power_val.to_bytes(2, 'big') * 2
            result_power = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, output_buffer, ctypes.byref(output_len))
            
            # 2. Configura frequência diretamente  
            freq_data = b'\x01' + int(freq * 1000).to_bytes(3, 'big')
            result_freq = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_FREQ_TABLE, ctypes.c_char_p(freq_data), 4, output_buffer, ctypes.byref(output_len))
            
            # 3. Delay mínimo como FastSurance (20ms vs. 100ms anterior)
            time.sleep(0.02)
            
            # Verifica se comandos executaram (sem retry complexo)
            if result_power == 0 and result_freq == 0:
                return True
            else:
                fc_log("threshold", f'[AVISO] Comando falhou: power={result_power}, freq={result_freq}')
                return False

        except Exception as e:
            fc_log("threshold", f'[ERRO] Exceção em _apply_power_and_frequency: {e}')
            return False

    def _execute_rfid_command_with_validation(self, command: int, data: bytes, data_len: int, description: str) -> bool:
        """OTIMIZAÇÃO #3: Função simplificada - executa comando direto sem validações complexas
        
        ANTES: 3 tentativas + delays + validações rigorosas (até 540ms por comando)
        DEPOIS: 1 tentativa direta (até 20ms por comando)
        """
        try:
            output_buffer = ctypes.create_string_buffer(64)
            output_len = ctypes.c_uint(0)
            
            if self.global_test_cancelled:
                return False
            
            # OTIMIZAÇÃO #3: Executa comando direto sem retries complexos
            result = rfid_sdk.UHF_RFID_Set(
                command,
                ctypes.c_char_p(data),
                data_len,
                output_buffer,
                ctypes.byref(output_len)
            )
            
            # Verificação simples - apenas se executou com sucesso
            if result == 0:
                # Delay mínimo como FastSurance (20ms vs. 100ms anterior)
                time.sleep(0.02)
                return True
            else:
                fc_log("threshold", f'[AVISO] {description} - Falha com status {result}')
                return False
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Exceção em _execute_rfid_command_with_validation: {e}')
            return False

    def _adaptive_delay_for_hardware_stability(self, command: int, description: str):
        """Implementa delay adaptativo baseado na estabilização do hardware.
        
        ARGS:
            command: Comando RFID executado
            description: description: Descrição para logs
            
        O delay é ajustado dinamicamente baseado na resposta do hardware.
        """
        try:
            # CORREÇÃO: Delay fixo simples para evitar travamento
            if command == RFID_CMD_SET_TXPOWER:
                delay = 0.15  # 150ms para potência
            elif command == RFID_CMD_SET_FREQ_TABLE:
                delay = 0.1   # 100ms para frequência
            else:
                delay = 0.05  # 50ms para outros comandos
            
            fc_log("threshold", f'[TIMER] Aguardando estabilização após {description} ({delay * 1000:.0f}ms)...')
            self._sleep_with_cancellation_check(delay)
            fc_log("threshold", f'[OK] Estabilização concluída após {delay * 1000:.0f}ms')         
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro no delay adaptativo: {e}')
            # Fallback para delay mínimo
            self._sleep_with_cancellation_check(ADAPTIVE_DELAY_MIN)

    def _check_hardware_stability(self, command: int) -> bool:
        """Verifica se o hardware está estável após um comando.
        
        ARGS:
            command: Comando RFID executado
            
        RETURNS:
            True se hardware estável, False caso contrário
        """
        try:
            # CORREÇÃO: Retorna True sempre para evitar loops infinitos
            # Em versões futuras, pode implementar verificação real
            return True
                
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro na verificação de estabilidade: {e}')
            return False

    def _execute_software_reset(self, enable_beep=True):
        """Executa software reset no reader para gerar bip e limpar estado.
        
        Args:
            enable_beep (bool): Se True, executa reset com bip. Se False, pula o reset.
        
        RETURNS:
            True se reset executado com sucesso (ou pulado), False caso contrário
        """
        if not enable_beep:
            fc_log("threshold", '[INFO] Reset desabilitado - conexao direta sem bip')
            return True
        
        if not HARDWARE_AVAILABLE or not rfid_sdk:
            fc_log("threshold", '[AVISO] Hardware nao disponivel - reset nao executado')
            return False
            
        try:
            # Abre conexão com o reader
            if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) != 0:
                fc_log("threshold", '[AVISO] Nao foi possivel conectar ao reader para reset')
                return False
            
            try:
                dummy_buffer = ctypes.create_string_buffer(32)
                dummy_len = ctypes.c_uint(0)
                
                # Envia comando de Software Reset
                # pInBuf=None, inlen=0 (não precisa de dados de entrada)
                status = rfid_sdk.UHF_RFID_Set(
                    RFID_CMD_SET_SOFTRESET, 
                    None,  # pInBuf é NULO
                    0,     # inlen é 0
                    dummy_buffer, 
                    ctypes.byref(dummy_len)
                )
                
                if status == 0:
                    fc_log("threshold", '[OK] Software Reset executado com sucesso!')
                    fc_log("threshold", '[OK] Bip emitido no buzzer do reader!')
                    return True
                else:
                    fc_log("threshold", f'[AVISO] Falha ao executar Software Reset - status: {status}')
                    return False
                    
            finally:
                # Fecha conexão
                rfid_sdk.UHF_RFID_Close(self.com_port)
                
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro ao executar Software Reset: {e}')
            return False

    def _quantize_half_db(self, value):
        """Arredonda para o múltiplo de 0,5 dBm mais próximo (ROUND_HALF_UP).

        - Exemplos: 12.7→13.0; 12.3→12.5; 12.2→12.0; -12.3→-12.5; -12.2→-12.0
        - Empates (x.25, x.75) arredondam para cima (longe de zero).
        """
        try:
            from decimal import Decimal, getcontext, ROUND_HALF_UP
            getcontext().prec = 10
            step = Decimal('0.5')
            q = (Decimal(str(value)) / step).to_integral_value(rounding=ROUND_HALF_UP)
            return float(q * step)
        except Exception:
            # Fallback numérico: aproximação próxima (pode divergir em negativos half-way)
            return round(value * 2) / 2.0

    def _adaptive_delay_for_scan_operation(self):
        """Implementa delay adaptativo para operações de scan de tags.
        
        O delay é ajustado baseado na estabilidade do sinal RFID.
        """
        try:
            # CORREÇÃO: Delay fixo simples para evitar travamento
            scan_delay = 0.02  # 20ms fixo para scan
            self._sleep_with_cancellation_check(scan_delay)
            
        except Exception as e:
            # Fallback para delay padrão em caso de erro
            self._sleep_with_cancellation_check(ADAPTIVE_DELAY_MIN)
    
    def _force_test_cancellation(self):
        """Força o cancelamento imediato do teste"""
        if self.global_test_cancelled:
            fc_log("threshold", '🛑 FORÇANDO SAÍDA IMEDIATA DO TESTE')
            # Força saída do método atual
            raise TestCancelledException("Teste cancelado pelo usuário")

    def _schedule_threshold_not_found_popup(self, below_min_range, freq_mhz, min_power, max_power_param):
        """Aviso no thread principal: execute_threshold_test corre numa worker thread."""
        def show():
            try:
                title = t('threshold.threshold_not_found_title')
                if below_min_range:
                    body = t('threshold.threshold_not_found_below_msg').format(
                        freq=f"{float(freq_mhz):.1f}",
                        min_power=f"{float(min_power):.1f}",
                    )
                else:
                    body = t('threshold.threshold_not_found_above_msg').format(
                        freq=f"{float(freq_mhz):.1f}",
                        max_power=f"{float(max_power_param):.1f}",
                    )
                messagebox.showwarning(title, body)
            except Exception as e:
                fc_log("threshold", f"[AVISO] Popup threshold não encontrado: {e}")
        try:
            self.after(0, show)
        except Exception:
            show()

    def _schedule_threshold_partial_skip_summary(self, skipped_freqs_mhz):
        """Aviso único no fim do ensaio: houve frequências sem threshold, mas o ensaio completou-se com pontos válidos."""
        if not skipped_freqs_mhz:
            return

        def show():
            try:
                uniq = sorted({float(f) for f in skipped_freqs_mhz})
                count = len(uniq)
                parts = [f"{x:.1f}" for x in uniq[:25]]
                if count > 25:
                    parts.append("…")
                freq_list = ", ".join(parts)
                title = t("threshold.threshold_partial_summary_title")
                body = t("threshold.threshold_partial_summary_msg").format(
                    count=count,
                    freq_list=freq_list,
                )
                messagebox.showinfo(title, body)
            except Exception as e:
                fc_log("threshold", f"[AVISO] Popup resumo lacunas threshold: {e}")

        try:
            self.after(0, show)
        except Exception:
            show()
    
    def execute_threshold_test(self, selected_tag_info, min_freq, max_freq, freq_step, max_power, power_step, distance, attenuator):
        """Executa o teste de threshold real - VERSÃO OTIMIZADA PARA PERFORMANCE
        
        OTIMIZAÇÕES IMPLEMENTADAS:
        1. Detecção rápida de tags SEM medição RSSI para medições normais
        2. Medição RSSI precisa (3 tentativas) APENAS para determinação do threshold
        3. Retorno para potência anterior para medição RSSI precisa
        4. Redução de 10 para 3 tentativas na medição RSSI
        5. Redução de pausas entre tentativas (0.1s → 0.05s)
        6. NOVA: Porta COM aberta uma vez durante todo o teste (elimina 1.575 aberturas/fechamentos)
        
        REDUÇÃO ESPERADA DE TEMPO: 80-85% (de ~14 min para ~2-3 min)
        MELHORIA ESPERADA DE REPETITIBILIDADE: Eliminação de instabilidades por múltiplas aberturas/fechamentos
        """
        
        # Reset da flag para evitar salvamento quando houver falha na potência máxima
        self._abort_save_due_to_max_power_failure = False
        self._abort_save_due_to_threshold_not_found = False

        # ANTI-FLASH: Marca que um teste está em andamento
        self.test_in_progress = True
        self.global_test_cancelled = False  # RESETA FLAG DE CANCELAMENTO
        self._stopped_by_reflected_protection = False
        fc_log("threshold", '[START] Teste iniciado - plotagem em tempo real ativada')
        
        # --- MEDIÇÃO DE TEMPO DE TESTE ---
        test_start_time = time.time()
        test_start_datetime = datetime.now()
        fc_log("threshold", f"[TIME] Teste iniciado em: {test_start_datetime.strftime('%d-%m-%Y %H:%M:%S')}")
        
        # --- VALIDAÇÃO DE LICENÇA ---
        is_valid, message = self._validate_license_limits(min_freq, max_power)
        if not is_valid:
            messagebox.showerror(t('threshold.error_license_limit'), t('threshold.error_license_limit_msg').format(message=message))
            return []
        
        # Reset do reader desabilitado (sem bip)
        fc_log("threshold", '[INFO] Reset desabilitado - conexao direta sem bip sonoro')
        if self._execute_software_reset(enable_beep=False):
            fc_log("threshold", '[OK] Conexao estabelecida sem bip!')
        else:
            fc_log("threshold", '[AVISO] Falha ao preparar conexao - continuando teste...')
        
        # --- NOTIFICA INÍCIO DO TESTE ---
        self._notify_test_start()
        
        # Cria janela de progresso NÃO-MODAL para permitir atualizações
        progress_window = tk.Toplevel(self)
        # Guarda referência para permitir cancelamento externo via botão principal
        self._progress_window = progress_window
        progress_window.title(t('threshold.progress_title'))
        progress_window.geometry("500x400")
        progress_window.transient(self)
        # progress_window.grab_set() removido (bloqueava atualizações)
        
        # Mantém a janela sempre no topo sem bloquear
        progress_window.attributes('-topmost', True)
        
        # Configura interface de progresso (mantém o código existente)
        progress_frame = tk.Frame(progress_window)
        progress_frame.pack(fill='both', expand=True, padx=20, pady=20)
        
        tk.Label(progress_frame, text=f"Testando: {selected_tag_info['name'] or selected_tag_info['epc']}", 
                font=("Arial", 12, "bold")).pack(pady=(0, 10))
        
        # Barra de progresso
        progress_var = tk.DoubleVar()
        progress_bar = ttk.Progressbar(progress_frame, variable=progress_var, 
                                     maximum=100, length=400)
        progress_bar.pack(pady=10)
        
        # Labels de status
        status_label = tk.Label(progress_frame, text=t('threshold.status_starting'), font=("Arial", 10))
        status_label.pack(pady=5)
        
        detail_label = tk.Label(progress_frame, text="", font=("Arial", 9))
        detail_label.pack(pady=5)
        
        # Label de estatísticas em tempo real
        stats_label = tk.Label(progress_frame, text=t('threshold.stats_waiting'), 
                              font=("Arial", 9), fg="blue")
        stats_label.pack(pady=5)
        
        # Tabela de resultados em tempo real (código existente mantido)
        results_frame = tk.Frame(progress_frame)
        results_frame.pack(fill='both', expand=True, pady=10)
        
        col_freq = t('threshold.col_freq_mhz')
        col_power = t('threshold.col_module_power_dbm')
        col_rssi = t('threshold.col_rssi_dbm')
        tk.Label(results_frame, text=t('threshold.realtime_results'), font=("Arial", 9, "bold")).pack()
        
        columns = (col_freq, col_power, col_rssi)
        results_tree = ttk.Treeview(results_frame, columns=columns, show='headings', height=10)
        
        results_tree.heading(col_freq, text=col_freq)
        results_tree.heading(col_power, text=col_power)
        results_tree.heading(col_rssi, text=col_rssi)
        
        results_tree.column(col_freq, width=150, anchor='center')
        results_tree.column(col_power, width=150, anchor='center')
        results_tree.column(col_rssi, width=150, anchor='center')
        
        results_scrollbar = ttk.Scrollbar(results_frame, orient='vertical', command=results_tree.yview)
        results_tree.configure(yscrollcommand=results_scrollbar.set)
        
        results_tree.pack(side='left', fill='both', expand=True)
        results_scrollbar.pack(side='right', fill='y')
        
        results_tree.tag_configure('threshold', background='#90EE90')
        
        # Lista para armazenar resultados
        threshold_results = []
        skipped_no_threshold_freqs = []  # Frequências onde não foi possível determinar threshold (ensaio continua)
        measurement_count = 0
        previous_threshold = None
        
        # Reseta variáveis de controle para garantir início limpo
        fc_log("threshold", '[INFO] Resetando variáveis de controle do teste...')
        self.previous_threshold = None
        self.current_freq_index = 0
        self.current_power_index = 0
        fc_log("threshold", '[OK] Variáveis de controle resetadas')        
        # Variável para controlar cancelamento
        test_cancelled = False
        
        # --- Adquirir porta e abrir (evita "Não foi possível abrir COM" após verificação) ---
        fc_log("threshold", '[HW] Adquirindo porta COM para o teste...')
        port_acquired = False
        com_opened = False
        try:
            if hasattr(self, 'port_manager') and self.port_manager:
                if not self.port_manager.acquire_port("Threshold", timeout=3.0):
                    messagebox.showerror(t('threshold.error_hardware_connect'), t('threshold.error_hardware_connect_msg').format(port=self.com_port))
                    return []
                port_acquired = True
            if not HARDWARE_AVAILABLE or not rfid_sdk:
                if port_acquired and hasattr(self, 'port_manager'):
                    self.port_manager.release_port("Threshold")
                return []
            try:
                rfid_sdk.UHF_RFID_Close(self.com_port)
            except Exception:
                pass
            time.sleep(0.25)
            open_result = rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE)
            if open_result != 0:
                time.sleep(0.35)
                open_result = rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE)
            if open_result != 0:
                if port_acquired and hasattr(self, 'port_manager'):
                    self.port_manager.release_port("Threshold")
                messagebox.showerror(t('threshold.error_hardware_connect'), t('threshold.error_hardware_connect_msg').format(port=self.com_port))
                return []
            com_opened = True
            fc_log("threshold", '[OK] Porta COM aberta com sucesso - mantendo aberta durante todo o teste')
        except Exception as e:
            if port_acquired and hasattr(self, 'port_manager'):
                try:
                    self.port_manager.release_port("Threshold")
                except Exception:
                    pass
            fc_log("threshold", f'[ERRO] Exceção ao abrir porta COM: {e}')
            messagebox.showerror(t('threshold.error_hardware_init'), t('threshold.error_hardware_init_msg').format(error=str(e)))
            return []
        
        # --- MODIFICAÇÃO 2: PAUSA DE AQUECIMENTO ---
        fc_log("threshold", '🔥 Aquecendo e estabilizando comunicação com o leitor...')
        time.sleep(0.5)
        
        # Atualiza painel Safety (temperatura e VSWR) no início do teste
        self._update_safety_displays_from_test(HEATING_POWER_DBM)
        
        # CORREÇÃO: Verificação inicial de potência máxima removida
        # Esta verificação estava causando aborto desnecessário quando o teste iniciava em frequências problemáticas
        # O teste deve começar diretamente sem verificação prévia de potência máxima
        fc_log("threshold", f'[START] INICIANDO TESTE: Pulando verificação inicial de potência máxima para evitar abortos desnecessários')    
        def cancel_test():
            nonlocal test_cancelled
            test_cancelled = True
            try:
                if progress_window and progress_window.winfo_exists():
                    progress_window.destroy()
            except Exception:
                pass
            finally:
                try:
                    self._progress_window = None
                except Exception:
                    pass
        
        # Botão de cancelar
        cancel_button = tk.Button(progress_frame, text=t('threshold.cancel_test'), command=cancel_test, 
                                bg='red', fg='white')
        cancel_button.pack(pady=5)
        
        try:
            # Calcula total de medições para barra de progresso
            total_frequencies = len(np.arange(min_freq, max_freq + freq_step, freq_step))
            current_freq_index = 0
            min_search_power = float(self.license_limits.get('min_power', 5))
            
            # print(f"[INFO] DEBUG LOOP: min_freq={min_freq}, max_freq={max_freq}, freq_step={freq_step}")
            for freq in np.arange(min_freq, max_freq + freq_step, freq_step):
                if test_cancelled or self.global_test_cancelled:
                    fc_log("threshold", '[ERRO] Teste cancelado pelo usuário')
                    raise TestCancelledException("Teste cancelado pelo usuário")
                    
                current_freq_index += 1
                # print(f"[INFO] DEBUG LOOP: Frequência atual: {freq}MHz (índice: {current_freq_index})")
                
                # NOVO ALGORITMO ADAPTATIVO: Usa mesma potência do threshold anterior
                if previous_threshold is not None:
                    start_power = previous_threshold  # [OK] USA MESMA POTÊNCIA (sem +2 dBm)
                else:
                    start_power = 15.0  # [OK] MODIFICAÇÃO: Primeira frequência sempre inicia com 15 dBm
                
                # Atualiza indicadores de temperatura e VSWR no painel Safety durante o teste
                self._update_safety_displays_from_test(start_power)
                
                threshold_found = False
                threshold_power = None
                threshold_rssi = None
                last_successful_power = None
                last_successful_rssi = None
                last_successful_module_power = None  # NOVO: armazena Module Power da última detecção
                
                # NOVA LÓGICA: Primeira leitura para decidir direção do algoritmo
                if previous_threshold is not None:
                    fc_log("threshold", f'[TARGET] TESTE INICIAL: {freq:.1f}MHz, {start_power:.1f}dBm (potência do threshold anterior)')
                else:
                    fc_log("threshold", f'[TARGET] TESTE INICIAL: {freq:.1f}MHz, {start_power:.1f}dBm (primeira frequência - potência fixa 15 dBm)')
                # Mede Module Power na potência inicial
                initial_module_power = self.real_module_power_measurement(freq, start_power, selected_tag_info['epc'])
                if initial_module_power is None:
                    fc_log("threshold", f'[AVISO] Falha na medição inicial, tentando recuperação...')
                    self._sleep_with_cancellation_check(0.1)  # OTIMIZADO: 80% mais rápido
                    if self.global_test_cancelled:
                        break
                    initial_module_power = self.real_module_power_measurement(freq, start_power, selected_tag_info['epc'])
                
                if initial_module_power is None:
                    fc_log("threshold", f'[ERRO] Falha na medição inicial após recuperação, pulando frequência {freq:.1f}MHz')
                    continue
                
                # Testa detecção na potência inicial para decidir direção
                initial_tag_detected = self.quick_tag_detection(freq, start_power, selected_tag_info['epc'])
                
                if initial_tag_detected:
                    fc_log("threshold", f'[OK] CASO B: Tag detectada na potência inicial - diminuindo potência até threshold')
                    # CASO B: Tag lida → diminui potência até threshold (curva de slope negativo)
                    # Armazena valores da medição inicial bem-sucedida
                    last_successful_power = start_power
                    last_successful_module_power = initial_module_power
                    
                    # Mede RSSI durante a detecção inicial
                    rssi_during_detection = self.quick_rssi_measurement(freq, start_power, selected_tag_info['epc'])
                    if rssi_during_detection is not None:
                        last_successful_rssi = rssi_during_detection
                        fc_log("threshold", f'[RF] RSSI medido na detecção inicial: {rssi_during_detection:.1f}dBm')                   
                    # CORREÇÃO: Usa o power_step configurado pelo usuário
                    dynamic_step = power_step  # Usa o passo configurado pelo usuário
                    for power in np.arange(start_power - dynamic_step, min_search_power - dynamic_step, -dynamic_step):
                        if test_cancelled or self.global_test_cancelled:
                            raise TestCancelledException("Teste cancelado pelo usuário")
                            
                        measurement_count += 1
                        
                        # Atualiza barra de progresso baseada na frequência atual
                        progress_percent = (current_freq_index / total_frequencies) * 100
                        progress_var.set(progress_percent)
                        
                        status_label.config(text=f"Freq: {freq:.1f} MHz, Power: {power:.1f} dBm (CASO B: diminuindo)")
                        detail_label.config(text=f"Procurando threshold... ({current_freq_index}/{total_frequencies})")
                        
                        # VERIFICAÇÃO DE CANCELAMENTO OTIMIZADA (a cada 3 medições)
                        if measurement_count % 3 == 0 and self.global_test_cancelled:
                            fc_log("threshold", '🛑 Cancelamento detectado durante medição')
                            raise TestCancelledException("Teste cancelado pelo usuário")
                        
                        # CORREÇÃO: Verificação contínua de potência máxima DESABILITADA (como na versão estável)
                        # DESABILITADO: Esta verificação estava causando erros desnecessários
                        # e interrompendo o teste principal que funcionava normalmente
                        max_power_detection = True  # Sempre assume OK para não interromper o teste
                        
                        # Código de verificação de potência mínima removido (causava abortos desnecessários)
                        if False:  # Código morto - nunca executado
                            fc_log("threshold", f'[AVISO] VERIFICAÇÃO DE POTÊNCIA MÍNIMA: Chegou em {power:.1f} dBm')                         
                            # Testa se ainda consegue ler na potência mínima
                            min_power_detection = self.quick_tag_detection(freq, power, selected_tag_info['epc'])
                            
                            if min_power_detection:
                                fc_log("threshold", f'[ERRO] VERIFICAÇÃO DE POTÊNCIA MÍNIMA FALHOU: Tag ainda lida em {power:.1f} dBm')
                                messagebox.showwarning(
                                    "Threshold Não Definido", 
                                    f"Não foi possível definir o threshold.\n\n"
                                    f"A tag ainda é lida na potência mínima de 5.0 dBm.\n"
                                    f"O threshold está abaixo do limite operacional mínimo.\n\n"
                                    f"O teste será cancelado automaticamente."
                                )
                                # ENCERRA o teste completamente e executa cancelamento automático
                                fc_log("threshold", '🛑 CANCELAMENTO AUTOMÁTICO: Não foi possível determinar o threshold')                           
                                # Fecha a porta COM
                                if com_opened:
                                    rfid_sdk.UHF_RFID_Close()
                                
                                # Fecha a janela de progresso
                                progress_window.destroy()
                                
                                # EXECUTA CANCELAMENTO COMPLETO AUTOMÁTICO DIRETAMENTE
                                fc_log("threshold", '🛑 EXECUTANDO CANCELAMENTO DIRETO...')                              
                                # 1. MARCA CANCELAMENTO GLOBAL
                                self.global_test_cancelled = True
                                self.test_in_progress = False
                                self.is_test_running = False
                                fc_log("threshold", '[OK] Flags de cancelamento setados')
                                
                                # 2. FECHA JANELA DE REALTIME IMEDIATAMENTE E FORÇADAMENTE
                                fc_log("threshold", '🛑 FECHANDO JANELA DE REALTIME IMEDIATAMENTE...')                              
                                # 2.1. FECHA JANELA DE REALTIME ESPECÍFICA
                                if hasattr(self, 'realtime_window') and self.realtime_window is not None:
                                    try:
                                        fc_log("threshold", f'🛑 Janela encontrada: {self.realtime_window}')
                                        fc_log("threshold", f'🛑 Janela existe: {self.realtime_window.winfo_exists()}')                                      
                                        # FECHAMENTO IMEDIATO E FORÇADO
                                        try:
                                            self.realtime_window.grab_release()
                                            fc_log("threshold", '[OK] Grab release executado')
                                        except:
                                            fc_log("threshold", '[AVISO] Grab release falhou')
                                        
                                        try:
                                            self.realtime_window.quit()
                                            fc_log("threshold", '[OK] Quit executado')
                                        except:
                                            fc_log("threshold", '[AVISO] Quit falhou')
                                        
                                        try:
                                            self.realtime_window.destroy()
                                            fc_log("threshold", '[OK] Destroy executado')
                                        except:
                                            fc_log("threshold", '[AVISO] Destroy falhou')
                                        
                                        # SE AINDA EXISTIR, FORÇA DE FORMA MAIS AGRESSIVA
                                        if self.realtime_window.winfo_exists():
                                            fc_log("threshold", '🛑 FORÇANDO FECHAMENTO AGRESSIVO...')
                                            try:
                                                self.realtime_window.attributes('-topmost', False)
                                                fc_log("threshold", '[OK] Topmost removido')
                                            except:
                                                pass
                                            
                                            try:
                                                self.realtime_window.withdraw()
                                                fc_log("threshold", '[OK] Janela escondida')
                                            except:
                                                pass
                                            
                                            try:
                                                self.realtime_window.destroy()
                                                fc_log("threshold", '[OK] Destroy agressivo executado')
                                            except:
                                                pass
                                        
                                        # LIMPA REFERÊNCIA
                                        self.realtime_window = None
                                        fc_log("threshold", '[OK] Referência da janela limpa')                                        
                                    except Exception as e:
                                        fc_log("threshold", f'[ERRO] Erro ao fechar janela de realtime: {e}')
                                        self.realtime_window = None
                                else:
                                    fc_log("threshold", 'ℹ️ Janela de realtime não está aberta')                           
                                # 2.2. FECHA TODAS AS JANELAS TOPLEVEL IMEDIATAMENTE
                                try:
                                    fc_log("threshold", '🛑 FECHANDO TODAS AS JANELAS TOPLEVEL IMEDIATAMENTE...')
                                    windows_closed = 0
                                    
                                    # Lista todas as janelas primeiro
                                    all_windows = []
                                    for child in self.root.winfo_children():
                                        if isinstance(child, tk.Toplevel):
                                            all_windows.append(child)
                                    
                                    fc_log("threshold", f'🛑 Encontradas {len(all_windows)} janelas para fechar')                                  
                                    # Fecha cada janela de forma agressiva
                                    for window in all_windows:
                                        try:
                                            fc_log("threshold", f'🛑 Fechando janela: {window}')                                          
                                            # Múltiplas tentativas de fechamento
                                            try:
                                                window.grab_release()
                                            except:
                                                pass
                                            
                                            try:
                                                window.quit()
                                            except:
                                                pass
                                            
                                            try:
                                                window.destroy()
                                            except:
                                                pass
                                            
                                            # Se ainda existir, força mais agressivamente
                                            if window.winfo_exists():
                                                try:
                                                    window.attributes('-topmost', False)
                                                    window.withdraw()
                                                    window.destroy()
                                                except:
                                                    pass
                                            
                                            windows_closed += 1
                                            fc_log("threshold", f'[OK] Janela fechada: {window}')
                                            
                                        except Exception as e:
                                            fc_log("threshold", f'[AVISO] Erro ao fechar janela {window}: {e}')
                                    
                                    fc_log("threshold", f'[OK] Total de janelas fechadas: {windows_closed}')
                                    
                                except Exception as e:
                                    fc_log("threshold", f'[ERRO] Erro ao fechar todas as janelas: {e}')
                                
                                # 2.3. FORÇA ATUALIZAÇÃO IMEDIATA
                                try:
                                    fc_log("threshold", '[INFO] Forçando atualização imediata...')
                                    self.root.update()
                                    self.update()
                                    fc_log("threshold", '[OK] Atualização forçada executada')
                                except Exception as e:
                                    fc_log("threshold", f'[AVISO] Erro na atualização forçada: {e}')                              
                                # 3. HABILITA BOTÃO DE INICIAR DIRETAMENTE
                                if hasattr(self, 'start_button'):
                                    try:
                                        fc_log("threshold", '🛑 Habilitando botão de iniciar diretamente...')
                                        self._set_test_button_idle()
                                        fc_log("threshold", '[OK] Botão de iniciar habilitado diretamente')
                                    except Exception as e:
                                        fc_log("threshold", f'[ERRO] Erro ao habilitar botão de iniciar: {e}')
                                else:
                                    fc_log("threshold", '[AVISO] Botão de iniciar não encontrado')
                                # 5. FORÇA FECHAMENTO EXTREMO IMEDIATO
                                try:
                                    fc_log("threshold", '💀 EXECUTANDO FECHAMENTO EXTREMO IMEDIATO...')                                  
                                    # 5.1. FORÇA FECHAMENTO EXTREMO IMEDIATO DE TODAS AS JANELAS
                                    self._extreme_window_kill()
                                    
                                    # 5.2. FORÇA ATUALIZAÇÃO IMEDIATA
                                    self.root.update()
                                    self.update()
                                    
                                    # 5.3. AGENDA FECHAMENTO EXTREMO ADICIONAL
                                    self.after(10, lambda: self._extreme_window_kill())
                                    self.after(50, lambda: self._extreme_window_kill())
                                    self.after(100, lambda: self._extreme_window_kill())
                                    self.after(200, lambda: self._extreme_window_kill())
                                    self.after(500, lambda: self._extreme_window_kill())
                                    
                                    fc_log("threshold", '[OK] Fechamento extremo executado e agendado')
                                except Exception as e:
                                    fc_log("threshold", f'[ERRO] Erro no fechamento extremo: {e}')
                                
                                return []
                            else:
                                fc_log("threshold", f'[OK] VERIFICAÇÃO DE POTÊNCIA MÍNIMA PASSOU: Tag não lida em {power:.1f} dBm')                    
                        # Medições
                        fc_log("threshold", f'[INFO] Testando: {freq:.1f}MHz, {power:.1f}dBm (CASO B: diminuindo)')
                        module_power = self.real_module_power_measurement(freq, power, selected_tag_info['epc'])
                        
                        # TENTATIVA DE RECUPERAÇÃO: Se falhar, tenta novamente
                        if module_power is None:
                            fc_log("threshold", f'[INFO] Tentativa de recuperação para {freq:.1f}MHz, {power:.1f}dBm')
                            self._sleep_with_cancellation_check(0.1)  # OTIMIZADO: 80% mais rápido  # Pequena pausa com verificação
                            if self.global_test_cancelled:
                                break
                            module_power = self.real_module_power_measurement(freq, power, selected_tag_info['epc'])
                        
                        if module_power is None:
                            fc_log("threshold", f'[AVISO] Falha na medição de Module Power após recuperação: {freq:.1f}MHz, {power:.1f}dBm')
                            continue
                        
                        # VERIFICAÇÃO DE CANCELAMENTO APÓS MEDIÇÃO
                        if self.global_test_cancelled:
                            fc_log("threshold", '🛑 Cancelamento detectado após medição')
                            raise TestCancelledException("Teste cancelado pelo usuário")
                        
                        # VERIFICAÇÃO DE CANCELAMENTO ANTES DA DETECÇÃO
                        if self.global_test_cancelled:
                            fc_log("threshold", '🛑 Cancelamento detectado antes da detecção')
                            raise TestCancelledException("Teste cancelado pelo usuário")
                        
                        # OTIMIZAÇÃO: Detecção rápida SEM RSSI para medições normais
                        tag_detected = self.quick_tag_detection(freq, power, selected_tag_info['epc'])
                        if tag_detected:
                            fc_log("threshold", f'[OK] Tag detectada: {freq:.1f}MHz, {power:.1f}dBm')
                        else:
                            fc_log("threshold", f'[ERRO] Tag NÃO detectada: {freq:.1f}MHz, {power:.1f}dBm')                        
                        if tag_detected:
                            # Tag detectada - armazena potência para threshold
                            last_successful_power = power
                            last_successful_module_power = module_power  # NOVO: armazena Module Power da detecção
                            
                            # NOVO: Mede RSSI durante a detecção bem-sucedida
                            rssi_during_detection = self.quick_rssi_measurement(freq, power, selected_tag_info['epc'])
                            if rssi_during_detection is not None:
                                last_successful_rssi = rssi_during_detection
                                fc_log("threshold", f'[RF] RSSI medido durante detecção: {rssi_during_detection:.1f}dBm')                           
                            # ATUALIZAÇÃO FORÇADA DO POPUP
                            progress_window.update()
                            
                        else:
                            # Tag não detectada - threshold encontrado
                            if last_successful_power is not None:
                                fc_log("threshold", f'[TARGET] THRESHOLD ENCONTRADO: {freq:.1f}MHz, {last_successful_power:.1f}dBm')
                                
                                # NOVA SOLUÇÃO: Usa os valores da última detecção bem-sucedida
                                threshold_found = True
                                threshold_power = last_successful_power
                                threshold_rssi = last_successful_rssi if last_successful_rssi is not None else -999.0
                                
                                # Usa o Module Power da última detecção
                                if last_successful_module_power is not None:
                                    fc_log("threshold", f'[TARGET] THRESHOLD FINAL: {freq:.1f}MHz, {threshold_power:.1f}dBm, Module Power: {last_successful_module_power:.1f}dBm, RSSI: {threshold_rssi:.1f}dBm')
                                else:
                                    fc_log("threshold", f'[TARGET] THRESHOLD FINAL: {freq:.1f}MHz, {threshold_power:.1f}dBm, RSSI: {threshold_rssi:.1f}dBm')
                                
                                # Adiciona na tabela do popup
                                item = results_tree.insert('', 'end', values=(
                                    f"{freq:.1f}", 
                                    f"{threshold_power:.1f}", 
                                    f"{threshold_rssi:.1f}" if threshold_rssi != -999.0 else t('threshold.failed')
                                ))
                                results_tree.item(item, tags=('threshold',))
                                results_tree.see(item)
                                
                                # PLOTAGEM EM TEMPO REAL (redesenho dos gráficos no thread principal)
                                self.add_realtime_point(freq, threshold_power, threshold_rssi)
                                progress_window.update()  # Atualiza o popup
                                
                                # Pequeno delay para garantir visualização
                                self._sleep_with_cancellation_check(0.005)  # OTIMIZADO: 50% mais rápido
                                if self.global_test_cancelled:
                                    break
                                
                                break
                        
                        # Atualização periódica para manter responsividade (OTIMIZADO)
                        if measurement_count % 10 == 0:  # A cada 10 medições (50% menos atualizações)
                            self.update()
                            progress_window.update()
                
                else:
                    # CASO A: Tag NÃO detectada na potência inicial - aumentando potência até threshold
                    fc_log("threshold", f'[ERRO] CASO A: Tag NÃO detectada na potência inicial - aumentando potência até threshold')                 
                    # CORREÇÃO: Usa apenas busca linear como na versão antiga estável
                    fc_log("threshold", f'[INFO] CICLO {current_freq_index}: Usando busca linear (como na versão estável)')
                    # CORREÇÃO: Usa o power_step configurado pelo usuário
                    dynamic_step = power_step  # Usa o passo configurado pelo usuário
                    for power in np.arange(start_power + dynamic_step, max_power + dynamic_step, dynamic_step):
                        if test_cancelled or self.global_test_cancelled:
                            raise TestCancelledException("Teste cancelado pelo usuário")
                            
                        measurement_count += 1
                        
                        # Atualiza barra de progresso baseada na frequência atual
                        progress_percent = (current_freq_index / total_frequencies) * 100
                        progress_var.set(progress_percent)
                        
                        status_label.config(text=f"Freq: {freq:.1f} MHz, Power: {power:.1f} dBm (CASO A: aumentando)")
                        detail_label.config(text=f"Procurando threshold... ({current_freq_index}/{total_frequencies})")
                        
                        # VERIFICAÇÃO DE CANCELAMENTO OTIMIZADA (a cada 3 medições)
                        if measurement_count % 3 == 0 and self.global_test_cancelled:
                            fc_log("threshold", '🛑 Cancelamento detectado durante medição')
                            raise TestCancelledException("Teste cancelado pelo usuário")
                        
                        # CORREÇÃO: Verificação contínua de potência máxima DESABILITADA (como na versão estável)
                        # DESABILITADO: Esta verificação estava causando erros desnecessários
                        # e interrompendo o teste principal que funcionava normalmente
                        max_power_detection = True  # Sempre assume OK para não interromper o teste
                        
                        # Código de verificação de potência mínima removido (causava abortos desnecessários)
                        if False:  # Código morto - nunca executado
                            fc_log("threshold", f'[AVISO] VERIFICAÇÃO DE POTÊNCIA MÍNIMA: Chegou em {power:.1f} dBm')                         
                            # Testa se ainda consegue ler na potência mínima
                            min_power_detection = self.quick_tag_detection(freq, power, selected_tag_info['epc'])
                            
                            if min_power_detection:
                                fc_log("threshold", f'[ERRO] VERIFICAÇÃO DE POTÊNCIA MÍNIMA FALHOU: Tag ainda lida em {power:.1f} dBm')
                                messagebox.showwarning(
                                    "Threshold Não Definido", 
                                    f"Não foi possível definir o threshold.\n\n"
                                    f"A tag ainda é lida na potência mínima de 5.0 dBm.\n"
                                    f"O threshold está abaixo do limite operacional mínimo.\n\n"
                                    f"O teste será cancelado automaticamente (CASO A)."
                                )
                                # ENCERRA o teste completamente e executa cancelamento automático
                                fc_log("threshold", '🛑 CANCELAMENTO AUTOMÁTICO CASO A: Não foi possível determinar o threshold')                           
                                # Fecha a porta COM
                                if com_opened:
                                    rfid_sdk.UHF_RFID_Close()
                                
                                # Fecha a janela de progresso
                                progress_window.destroy()
                                
                                # EXECUTA CANCELAMENTO COMPLETO AUTOMÁTICO DIRETAMENTE
                                fc_log("threshold", '🛑 EXECUTANDO CANCELAMENTO DIRETO CASO A...')                              
                                # 1. MARCA CANCELAMENTO GLOBAL
                                self.global_test_cancelled = True
                                self.test_in_progress = False
                                self.is_test_running = False
                                fc_log("threshold", '[OK] Flags de cancelamento setados (CASO A)')
                                
                                # 2. FECHA JANELA DE REALTIME IMEDIATAMENTE E FORÇADAMENTE (CASO A)
                                fc_log("threshold", '🛑 FECHANDO JANELA DE REALTIME IMEDIATAMENTE (CASO A)...')                              
                                # 2.1. FECHA JANELA DE REALTIME ESPECÍFICA (CASO A)
                                if hasattr(self, 'realtime_window') and self.realtime_window is not None:
                                    try:
                                        fc_log("threshold", f'🛑 Janela encontrada (CASO A): {self.realtime_window}')
                                        fc_log("threshold", f'🛑 Janela existe (CASO A): {self.realtime_window.winfo_exists()}')                                      
                                        # FECHAMENTO IMEDIATO E FORÇADO (CASO A)
                                        try:
                                            self.realtime_window.grab_release()
                                            fc_log("threshold", '[OK] Grab release executado (CASO A)')
                                        except:
                                            fc_log("threshold", '[AVISO] Grab release falhou (CASO A)')
                                        
                                        try:
                                            self.realtime_window.quit()
                                            fc_log("threshold", '[OK] Quit executado (CASO A)')
                                        except:
                                            fc_log("threshold", '[AVISO] Quit falhou (CASO A)')
                                        
                                        try:
                                            self.realtime_window.destroy()
                                            fc_log("threshold", '[OK] Destroy executado (CASO A)')
                                        except:
                                            fc_log("threshold", '[AVISO] Destroy falhou (CASO A)')
                                        
                                        # SE AINDA EXISTIR, FORÇA DE FORMA MAIS AGRESSIVA (CASO A)
                                        if self.realtime_window.winfo_exists():
                                            fc_log("threshold", '🛑 FORÇANDO FECHAMENTO AGRESSIVO (CASO A)...')
                                            try:
                                                self.realtime_window.attributes('-topmost', False)
                                                fc_log("threshold", '[OK] Topmost removido (CASO A)')
                                            except:
                                                pass
                                            
                                            try:
                                                self.realtime_window.withdraw()
                                                fc_log("threshold", '[OK] Janela escondida (CASO A)')
                                            except:
                                                pass
                                            
                                            try:
                                                self.realtime_window.destroy()
                                                fc_log("threshold", '[OK] Destroy agressivo executado (CASO A)')
                                            except:
                                                pass
                                        
                                        # LIMPA REFERÊNCIA (CASO A)
                                        self.realtime_window = None
                                        fc_log("threshold", '[OK] Referência da janela limpa (CASO A)')                                        
                                    except Exception as e:
                                        fc_log("threshold", f'[ERRO] Erro ao fechar janela de realtime (CASO A): {e}')
                                        self.realtime_window = None
                                else:
                                    fc_log("threshold", 'ℹ️ Janela de realtime não está aberta (CASO A)')                           
                                # 2.2. FECHA TODAS AS JANELAS TOPLEVEL IMEDIATAMENTE (CASO A)
                                try:
                                    fc_log("threshold", '🛑 FECHANDO TODAS AS JANELAS TOPLEVEL IMEDIATAMENTE (CASO A)...')
                                    windows_closed = 0
                                    
                                    # Lista todas as janelas primeiro (CASO A)
                                    all_windows = []
                                    for child in self.root.winfo_children():
                                        if isinstance(child, tk.Toplevel):
                                            all_windows.append(child)
                                    
                                    fc_log("threshold", f'🛑 Encontradas {len(all_windows)} janelas para fechar (CASO A)')                                  
                                    # Fecha cada janela de forma agressiva (CASO A)
                                    for window in all_windows:
                                        try:
                                            fc_log("threshold", f'🛑 Fechando janela (CASO A): {window}')                                          
                                            # Múltiplas tentativas de fechamento (CASO A)
                                            try:
                                                window.grab_release()
                                            except:
                                                pass
                                            
                                            try:
                                                window.quit()
                                            except:
                                                pass
                                            
                                            try:
                                                window.destroy()
                                            except:
                                                pass
                                            
                                            # Se ainda existir, força mais agressivamente (CASO A)
                                            if window.winfo_exists():
                                                try:
                                                    window.attributes('-topmost', False)
                                                    window.withdraw()
                                                    window.destroy()
                                                except:
                                                    pass
                                            
                                            windows_closed += 1
                                            fc_log("threshold", f'[OK] Janela fechada (CASO A): {window}')
                                            
                                        except Exception as e:
                                            fc_log("threshold", f'[AVISO] Erro ao fechar janela {window} (CASO A): {e}')
                                    
                                    fc_log("threshold", f'[OK] Total de janelas fechadas (CASO A): {windows_closed}')
                                    
                                except Exception as e:
                                    fc_log("threshold", f'[ERRO] Erro ao fechar todas as janelas (CASO A): {e}')
                                
                                # 2.3. FORÇA ATUALIZAÇÃO IMEDIATA (CASO A)
                                try:
                                    fc_log("threshold", '[INFO] Forçando atualização imediata (CASO A)...')
                                    self.root.update()
                                    self.update()
                                    fc_log("threshold", '[OK] Atualização forçada executada (CASO A)')
                                except Exception as e:
                                    fc_log("threshold", f'[AVISO] Erro na atualização forçada (CASO A): {e}')                              
                                # 3. HABILITA BOTÃO DE INICIAR DIRETAMENTE
                                if hasattr(self, 'start_button'):
                                    try:
                                        fc_log("threshold", '🛑 Habilitando botão de iniciar diretamente (CASO A)...')
                                        self._set_test_button_idle()
                                        fc_log("threshold", '[OK] Botão de iniciar habilitado diretamente (CASO A)')
                                    except Exception as e:
                                        fc_log("threshold", f'[ERRO] Erro ao habilitar botão de iniciar (CASO A): {e}')
                                else:
                                    fc_log("threshold", '[AVISO] Botão de iniciar não encontrado (CASO A)')
                                # 5. FORÇA ATUALIZAÇÃO DA INTERFACE
                                try:
                                    fc_log("threshold", '💀 EXECUTANDO FECHAMENTO EXTREMO IMEDIATO (CASO A)...')                                  
                                    # 5.1. FORÇA FECHAMENTO EXTREMO IMEDIATO DE TODAS AS JANELAS (CASO A)
                                    self._extreme_window_kill()
                                    
                                    # 5.2. FORÇA ATUALIZAÇÃO IMEDIATA (CASO A)
                                    self.root.update()
                                    self.update()
                                    
                                    # 5.3. AGENDA FECHAMENTO EXTREMO ADICIONAL (CASO A)
                                    self.after(10, lambda: self._extreme_window_kill())
                                    self.after(50, lambda: self._extreme_window_kill())
                                    self.after(100, lambda: self._extreme_window_kill())
                                    self.after(200, lambda: self._extreme_window_kill())
                                    self.after(500, lambda: self._extreme_window_kill())
                                    
                                    fc_log("threshold", '[OK] Fechamento extremo executado e agendado (CASO A)')
                                except Exception as e:
                                    fc_log("threshold", f'[ERRO] Erro no fechamento extremo (CASO A): {e}')
                                
                                return []
                            else:
                                fc_log("threshold", f'[OK] VERIFICAÇÃO DE POTÊNCIA MÍNIMA PASSOU: Tag não lida em {power:.1f} dBm')                    
                        # Medições
                        fc_log("threshold", f'[INFO] Testando: {freq:.1f}MHz, {power:.1f}dBm (CASO A: aumentando)')
                        module_power = self.real_module_power_measurement(freq, power, selected_tag_info['epc'])
                        
                        # TENTATIVA DE RECUPERAÇÃO: Se falhar, tenta novamente
                        if module_power is None:
                            fc_log("threshold", f'[INFO] Tentativa de recuperação para {freq:.1f}MHz, {power:.1f}dBm')
                            self._sleep_with_cancellation_check(0.1)  # OTIMIZADO: 80% mais rápido  # Pequena pausa com verificação
                            if self.global_test_cancelled:
                                break
                            module_power = self.real_module_power_measurement(freq, power, selected_tag_info['epc'])
                        
                        if module_power is None:
                            fc_log("threshold", f'[AVISO] Falha na medição de Module Power após recuperação: {freq:.1f}MHz, {power:.1f}dBm')
                            continue
                        
                        # VERIFICAÇÃO DE CANCELAMENTO APÓS MEDIÇÃO
                        if self.global_test_cancelled:
                            fc_log("threshold", '🛑 Cancelamento detectado após medição')
                            raise TestCancelledException("Teste cancelado pelo usuário")
                        
                        # VERIFICAÇÃO DE CANCELAMENTO ANTES DA DETECÇÃO
                        if self.global_test_cancelled:
                            fc_log("threshold", '🛑 Cancelamento detectado antes da detecção')
                            raise TestCancelledException("Teste cancelado pelo usuário")
                        
                        # OTIMIZAÇÃO: Detecção rápida SEM RSSI para medições normais
                        tag_detected = self.quick_tag_detection(freq, power, selected_tag_info['epc'])
                        if tag_detected:
                            fc_log("threshold", f'[OK] Tag detectada: {freq:.1f}MHz, {power:.1f}dBm')
                        else:
                            fc_log("threshold", f'[ERRO] Tag NÃO detectada: {freq:.1f}MHz, {power:.1f}dBm')                        
                        if tag_detected:
                            # Tag detectada - threshold encontrado
                            fc_log("threshold", f'[TARGET] THRESHOLD ENCONTRADO: {freq:.1f}MHz, {power:.1f}dBm')
                            
                            threshold_found = True
                            threshold_power = power
                            threshold_rssi = -999.0  # RSSI será medido separadamente
                            
                            # Mede RSSI no threshold encontrado
                            rssi_at_threshold = self.quick_rssi_measurement(freq, power, selected_tag_info['epc'])
                            if rssi_at_threshold is not None:
                                threshold_rssi = rssi_at_threshold
                                fc_log("threshold", f'[RF] RSSI medido no threshold: {rssi_at_threshold:.1f}dBm')
                            
                            # Usa o Module Power da medição atual
                            if module_power is not None:
                                fc_log("threshold", f'[TARGET] THRESHOLD FINAL: {freq:.1f}MHz, {threshold_power:.1f}dBm, Module Power: {module_power:.1f}dBm, RSSI: {threshold_rssi:.1f}dBm')
                            else:
                                fc_log("threshold", f'[TARGET] THRESHOLD FINAL: {freq:.1f}MHz, {threshold_power:.1f}dBm, RSSI: {threshold_rssi:.1f}dBm')
                            
                            # Adiciona na tabela do popup
                            item = results_tree.insert('', 'end', values=(
                                f"{freq:.1f}", 
                                f"{threshold_power:.1f}", 
                                f"{threshold_rssi:.1f}" if threshold_rssi != -999.0 else "FALHOU"
                            ))
                            results_tree.item(item, tags=('threshold',))
                            results_tree.see(item)
                            
                            # PLOTAGEM EM TEMPO REAL (redesenho dos gráficos no thread principal)
                            self.add_realtime_point(freq, threshold_power, threshold_rssi)
                            progress_window.update()  # Atualiza o popup
                            
                            # Pequeno delay para garantir visualização
                            self._sleep_with_cancellation_check(0.005)  # OTIMIZADO: 50% mais rápido
                            if self.global_test_cancelled:
                                break
                            
                            break
                        
                        # Atualização periódica para manter responsividade (OTIMIZADO)
                        if measurement_count % 10 == 0:  # A cada 10 medições (50% menos atualizações)
                            self.update()
                            progress_window.update()
                
                # Sem potências abaixo da inicial para sondar (ex.: threshold anterior = mínimo): usa a leitura inicial
                if (
                    initial_tag_detected
                    and not threshold_found
                    and last_successful_power is not None
                    and len(np.arange(start_power - power_step, min_search_power - power_step, -power_step)) == 0
                ):
                    threshold_found = True
                    threshold_power = last_successful_power
                    threshold_rssi = last_successful_rssi if last_successful_rssi is not None else -999.0
                    fc_log(
                        "threshold",
                        f"[TARGET] THRESHOLD na potência inicial (sem descida): {freq:.1f}MHz, {threshold_power:.1f}dBm",
                    )
                    try:
                        self.add_realtime_point(freq, threshold_power, threshold_rssi)
                        item = results_tree.insert(
                            '',
                            'end',
                            values=(
                                f"{freq:.1f}",
                                f"{threshold_power:.1f}",
                                f"{threshold_rssi:.1f}" if threshold_rssi != -999.0 else t('threshold.failed'),
                            ),
                        )
                        results_tree.item(item, tags=('threshold',))
                        results_tree.see(item)
                    except Exception as e:
                        fc_log("threshold", f"[AVISO] UI threshold inicial: {e}")
                
                if not threshold_found:
                    skipped_no_threshold_freqs.append(float(freq))
                    fc_log(
                        "threshold",
                        f"[AVISO] Threshold não determinado em {freq:.1f} MHz — a continuar com as restantes frequências (sem plotar este ponto).",
                    )
                    try:
                        detail_label.config(
                            text=f"{freq:.1f} MHz: threshold não determinado — a continuar… ({current_freq_index}/{total_frequencies})"
                        )
                        progress_window.update_idletasks()
                    except Exception:
                        pass
                    continue
                
                # Armazena resultado se threshold foi encontrado
                if threshold_found:
                    fc_log("threshold", f'[INFO] DEBUG: Salvando resultado com frequência: {freq}MHz')
                    # Garante quantização de potência (resolução 0,5 dBm)
                    # CORREÇÃO CRÍTICA: Valida frequência ANTES de adicionar aos resultados
                    if not self._validate_frequency_against_license(freq):
                        fc_log("threshold", f'[ERRO] Threshold: Frequência {freq} MHz está em faixa proibida - PULANDO plotagem no gráfico')
                        continue  # Pula esta frequência, não adiciona aos resultados
                    
                    threshold_power_q = self._quantize_half_db(threshold_power) if threshold_power is not None else None
                    module_power_base = last_successful_module_power if last_successful_module_power is not None else threshold_power_q
                    module_power_q = self._quantize_half_db(module_power_base) if module_power_base is not None else None

                    result = {
                        'freq_mhz': freq,
                        'threshold_power': threshold_power_q,  # IMPORTANTE: Nome correto da chave
                        'threshold_rssi': threshold_rssi,    # IMPORTANTE: Nome correto da chave
                        'module_power': module_power_q,  # Module Power com quantização 0,5 dBm
                        'rssi': threshold_rssi,              # Para compatibilidade
                        'timestamp': datetime.now().strftime("%d-%m-%Y %H:%M"),
                        'test_id': self.test_counter + 1,
                        'description': self.description_input_var.get() or f'Teste {self.test_counter + 1}',
                        'epc': selected_tag_info['epc']
                    }
                    
                    # Aplica correções ANTES de adicionar aos resultados
                    result = apply_corrections_to_result(
                        result,
                        attenuator,
                        distance,
                        max_link_forward_eirp_dbm=self._max_link_forward_eirp_dbm_for_active_license(),
                    )
                    
                    # IMPORTANTE: Garante que as chaves corretas existam após correções
                    if 'threshold_power' not in result:
                        result['threshold_power'] = result.get('module_power', threshold_power)
                    if 'threshold_rssi' not in result:
                        result['threshold_rssi'] = result.get('rssi', threshold_rssi)
                    
                    threshold_results.append(result)
                    previous_threshold = threshold_power
                    
                    # Atualiza estatísticas no popup
                    if threshold_results:
                        # USA AS CHAVES CORRETAS
                        powers = [r.get('threshold_power', r.get('module_power', 0)) for r in threshold_results]
                        rssis = [r.get('threshold_rssi', r.get('rssi', 0)) for r in threshold_results]
                        stats_label.config(
                            text=f"Thresholds encontrados: {len(threshold_results)} | "
                                 f"Power médio: {np.mean(powers):.1f} dBm | "
                                 f"RSSI médio: {np.mean(rssis):.1f} dBm"
                        )
            
            # Teste concluído
            if not test_cancelled:
                # --- CALCULA TEMPO TOTAL DO TESTE ---
                test_end_time = time.time()
                test_duration = test_end_time - test_start_time
                test_end_datetime = datetime.now()
                
                fc_log("threshold", f"[TIME] Teste finalizado em: {test_end_datetime.strftime('%d-%m-%Y %H:%M:%S')}")
                fc_log("threshold", f'[TIMER] Duração total do teste: {test_duration:.2f} segundos ({test_duration / 60:.2f} minutos)')

                max_power_abort = getattr(self, '_abort_save_due_to_max_power_failure', False)
                no_threshold_points = len(threshold_results) == 0

                if max_power_abort:
                    try:
                        status_label.config(text=t('threshold.status_aborted_threshold'))
                    except Exception:
                        pass
                    fc_log("threshold", '🛑 Salvamento e histórico ignorados: falha de leitura na potência máxima.')
                    progress_window.after(2000, progress_window.destroy)
                elif no_threshold_points:
                    try:
                        status_label.config(text=t('threshold.status_aborted_threshold'))
                        detail_label.config(text=t('threshold.threshold_not_found_title'))
                    except Exception:
                        pass
                    fc_log(
                        "threshold",
                        "[ERRO] Nenhum threshold determinado em todo o ensaio — histórico e ficheiros não gravados.",
                    )
                    progress_window.after(300, progress_window.destroy)
                else:
                    status_label.config(text="[OK] Teste de threshold concluído!")
                    detail_txt = (
                        f"Total: {len(threshold_results)} thresholds encontrados | Tempo: {test_duration:.1f}s"
                    )
                    if skipped_no_threshold_freqs:
                        detail_txt += f" | {len(skipped_no_threshold_freqs)} freq. sem threshold"
                    detail_label.config(text=detail_txt)
                    self.save_test_results_to_file(
                        threshold_results,
                        selected_tag_info,
                        test_start_datetime,
                        test_end_datetime,
                        test_duration,
                    )
                    description = self.description_input_var.get()
                    self.add_test_to_history(
                        selected_tag_info,
                        threshold_results,
                        description,
                        attenuator,
                        distance,
                        test_start_datetime,
                        test_end_datetime,
                        test_duration,
                    )
                    if skipped_no_threshold_freqs:
                        self._schedule_threshold_partial_skip_summary(skipped_no_threshold_freqs)
                    progress_window.after(2000, progress_window.destroy)
            
        except TestCancelledException:
            fc_log("threshold", '[OK] Teste cancelado pelo usuário - saindo normalmente')
            # Não mostra erro para cancelamento
        except Exception as e:
            # Suprime popup para não interromper a UX; mantém log detalhado no console
            fc_log("threshold", f'Erro durante o teste: {str(e)}')
            traceback.print_exc()
            
        finally:
            self.test_in_progress = False
            self.is_test_running = False
            self.global_test_cancelled = False  # RESETA FLAG DE CANCELAMENTO
            self._abort_save_due_to_threshold_not_found = False
            
            # CORREÇÃO: Para o sistema de segurança após o teste terminar
            if hasattr(self, '_stop_safety_monitoring'):
                try:
                    self._stop_safety_monitoring()
                    fc_log("threshold", '[OK] Sistema de segurança parado após teste')
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao parar sistema de segurança: {e}')            
            # --- LIMPA REFERÊNCIA DA THREAD ---
            self.active_test_thread = None
            
            # --- RESTAURA BOTÃO ÚNICO PARA INICIAR TESTE ---
            self._set_test_button_idle()

            # NOVO: limpa buffer de realtime ao final do teste
            try:
                self._rt_buffer = []
            except Exception:
                pass
            
            # --- NOTIFICA FIM DO TESTE ---
            self._notify_test_end()
            
            if hasattr(self, 'realtime_points'):
                self.realtime_points = {'freqs': [], 'powers': [], 'rssis': [], 'timestamps': []}
            
            # Atualização final
            self.update_history_tree()
            self.update_graphs_from_history()
            
            # --- FECHA PORTA COM E LIBERA NO PORT_MANAGER AO FINAL ---
            if com_opened:
                try:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    fc_log("threshold", '[HW] Porta COM fechada ao final do teste.')
                    com_opened = False
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao fechar porta COM: {e}')
                if hasattr(self, 'port_manager') and self.port_manager:
                    try:
                        self.port_manager.release_port("Threshold")
                    except Exception:
                        pass
                if getattr(self, '_stopped_by_reflected_protection', False) and getattr(self, 'app_shell', None) and hasattr(self.app_shell, 'perform_strong_reader_reset_async'):
                    self.after(300, lambda: self.app_shell.perform_strong_reader_reset_async() if getattr(self, 'app_shell', None) else None)
                self._stopped_by_reflected_protection = False
            # Fecha o popup se ainda estiver aberto
            try:
                if progress_window.winfo_exists():
                    progress_window.destroy()
            except:
                pass


    def real_module_power_measurement(self, freq, power, epc):
        """Medição real de Module Power usando hardware - EMITE RF
        
        OTIMIZAÇÃO: Porta COM já está aberta pela função principal
        """
        if not HARDWARE_AVAILABLE:
            fc_log("threshold", 'Hardware não disponível para medição de potência')
            return None
        
        try:
            # OTIMIZAÇÃO: Não abre/fecha porta COM (já está aberta)
            output_buffer = ctypes.create_string_buffer(256)
            output_len = ctypes.c_uint(0)
            
            # Usa helper com retries e delay de estabilização
            if not self._apply_power_and_frequency(freq, power):
                fc_log("threshold", '[AVISO] Falha ao aplicar potência/frequência em real_module_power_measurement')
                return None
            
            # MODULE POWER É A POTÊNCIA CONFIGURADA (5-25 dBm)
            # Não adiciona ganho - é a potência real configurada no módulo
            module_power = power  # Potência configurada (5-25 dBm)
            
            return module_power
                    
        except Exception as e:
            fc_log("threshold", f'Erro na medição de Module Power: {e}')
            return None
    
    def bytes_to_int_fastchecker(self, b):
        """Implementa exatamente o algoritmo bytesToInt do FastChecker"""
        length = len(b)
        value = 0
        
        for i in range(length):
            shift = (length - 1 - i) * 8
            
            # Trata bytes negativos corretamente
            if b[i] < 0:
                # Para bytes negativos, estende o sinal
                byte_val = b[i] | 0xFFFFFF00
            else:
                byte_val = b[i] & 0x000000FF
            
            value += byte_val << shift
        
        return value

    def real_rssi_measurement(self, freq, power, epc):
        """Medição real de RSSI usando hardware - VERSÃO COM ESTABILIZAÇÃO
        
        OBJETIVO: Medição RSSI precisa para determinação do threshold
        NOVA FUNCIONALIDADE: Sistema de estabilização RSSI implementado
        PRECISÃO: Alta com verificação de estabilidade automática
        OTIMIZAÇÃO: Porta COM já está aberta pela função principal
        
        USO: APENAS para determinar threshold, não para medições normais
        RETORNO: Valor RSSI estável ou None se não detectado
        """
        if not HARDWARE_AVAILABLE:
            fc_log("threshold", 'Hardware não disponível para medição de RSSI')
            return None
        
        try:
            fc_log("threshold", f'[INFO] Iniciando medição RSSI com estabilização: {freq:.1f}MHz, {power:.1f}dBm')         
            # NOVA IMPLEMENTAÇÃO: Usa sistema de estabilização RSSI
            stable_rssi = self._check_rssi_stability(freq, power, epc)
            
            if stable_rssi is not None:
                fc_log("threshold", f'[OK] RSSI estabilizado obtido: {stable_rssi:.1f}dBm')
                return stable_rssi
            else:
                fc_log("threshold", f'[AVISO] Falha na estabilização RSSI - tentando aumento de potência')              
                # NOVA FUNCIONALIDADE: Aumenta potência e repete medição
                max_power = 25.0  # Limite máximo de potência
                increased_power_rssi = self._increase_power_and_retry_rssi(freq, power, epc, max_power)
                
                if increased_power_rssi is not None:
                    fc_log("threshold", f'[OK] RSSI obtido com aumento de potência: {increased_power_rssi:.1f}dBm')
                    return increased_power_rssi
                else:
                    fc_log("threshold", f'[AVISO] Falha mesmo com aumento de potência - tentando medição tradicional')              
                # FALLBACK: Medição tradicional se estabilização falhar
                output_buffer = ctypes.create_string_buffer(256)
                output_len = ctypes.c_uint(0)
                
                # Configura potência e frequência usando helper com retries e delay
                if not self._apply_power_and_frequency(freq, power):
                    fc_log("threshold", '[AVISO] Falha ao aplicar potência/frequência no fallback de real_rssi_measurement')
                    return None
                
                # Executa inventário para medição
                inv_tag_input_data = bytes([0x00, 0x64])  # 100ms de inventário (como EPC_RSSI)
                result = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, 
                                              ctypes.c_char_p(inv_tag_input_data), 2, 
                                              output_buffer, ctypes.byref(output_len))
                
                if result == 0:
                    # Extrai EPC e verifica se é a tag esperada
                    detected_epc = self._extract_epc_from_inv_response(output_buffer.raw, output_len.value)
                    if not detected_epc:
                        return None
                    
                    if epc and epc.upper() == detected_epc:
                        # Extrai RSSI
                        rssi_bytes = output_buffer.raw[output_len.value - 3:output_len.value - 1]
                        rssi_raw = struct.unpack('>h', rssi_bytes)[0]
                        fallback_rssi = float(int(rssi_raw)) / 10.0
                        
                        fc_log("threshold", f'[RF] RSSI obtido por fallback: {fallback_rssi:.1f}dBm')
                        return fallback_rssi
                    else:
                        fc_log("threshold", f'[AVISO] Tag diferente detectada no fallback: {detected_epc}')
                        return None
                else:
                    fc_log("threshold", f'[AVISO] Falha no inventário durante fallback')
                    return None
                    
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro na medição real RSSI: {e}')
            return None
    
    def save_test_results_to_file(self, results, tag_info, test_start_datetime=None, test_end_datetime=None, test_duration=None):
        """Salva os resultados do teste - VERSÃO REPARADA COM MEDIÇÃO DE TEMPO"""
        # NOVO: Desativa geração automática de arquivos CSV locais (a pedido do usuário)
        if getattr(self, 'disable_threshold_csv', True):
            fc_log("threshold", 'ℹ️ Criação de CSV de Threshold desativada (disable_threshold_csv=True). Resultados não serão gravados em arquivo.')
            return
        # Evita salvar quando houve falha em potência máxima (flag setada durante o teste)
        if getattr(self, '_abort_save_due_to_max_power_failure', False):
            fc_log("threshold", '🛑 Abortando salvamento: falha de leitura na potência máxima.')
            return
        if getattr(self, '_abort_save_due_to_threshold_not_found', False):
            fc_log("threshold", '🛑 Abortando salvamento: threshold não encontrado.')
            return
        if not results:
            fc_log("threshold", '[AVISO] Nenhum resultado para salvar')
            return
            
        try:
            # Cria nome do arquivo
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            tag_name = tag_info.get('name') or tag_info.get('epc', 'unknown')[-6:]
            filename = f"threshold_test_{tag_name}_{timestamp}.csv"
            
            # Valida dados antes de salvar
            valid_results = []
            for result in results:
                if (isinstance(result, dict) and 
                    'freq_mhz' in result and 
                    'threshold_power' in result and 
                    'threshold_rssi' in result):
                    # Garante que os campos corrigidos estejam presentes
                    if 'irradiated_power' not in result:
                        result['irradiated_power'] = result.get('module_power', result['threshold_power'])
                    # REMOVIDO: Não sobrescreve o backscatter que já foi calculado corretamente
                    # O backscatter é calculado pela função apply_corrections_to_result
                    if 'power_on_tag_forward' not in result:
                        result['power_on_tag_forward'] = result.get('module_power', result['threshold_power'])
                    if 'power_on_tag_reversed' not in result:
                        result['power_on_tag_reversed'] = result.get('rssi', result['threshold_rssi'])
                    valid_results.append(result)
                else:
                    fc_log("threshold", f'[AVISO] Resultado inválido ignorado: {result}')            
            if not valid_results:
                fc_log("threshold", '[ERRO] Nenhum resultado válido para salvar')
                return
            
            # Converte para DataFrame e salva
            df = pd.DataFrame(valid_results)
            
            # Garante que todas as colunas necessárias estejam presentes
            required_columns = ['freq_mhz', 'threshold_power', 'threshold_rssi', 'module_power', 'rssi', 'irradiated_power', 'backscatter', 'power_on_tag_forward', 'power_on_tag_reversed', 'timestamp', 'test_id', 'description', 'epc', 'test_start_time', 'test_end_time', 'test_duration_seconds']
            
            for col in required_columns:
                if col not in df.columns:
                    if col == 'irradiated_power':
                        df[col] = df['module_power']
                    elif col == 'backscatter':
                        attenuator_val = self._parse_attenuator_db_for_test()
                        df[col] = df['rssi'] + attenuator_val
                    elif col == 'power_on_tag_forward':
                        df[col] = df['module_power']
                    elif col == 'power_on_tag_reversed':
                        df[col] = df['rssi']
                    elif col == 'test_start_time':
                        df[col] = test_start_datetime.strftime("%d-%m-%Y %H:%M:%S") if test_start_datetime else ''
                    elif col == 'test_end_time':
                        df[col] = test_end_datetime.strftime("%d-%m-%Y %H:%M:%S") if test_end_datetime else ''
                    elif col == 'test_duration_seconds':
                        df[col] = test_duration if test_duration else ''
                    else:
                        df[col] = ''
            
            df.to_csv(filename, index=False)
            
            fc_log("threshold", f'[OK] Resultados salvos: {filename} ({len(valid_results)} registros)')
            # Popup suprimido intencionalmente para evitar interrupções durante testes
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao salvar resultados: {e}')
            try:
                messagebox.showerror(t('threshold.error_saving_results'), t('threshold.error_saving_results_msg').format(error=str(e)))
            except:
                pass
    
    def _unified_threshold_cancellation(self, progress_window, com_opened, reason_text=""):
        """Cancela o teste usando exatamente a mesma sequência já consolidada.
        NÃO altera o fluxo existente de 5 dBm; apenas o reutiliza aqui.
        """
        try:
            if reason_text:
                fc_log("threshold", f'🛑 CANCELAMENTO AUTOMÁTICO: {reason_text}')
            else:
                fc_log("threshold", '🛑 CANCELAMENTO AUTOMÁTICO: Encerrando teste de threshold')
            # Fecha a porta COM
            if com_opened:
                try:
                    rfid_sdk.UHF_RFID_Close()
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao fechar porta COM: {e}')
            
            # Fecha a janela de progresso
            try:
                progress_window.destroy()
            except Exception as e:
                fc_log("threshold", f'[AVISO] Erro ao destruir janela de progresso: {e}')
            
            # Flags
            self.global_test_cancelled = True
            self.test_in_progress = False
            self.is_test_running = False
            fc_log("threshold", '[OK] Flags de cancelamento setados (unificado)')
            
            # Fecha janela de realtime se existir
            fc_log("threshold", '🛑 FECHANDO JANELA DE REALTIME IMEDIATAMENTE (unificado)...')
            if hasattr(self, 'realtime_window') and self.realtime_window is not None:
                try:
                    try:
                        self.realtime_window.grab_release()
                    except:
                        pass
                    try:
                        self.realtime_window.quit()
                    except:
                        pass
                    try:
                        self.realtime_window.destroy()
                    except:
                        pass
                    if self.realtime_window.winfo_exists():
                        try:
                            self.realtime_window.attributes('-topmost', False)
                            self.realtime_window.withdraw()
                            self.realtime_window.destroy()
                        except:
                            pass
                    self.realtime_window = None
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao fechar janela de realtime (unificado): {e}')
            else:
                fc_log("threshold", 'ℹ️ Janela de realtime não está aberta (unificado)')       
            # Fecha todas as toplevels
            try:
                all_windows = []
                for child in self.root.winfo_children():
                    if isinstance(child, tk.Toplevel):
                        all_windows.append(child)
                for window in all_windows:
                    try:
                        try:
                            window.grab_release()
                        except:
                            pass
                        try:
                            window.quit()
                        except:
                            pass
                        try:
                            window.destroy()
                        except:
                            pass
                        if window.winfo_exists():
                            try:
                                window.attributes('-topmost', False)
                                window.withdraw()
                                window.destroy()
                            except:
                                pass
                    except Exception as e:
                        fc_log("threshold", f'[AVISO] Erro ao fechar janela (unificado): {e}')
            except Exception as e:
                fc_log("threshold", f'[AVISO] Erro ao fechar janelas toplevel (unificado): {e}')
            
            # Atualização UI
            try:
                self.root.update()
                self.update()
            except Exception as e:
                fc_log("threshold", f'[AVISO] Erro na atualização forçada (unificado): {e}')          
            # Restaura botão único
            if hasattr(self, 'start_button'):
                try:
                    self._set_test_button_idle()
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao habilitar botão de iniciar (unificado): {e}')            
            # Fechamento extremo adicional
            try:
                self._extreme_window_kill()
                self.after(10, lambda: self._extreme_window_kill())
                self.after(50, lambda: self._extreme_window_kill())
                self.after(100, lambda: self._extreme_window_kill())
                self.after(200, lambda: self._extreme_window_kill())
                self.after(500, lambda: self._extreme_window_kill())
            except Exception as e:
                fc_log("threshold", f'[AVISO] Erro no fechamento extremo (unificado): {e}')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro no cancelamento unificado: {e}')


    
    # Função force_close() removida - não é mais necessária
    
    def add_test_to_history(self, tag_info, results, description, attenuator=None, distance=None, test_start_datetime=None, test_end_datetime=None, test_duration=None):
        """SISTEMA ÚNICO DE MARCAÇÃO - Adiciona teste ao histórico com checkbox marcado por padrão e medição de tempo"""
        
        fc_log("threshold", f'[NOTE] SISTEMA ÚNICO: Adicionando teste ao histórico: {description}')       
        # Verifica se já existe um teste com a mesma descrição e EPC recente (últimos 10 segundos)
        current_time = datetime.now()
        recent_tests = []
        
        for test in self.test_history:
            test_time = self._parse_test_history_date_time(test.get('date_time', ''))
            if test_time is None:
                continue
            try:
                time_diff = (current_time - test_time).total_seconds()
                if (
                    test.get('description') == description
                    and test.get('epc') == tag_info['epc']
                    and time_diff < 10
                ):
                    recent_tests.append(test)
                    fc_log(
                        "threshold",
                        f"[AVISO] SISTEMA ÚNICO: Teste duplicado encontrado: ID {test.get('id')}, "
                        f"descrição '{description}', tempo: {time_diff:.1f}s",
                    )
            except Exception as e:
                fc_log("threshold", f'[AVISO] SISTEMA ÚNICO: Erro ao verificar teste: {e}')
        
        # Se já existe um teste recente com a mesma descrição, não adiciona
        if recent_tests:
            fc_log("threshold", f"[AVISO] SISTEMA ÚNICO: Teste '{description}' já existe no histórico (evitando duplicação)")
            fc_log("threshold", f'[AVISO] SISTEMA ÚNICO: Testes recentes encontrados: {len(recent_tests)}')
            return
        
        # CORREÇÃO CRÍTICA: Usa o ID esperado em vez de incrementar o contador
        if hasattr(self, 'expected_test_id') and self.expected_test_id:
            test_id = self.expected_test_id
            self.test_counter = test_id  # Atualiza o contador para manter consistência
            fc_log("threshold", f"[INFO] DEBUG COR: Salvando teste com ID esperado = {test_id} (current_test_id = {getattr(self, 'current_test_id', 'NÃO EXISTE')})")
        else:
            # Fallback: incrementa o contador se não houver ID esperado
            self.test_counter += 1
            test_id = self.test_counter
            fc_log("threshold", f"[INFO] DEBUG COR: Fallback - Salvando teste com ID = {test_id} (current_test_id = {getattr(self, 'current_test_id', 'NÃO EXISTE')})")
        # Calibração ativa no momento do teste: ficheiro JSON (basename) e nome do perfil
        calibration_name = ""
        calibration_file = ""
        try:
            from .calibration_service import has_active_calibration, get_active_profile
            if has_active_calibration():
                profile = get_active_profile() or {}
                calibration_name = profile.get("profile_name") or profile.get("name") or ""
                calibration_file = (profile.get("calibration_source_file") or "").strip()
        except ImportError:
            pass

        test_record = {
            'id': test_id,
            'epc': tag_info['epc'],  # Sempre o EPC de 24 dígitos
            'tag_name': tag_info['name'] if tag_info['name'] else "",  # Apelido se fornecido, senão vazio
            'description': description,
            'date_time': current_time.strftime("%d-%m-%Y %H:%M"),
            'show_in_graphs': True,  # SISTEMA ÚNICO: Checkbox marcado por padrão
            'results': results,
            'tag_info': tag_info,
            'attenuator': attenuator,  # Valor do atenuador usado no teste
            'distance': distance,      # Valor da distância usada no teste
            'calibration': calibration_name,  # Nome do perfil (legado / exportação)
            'calibration_file': calibration_file,  # Nome do ficheiro .json carregado/gravado
            'test_start_time': test_start_datetime.strftime("%d-%m-%Y %H:%M:%S") if test_start_datetime else '',
            'test_end_time': test_end_datetime.strftime("%d-%m-%Y %H:%M:%S") if test_end_datetime else '',
            'test_duration_seconds': test_duration if test_duration else ''
        }
        
        fc_log("threshold", f'[DATA] SISTEMA ÚNICO: Criando registro de teste: ID {self.test_counter}')        
        # Salva no banco de dados
        success = self.database.add_test_to_history(test_record)
        
        if success:
            # Atualiza a lista em memória sincronizada
            self.test_history = self.database.get_test_history()
            fc_log("threshold", f'[OK] SISTEMA ÚNICO: Teste {self.test_counter} adicionado ao histórico (Total: {len(self.test_history)} testes)')           
            # Atualiza interface e gráficos (SISTEMA ÚNICO)
            self.update_history_tree()
            self.update_graphs_from_history()
            
            # CORREÇÃO: Força sincronização após adicionar novo teste
            fc_log("threshold", '[INFO] SISTEMA ÚNICO: Forçando sincronização após novo teste...')
            self._force_sync_database_interface()
            
            # Marca que o estado foi alterado
            self._mark_state_changed()
        else:
            fc_log("threshold", f'[ERRO] SISTEMA ÚNICO: Erro ao salvar teste no banco de dados')
            self.test_counter = max(0, self.test_counter - 1)
    
    def update_history_counter(self):
        """Atualiza o contador de testes no histórico"""
        if hasattr(self, 'history_counter_label'):
            test_count = len(self.test_history)
            if test_count == 1:
                self.history_counter_label.config(text=t('threshold.test_count_single').format(count=test_count))
            else:
                self.history_counter_label.config(text=t('threshold.tests_count').format(count=test_count))
            fc_log("threshold", f'[INFO] Contador de histórico atualizado: {test_count} testes')
    def update_history_tree(self):
        """SISTEMA ÚNICO DE MARCAÇÃO - Atualiza interface baseada no estado do banco de dados"""
        # PRESERVA SELEÇÃO ATUAL
        current_selection = None
        if hasattr(self, 'selected_test_for_table') and self.selected_test_for_table:
            tid = self.selected_test_for_table.get('id')
            if tid is not None:
                current_selection = {'id': tid}
            fc_log("threshold", f"[INFO] PRESERVANDO seleção atual: {self.selected_test_for_table.get('description', 'N/A')}")       
        # Carrega dados do banco
        fc_log("threshold", '[INFO] SISTEMA ÚNICO: Carregando dados do banco...')
        self.test_history = self.database.get_test_history()
        
        # Limpa a árvore da área principal
        if hasattr(self, 'tree'):
            for item in self.tree.get_children():
                self.tree.delete(item)
        
        fc_log("threshold", f'[DATA] SISTEMA ÚNICO: Atualizando interface com {len(self.test_history)} testes')        
        # Insere os testes do histórico na tabela principal (SISTEMA ÚNICO)
        selected_item_id = None
        for row_index, test in enumerate(self.test_history):
            # USA APENAS O ESTADO DO BANCO DE DADOS - PERSISTÊNCIA REAL
            checkbox_state = "☑" if test.get('show_in_graphs', False) else "☐"
            fc_log("threshold", f"[INFO] Usando estado do banco para {test.get('description', 'N/A')}: {checkbox_state}")
            
            # Obtém o apelido da tag (se fornecido)
            tag_name = test.get('tag_name', "")
            
            # Obtém valores de atenuador e distância (com fallback para valores padrão)
            attenuator = test.get('attenuator', 0.0)
            distance = test.get('distance', 1.0)
            
            # Formata os valores para exibição
            attenuator_str = f"{attenuator:.1f}" if attenuator is not None else "0.0"
            distance_str = f"{distance:.2f}" if distance is not None else "1.00"
            calibration_file_str = (test.get('calibration_file') or "").strip()
            calibration_name_str = (test.get('calibration') or "").strip()
            # Testes só estatísticos: sem vínculo a perfil → célula em branco; restantes mantêm "sem calibração"
            _epc_disp = (test.get('epc') or '').strip()
            _epc_low = _epc_disp.casefold()
            is_statistical_row = bool(test.get('is_statistical')) or bool(test.get('stat_type')) or (
                _epc_low.startswith('estatístico') or _epc_low.startswith('estatistico')
            )
            if calibration_file_str or calibration_name_str:
                calibration_str = calibration_file_str or calibration_name_str
            elif is_statistical_row:
                calibration_str = ""
            else:
                calibration_str = t('threshold.calibration_history_none')

            # Formata o tempo de duração do teste
            test_duration = test.get('test_duration_seconds', '')
            if test_duration and test_duration != '':
                if isinstance(test_duration, (int, float)):
                    time_str = f"{test_duration:.1f}s"
                else:
                    time_str = str(test_duration)
            else:
                time_str = "N/A"
            
            # Formata timestamp conforme idioma
            date_time_raw = test.get('date_time', '-')
            if date_time_raw != '-' and isinstance(date_time_raw, str) and date_time_raw:
                try:
                    from datetime import datetime
                    if 'T' in date_time_raw:
                        date_time_dt = datetime.fromisoformat(date_time_raw.replace('Z', '+00:00'))
                    else:
                        try:
                            date_time_dt = datetime.strptime(date_time_raw, '%d/%m/%Y %H:%M:%S')
                        except:
                            try:
                                date_time_dt = datetime.strptime(date_time_raw, '%Y-%m-%d %H:%M:%S')
                            except:
                                try:
                                    date_time_dt = datetime.strptime(date_time_raw, '%d-%m-%Y %H:%M:%S')
                                except:
                                    date_time_dt = None
                    if date_time_dt:
                        translator = _get_translator()
                        if translator.get_language() == 'en':
                            date_time_formatted = date_time_dt.strftime('%m/%d/%y %H:%M:%S')
                        else:
                            date_time_formatted = date_time_dt.strftime('%d/%m/%Y %H:%M:%S')
                    else:
                        date_time_formatted = date_time_raw
                except:
                    date_time_formatted = date_time_raw
            else:
                date_time_formatted = date_time_raw
            
            # Insere na área principal (iid = id do teste: data/hora na célula é formatada, não bate com o valor da BD)
            row_iid = str(test.get('id', '')) if test.get('id') is not None else None
            insert_kw = dict(
                values=(
                checkbox_state,      # Plot (primeira coluna)
                test['description'], # Nome do Teste (segunda coluna)
                test['epc'],         # EPC de 24 dígitos
                calibration_str,     # Perfil/ficheiro; vazio se estatístico; "sem calibração" se teste normal sem dados
                attenuator_str,      # Valor do atenuador
                distance_str,        # Valor da distância
                date_time_formatted,  # Data/Hora formatada conforme idioma
                time_str
            ))
            try:
                if row_iid:
                    item_id = self.tree.insert('', 'end', iid=row_iid, **insert_kw)
                else:
                    item_id = self.tree.insert('', 'end', **insert_kw)
            except tk.TclError as e:
                fc_log(
                    "threshold",
                    f"[AVISO] iid Treeview inválido ou duplicado ({row_iid!r}): {e} — usando hist_{row_index}",
                )
                item_id = self.tree.insert('', 'end', iid=f"hist_{row_index}", **insert_kw)
            
            # VERIFICA SE É O ITEM SELECIONADO ANTERIORMENTE
            if current_selection and str(test.get('id', '')) == str(current_selection.get('id', '')):
                selected_item_id = item_id
                fc_log("threshold", f"[INFO] RESTAURANDO seleção para: {test.get('description', 'N/A')}")           
            fc_log("threshold", f"   - SISTEMA ÚNICO: Teste {test['id']}: {test.get('description', 'Sem descrição')} - {checkbox_state}")      
        # RESTAURA SELEÇÃO VISUAL
        if selected_item_id:
            self._highlight_selected_test(selected_item_id)
            fc_log("threshold", f'[OK] Seleção visual restaurada')       
        # Scroll para a linha relevante (sem selection_set: evita “várias linhas” ativas com o destaque azul da tabela)
        if hasattr(self, 'tree') and self.tree.get_children():
            target = selected_item_id or self.tree.get_children()[-1]
            try:
                self.tree.see(target)
            except tk.TclError:
                self.tree.see(self.tree.get_children()[-1])
            fc_log("threshold", '📜 Scroll para linha visível (seleção para tabela = destaque único)')    
        # NOVO: Atualiza o contador de testes no histórico
        self.update_history_counter()
        
        # CORREÇÃO: Força verificação de sincronização após atualização
        self._verify_sync_consistency()
        
        fc_log("threshold", '[OK] SISTEMA ÚNICO: Interface atualizada com dados do banco')    
    def _verify_sync_consistency(self):
        """Verifica e corrige inconsistências entre banco de dados e interface"""
        try:
            if not hasattr(self, 'tree') or not self.tree.get_children():
                return
            
            fc_log("threshold", '[INFO] Verificando consistência de sincronização...')          
            # Conta inconsistências
            inconsistencies = 0
            
            for item in self.tree.get_children():
                current_checkbox = self.tree.set(item, 'Plot')
                test = self._test_for_history_tree_item(item)
                
                if test:
                    expected_checkbox = "☑" if test.get('show_in_graphs', False) else "☐"
                    
                    if current_checkbox != expected_checkbox:
                        fc_log("threshold", f"[AVISO] INCONSISTÊNCIA: {test.get('description', 'N/A')} - Interface: {current_checkbox}, Banco: {expected_checkbox}")
                        # Corrige a inconsistência
                        self.tree.set(item, 'Plot', expected_checkbox)
                        inconsistencies += 1
            
            if inconsistencies > 0:
                fc_log("threshold", f'[INFO] {inconsistencies} inconsistências corrigidas')
                # Atualiza gráficos após correção
                self.update_graphs_from_history()
            else:
                fc_log("threshold", '[OK] Sincronização consistente')               
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro na verificação de consistência: {e}')  
    def _force_sync_database_interface(self):
        """FORÇA SINCRONIZAÇÃO COMPLETA entre banco de dados e interface visual"""
        try:
            fc_log("threshold", '[INFO] INICIANDO SINCRONIZAÇÃO FORÇADA...')          
            # 1. Recarrega dados do banco para garantir estado atual
            self.test_history = self.database.get_test_history()
            fc_log("threshold", f'[DATA] Dados recarregados do banco: {len(self.test_history)} testes')
            
            # 2. Verifica se há testes marcados no banco que não estão visuais
            marked_in_db = [t for t in self.test_history if t.get('show_in_graphs', False)]
            fc_log("threshold", f'[INFO] Testes marcados no banco: {len(marked_in_db)}')
            
            # 3. Verifica se há testes marcados na interface que não estão no banco
            marked_in_interface = []
            if hasattr(self, 'tree'):
                for item in self.tree.get_children():
                    checkbox_state = self.tree.set(item, 'Plot')
                    if checkbox_state == "☑":
                        trow = self._test_for_history_tree_item(item)
                        if trow:
                            marked_in_interface.append((trow.get('epc'), trow.get('date_time')))
            
            fc_log("threshold", f'[INFO] Testes marcados na interface: {len(marked_in_interface)}')
            
            # 4. Força sincronização: interface deve refletir exatamente o banco
            sync_count = 0
            if hasattr(self, 'tree'):
                for item in self.tree.get_children():
                    test = self._test_for_history_tree_item(item)
                    
                    if test:
                        # Sincroniza checkbox com estado do banco
                        expected_state = "☑" if test.get('show_in_graphs', False) else "☐"
                        current_state = self.tree.set(item, 'Plot')
                        
                        if current_state != expected_state:
                            fc_log("threshold", f"[INFO] SINCRONIZANDO: {test.get('description', 'N/A')} - {current_state} → {expected_state}")
                            self.tree.set(item, 'Plot', expected_state)
                            sync_count += 1
                        else:
                            fc_log("threshold", f"[OK] JÁ SINCRONIZADO: {test.get('description', 'N/A')} - {current_state}")
                    else:
                        fc_log("threshold", f'[AVISO] Teste não encontrado no banco (iid={item!r})')
            fc_log("threshold", f'[INFO] {sync_count} itens sincronizados na interface')
            
            # 5. Atualiza gráficos com estado sincronizado
            fc_log("threshold", '[INFO] Atualizando gráficos com estado sincronizado...')
            self.update_graphs_from_history()
            
            fc_log("threshold", '[OK] SINCRONIZAÇÃO FORÇADA CONCLUÍDA')         
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro durante sincronização forçada: {e}')
            import traceback
            traceback.print_exc()
    
    def _clear_all_test_selections(self):
        """Limpa todas as seleções de testes (marca todos como não selecionados)"""
        try:
            fc_log("threshold", '🧹 INICIANDO LIMPEZA DE TODAS AS SELEÇÕES...')        
            # 1. Limpa seleção específica para tabela
            if hasattr(self, 'selected_test_for_table'):
                self.selected_test_for_table = None
                fc_log("threshold", '🧹 Seleção específica para tabela limpa')       
            # 2. Desmarca todos os testes no banco de dados
            cleared_count = 0
            for test in self.test_history:
                if test.get('show_in_graphs', False):
                    success = self.database.update_test_visibility(test['id'], False)
                    if success:
                        test['show_in_graphs'] = False
                        cleared_count += 1
                        fc_log("threshold", f"🧹 Teste {test.get('description', 'N/A')} desmarcado no banco")
                    else:
                        fc_log("threshold", f"[AVISO] Erro ao desmarcar teste {test.get('description', 'N/A')}")
            
            fc_log("threshold", f'🧹 {cleared_count} testes desmarcados no banco de dados')          
            # 3. Atualiza interface visual
            if hasattr(self, 'tree'):
                for item in self.tree.get_children():
                    self.tree.set(item, 'Plot', '☐')
                fc_log("threshold", '🧹 Interface visual atualizada')          
            # 4. Remove destaque de seleção
            if hasattr(self, 'tree'):
                for item in self.tree.get_children():
                    self.tree.item(item, tags=())
                fc_log("threshold", '🧹 Destaque de seleção removido')        
            # 5. Atualiza gráficos
            fc_log("threshold", '[INFO] Atualizando gráficos após limpeza...')
            self.update_graphs_from_history()
            
            fc_log("threshold", '[OK] LIMPEZA DE SELEÇÕES CONCLUÍDA')          
            # 6. Notifica usuário
            messagebox.showinfo(t('threshold.selections_cleared'), t('threshold.selections_cleared_msg').format(count=cleared_count))
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro durante limpeza de seleções: {e}')
            import traceback
            traceback.print_exc()
    
    def show_realtime_table(self, test_id=None):
        """Mostra tabela em tempo real com dados linha por linha até encontrar threshold"""
        try:
            # Determina qual teste usar
            if test_id is None:
                if hasattr(self, 'selected_test_for_table') and self.selected_test_for_table:
                    test = self.selected_test_for_table
                else:
                    # Usa o primeiro teste marcado se não houver seleção específica
                    marked_tests = [t for t in self.test_history if t.get('show_in_graphs', False)]
                    if not marked_tests:
                        messagebox.showwarning(
                            t('threshold.no_test_selected_title'),
                            t('threshold.no_test_selected_realtime_msg'))
                        return
                    test = marked_tests[0]
            else:
                test = next((t for t in self.test_history if t['id'] == test_id), None)
                if not test:
                    messagebox.showerror(t('threshold.error_test_not_found'), t('threshold.error_test_not_found_msg').format(id=test_id))
                    return
            
            fc_log("threshold", f"[INFO] INICIANDO TABELA EM TEMPO REAL para: {test.get('description', 'N/A')}")
            
            # Cria janela de tabela em tempo real
            self._create_realtime_table_window(test)
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao mostrar tabela em tempo real: {e}')
            import traceback
            traceback.print_exc()
            messagebox.showerror(
                t('threshold.error'),
                t('threshold.error_showing_realtime_table_msg').format(error=str(e)))
    
    def _create_realtime_table_window(self, test):
        """Cria janela da tabela em tempo real"""
        try:
            # Cria nova janela
            realtime_window = tk.Toplevel(self.root)
            # Armazena referência da janela para poder fechá-la durante o cancelamento
            self.realtime_window = realtime_window
            realtime_window.title(f"Tabela em Tempo Real - {test.get('description', 'N/A')}")
            realtime_window.geometry("1200x800")
            realtime_window.configure(bg='#f0f0f0')
            
            # Configura janela como modal
            realtime_window.transient(self.root)
            realtime_window.grab_set()
            
            # Adiciona callback para limpar referência ao fechar a janela
            def on_window_close():
                # Cancela atualização automática ao fechar
                if hasattr(self, 'realtime_refresh_after_id') and self.realtime_refresh_after_id is not None:
                    try:
                        self.after_cancel(self.realtime_refresh_after_id)
                        self.realtime_refresh_after_id = None
                        fc_log("threshold", '🛑 Agendamento de atualização cancelado ao fechar janela')
                    except Exception as e:
                        fc_log("threshold", f'[AVISO] Erro ao cancelar agendamento: {e}')
                
                # Limpa referências
                if hasattr(self, 'realtime_window'):
                    self.realtime_window = None
                if hasattr(self, 'realtime_auto_refresh_var'):
                    self.realtime_auto_refresh_var = None
                
                realtime_window.destroy()
            
            realtime_window.protocol("WM_DELETE_WINDOW", on_window_close)
            
            # Frame principal
            main_frame = ttk.Frame(realtime_window)
            main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
            
            # Cabeçalho
            header_frame = ttk.Frame(main_frame)
            header_frame.pack(fill=tk.X, pady=(0, 10))
            
            # Título principal
            title_frame = ttk.Frame(header_frame)
            title_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
            
            ttk.Label(title_frame, text="[INFO] TABELA EM TEMPO REAL", 
                     font=('Arial', 16, 'bold')).pack(side=tk.LEFT)
            
            # Informações do teste (incluindo tempo total)
            test_info_frame = ttk.Frame(title_frame)
            test_info_frame.pack(side=tk.LEFT, padx=(20, 0))
            
            # Tempo total do teste
            test_duration = test.get('test_duration_seconds', None)
            if test_duration:
                duration_text = f"[TIMER] Tempo Total: {test_duration:.1f}s"
                duration_color = '#2E8B57'  # Verde escuro para destaque
            else:
                duration_text = "[TIMER] Tempo Total: N/A"
                duration_color = '#666666'  # Cinza para N/A
            
            duration_label = ttk.Label(test_info_frame, text=duration_text, 
                                     font=('Arial', 10, 'bold'), foreground=duration_color)
            duration_label.pack(side=tk.LEFT)
            
            # Botões de controle
            control_frame = ttk.Frame(header_frame)
            control_frame.pack(side=tk.RIGHT)
            
            refresh_btn = ttk.Button(control_frame, text="[INFO] Atualizar", 
                                   command=lambda: self._refresh_realtime_table(realtime_tree, test))
            refresh_btn.pack(side=tk.LEFT, padx=(0, 5))
            
            auto_refresh_var = tk.BooleanVar(value=True)
            # Armazena referência da variável de controle para poder cancelar a atualização
            self.realtime_auto_refresh_var = auto_refresh_var
            auto_refresh_cb = ttk.Checkbutton(control_frame, text="Atualização Automática", 
                                            variable=auto_refresh_var)
            auto_refresh_cb.pack(side=tk.LEFT)
            
            # Cria Treeview para dados em tempo real
            columns = ('timestamp', 'power', 'frequency', 'rssi', 'threshold_status', 'test_time', 'notes')
            realtime_tree = ttk.Treeview(main_frame, columns=columns, show='headings', height=20)
            
            # Configura colunas
            realtime_tree.heading('timestamp', text='[TIME] Timestamp')
            realtime_tree.heading('power', text='[RF] Power (dBm)')
            realtime_tree.heading('frequency', text='[INFO] Freq (MHz)')
            realtime_tree.heading('rssi', text='📶 RSSI')
            realtime_tree.heading('threshold_status', text='[TARGET] Status Threshold')
            realtime_tree.heading('test_time', text='[TIMER] Tempo Teste')
            realtime_tree.heading('notes', text='[NOTE] Observações')
            
            # Configura larguras das colunas
            realtime_tree.column('timestamp', width=150)
            realtime_tree.column('power', width=120)
            realtime_tree.column('frequency', width=120)
            realtime_tree.column('rssi', width=100)
            realtime_tree.column('threshold_status', width=150)
            realtime_tree.column('test_time', width=120)
            realtime_tree.column('notes', width=180)
            
            # Scrollbar
            scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=realtime_tree.yview)
            realtime_tree.configure(yscrollcommand=scrollbar.set)
            
            # Pack dos elementos
            realtime_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
            
            # Status bar
            status_frame = ttk.Frame(main_frame)
            status_frame.pack(fill=tk.X, pady=(10, 0))
            
            status_label = ttk.Label(status_frame, text="⏳ Aguardando dados...", 
                                   font=('Arial', 10))
            status_label.pack(side=tk.LEFT)
            
            # Carrega dados iniciais
            self._load_realtime_data(realtime_tree, test, status_label)
            
            # Configura atualização automática se habilitada
            if auto_refresh_var.get():
                self._setup_auto_refresh(realtime_tree, test, status_label, auto_refresh_var)
            
            fc_log("threshold", f"[OK] Janela de tabela em tempo real criada para teste: {test.get('description', 'N/A')}")
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao criar janela de tabela em tempo real: {e}')
            import traceback
            traceback.print_exc()
    
    def _load_realtime_data(self, tree, test, status_label):
        """Carrega dados em tempo real na tabela"""
        try:
            # Limpa dados existentes
            for item in tree.get_children():
                tree.delete(item)
            
            # Obtém resultados do teste
            results = test.get('results', [])
            if not results:
                status_label.config(text="[AVISO] Nenhum resultado encontrado para este teste")
                return
            
            fc_log("threshold", f'[INFO] Carregando {len(results)} resultados em tempo real...')
            
            # Processa cada resultado
            threshold_found = False
            for i, result in enumerate(results):
                # Determina status do threshold
                threshold_status = self._analyze_threshold_status(result, test)
                
                # Destaca se threshold foi encontrado
                tags = []
                if threshold_status == "THRESHOLD ENCONTRADO! [TARGET]":
                    threshold_found = True
                    tags.append('threshold_found')
                
                # Calcula tempo de teste para este resultado
                test_time_str = "N/A"
                if hasattr(self, 'test_duration'):
                    # Mostra a duração total do teste (do início ao fim)
                    test_time_str = f"{self.test_duration:.1f}s"
                
                # Formata timestamp conforme idioma
                timestamp_raw = result.get('timestamp', 'N/A')
                if timestamp_raw != 'N/A' and isinstance(timestamp_raw, str) and timestamp_raw:
                    try:
                        from datetime import datetime
                        if 'T' in timestamp_raw:
                            timestamp_dt = datetime.fromisoformat(timestamp_raw.replace('Z', '+00:00'))
                        else:
                            try:
                                timestamp_dt = datetime.strptime(timestamp_raw, '%d/%m/%Y %H:%M:%S')
                            except:
                                try:
                                    timestamp_dt = datetime.strptime(timestamp_raw, '%Y-%m-%d %H:%M:%S')
                                except:
                                    timestamp_dt = None
                        if timestamp_dt:
                            translator = _get_translator()
                            if translator.get_language() == 'en':
                                timestamp_formatted = timestamp_dt.strftime('%m/%d/%y %H:%M:%S')
                            else:
                                timestamp_formatted = timestamp_dt.strftime('%d/%m/%Y %H:%M:%S')
                        else:
                            timestamp_formatted = timestamp_raw
                    except:
                        timestamp_formatted = timestamp_raw
                else:
                    timestamp_formatted = timestamp_raw
                
                # Insere na tabela
                item = tree.insert('', 'end', values=(
                    timestamp_formatted,  # Data/Hora formatada conforme idioma
                    f"{result.get('power', 'N/A')} dBm",
                    f"{result.get('frequency', 'N/A')} MHz",
                    f"{result.get('rssi', 'N/A')}",
                    threshold_status,
                    test_time_str,
                    result.get('notes', '')
                ), tags=tags)
                
                # Destaca visualmente se threshold foi encontrado
                if threshold_found:
                    tree.tag_configure('threshold_found', background='#90EE90', foreground='black')
            
            # Atualiza status
            if threshold_found:
                status_label.config(text="[TARGET] THRESHOLD ENCONTRADO! Teste concluído com sucesso.")
            else:
                status_label.config(text=f"[INFO] {len(results)} resultados carregados. Threshold ainda não encontrado.")
            
            fc_log("threshold", f'[OK] Dados em tempo real carregados: {len(results)} resultados')
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao carregar dados em tempo real: {e}')
            import traceback
            traceback.print_exc()
            status_label.config(text=f"[ERRO] Erro ao carregar dados: {e}")
    
    def _analyze_threshold_status(self, result, test):
        """Analisa se o resultado indica que o threshold foi encontrado"""
        try:
            # Obtém parâmetros do teste
            target_power = test.get('target_power', 0)
            target_frequency = test.get('target_frequency', 0)
            threshold_rssi = test.get('threshold_rssi', -50)
            
            # Verifica se os parâmetros estão dentro do threshold
            power = result.get('power', 0)
            frequency = result.get('frequency', 0)
            rssi = result.get('rssi', -100)
            
            # Lógica para determinar se threshold foi encontrado
            # (pode ser personalizada conforme necessário)
            if (abs(power - target_power) <= 2 and  # Power dentro de ±2 dBm
                abs(frequency - target_frequency) <= 5 and  # Freq dentro de ±5 MHz
                rssi >= threshold_rssi):  # RSSI acima do threshold
                return "THRESHOLD ENCONTRADO! [TARGET]"
            elif rssi >= threshold_rssi:
                return "RSSI OK 📶"
            elif abs(power - target_power) <= 2:
                return "Power OK [RF]"
            elif abs(frequency - target_frequency) <= 5:
                return "Freq OK [INFO]"
            else:
                return "Aguardando... ⏳"
                
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao analisar status do threshold: {e}')
            return "Erro na análise [ERRO]"
    
    def _refresh_realtime_table(self, tree, test):
        """Atualiza manualmente a tabela em tempo real"""
        try:
            # VERIFICAÇÃO ADICIONAL: Não atualizar se teste foi cancelado
            if (hasattr(self, 'global_test_cancelled') and self.global_test_cancelled) or \
               (not hasattr(self, 'test_in_progress') or not self.test_in_progress):
                fc_log("threshold", '🛑 _refresh_realtime_table: Teste cancelado - não atualizando tabela')
                return
            
            fc_log("threshold", '[INFO] _refresh_realtime_table: Atualizando tabela em tempo real...')
            # Recarrega dados do banco
            self.test_history = self.database.get_test_history()
            updated_test = next((t for t in self.test_history if t['id'] == test['id']), None)
            
            if updated_test:
                # Atualiza status label
                status_label = None
                for widget in tree.master.winfo_children():
                    if isinstance(widget, ttk.Frame) and hasattr(widget, 'winfo_children'):
                        for child in widget.winfo_children():
                            if isinstance(child, ttk.Label) and 'Aguardando' in child.cget('text'):
                                status_label = child
                                break
                
                self._load_realtime_data(tree, updated_test, status_label)
                fc_log("threshold", '[OK] Tabela em tempo real atualizada')
            else:
                fc_log("threshold", '[ERRO] Teste não encontrado para atualização')              
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao atualizar tabela em tempo real: {e}')
            import traceback
            traceback.print_exc()
    
    def _setup_auto_refresh(self, tree, test, status_label, auto_refresh_var):
        """Configura atualização automática da tabela"""
        # Armazena referências como atributos da instância para evitar problemas de escopo
        self._current_auto_refresh_tree = tree
        self._current_auto_refresh_test = test
        self._current_auto_refresh_var = auto_refresh_var
        
        # Inicia primeira atualização automática
        self.realtime_refresh_after_id = self.after(2000, lambda: self._execute_auto_refresh())
    
    def _execute_auto_refresh(self):
        """Executa atualização automática da tabela (método da instância)"""
        try:
            # LOG DETALHADO para debug
            auto_refresh_enabled = getattr(self, '_current_auto_refresh_var', None)
            if auto_refresh_enabled:
                auto_refresh_enabled = auto_refresh_enabled.get()
            else:
                auto_refresh_enabled = False
                
            test_in_progress = getattr(self, 'test_in_progress', False)
            global_cancelled = getattr(self, 'global_test_cancelled', False)
            
            fc_log("threshold", f'[INFO] AUTO_REFRESH: auto_refresh_enabled={auto_refresh_enabled}, test_in_progress={test_in_progress}, global_cancelled={global_cancelled}')
            
            # Verifica RIGOROSAMENTE se deve continuar a atualização
            should_continue = (
                auto_refresh_enabled and 
                test_in_progress and 
                not global_cancelled
            )
            
            fc_log("threshold", f'[INFO] AUTO_REFRESH: should_continue={should_continue}')
            
            if should_continue:
                # Verifica se a janela ainda existe antes de atualizar
                try:
                    tree = getattr(self, '_current_auto_refresh_tree', None)
                    test = getattr(self, '_current_auto_refresh_test', None)
                    
                    if tree and tree.winfo_exists():
                        fc_log("threshold", '[INFO] AUTO_REFRESH: Atualizando tabela...')
                        self._refresh_realtime_table(tree, test)
                        # Agenda próxima atualização em 2 segundos
                        self.realtime_refresh_after_id = self.after(2000, lambda: self._execute_auto_refresh())
                        fc_log("threshold", '[INFO] AUTO_REFRESH: Próxima atualização agendada')
                    else:
                        fc_log("threshold", '🛑 AUTO_REFRESH: Janela de realtime foi fechada - parando atualização automática')
                        return
                except tk.TclError:
                    fc_log("threshold", '🛑 AUTO_REFRESH: Janela de realtime foi destruída - parando atualização automática')
                    return
            else:
                fc_log("threshold", '🛑 AUTO_REFRESH: Atualização automática PARADA (teste cancelado ou desabilitado)')
                return
                
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro na atualização automática: {e}')
            import traceback
            traceback.print_exc()
    
    # Função on_history_click removida - estava causando marcação dupla
    # Agora apenas on_main_tree_click() lida com cliques no checkbox
    
    def on_main_tree_click(self, event):
        """SISTEMA ÚNICO DE MARCAÇÃO - Clique nos checkboxes da interface + Seleção para tabela"""
        region = self.tree.identify("region", event.x, event.y)
        fc_log("threshold", f'[INFO] CLIQUE: region={region}')
        
        if region == "cell":
            column = self.tree.identify_column(event.x)
            item = self.tree.identify_row(event.y)
            fc_log("threshold", f'[INFO] CLIQUE: column={column}, item={item}')
            
            if column == '#1' and item:  # Coluna do checkbox
                fc_log("threshold", f'[INFO] SISTEMA ÚNICO: Checkbox clicado!')                
                test = self._test_for_history_tree_item(item)
                
                if test:
                    # Inverte o estado do checkbox (SISTEMA ÚNICO)
                    current_checkbox = self.tree.set(item, 'Plot')
                    new_checkbox_state = "☑" if current_checkbox == "☐" else "☐"
                    self.tree.set(item, 'Plot', new_checkbox_state)
                    
                    fc_log("threshold", f"[INFO] SISTEMA ÚNICO: Checkbox alterado para teste {test['id']} ({test['description']}): {current_checkbox} → {new_checkbox_state}")                  
                    # SALVA NO BANCO (sincronização)
                    new_state = (new_checkbox_state == "☑")
                    success = self.database.update_test_visibility(test['id'], new_state)
                    if success:
                        fc_log("threshold", f'[OK] SISTEMA ÚNICO: Estado salvo no banco de dados')
                        # Atualiza o estado local para manter sincronização
                        test['show_in_graphs'] = new_state
                        
                        # Atualiza apenas os gráficos (sem recarregar toda a interface)
                        self.update_graphs_from_history()
                    else:
                        fc_log("threshold", f'[ERRO] SISTEMA ÚNICO: Erro ao salvar no banco de dados')
                        # Reverte o estado visual se falhou
                        self.tree.set(item, 'Plot', current_checkbox)
                else:
                    fc_log("threshold", f'[ERRO] SISTEMA ÚNICO: Teste não encontrado para iid={item!r}')          
            elif item:  # Qualquer coluna exceto checkbox: uma única linha “para tabela”
                fc_log("threshold", f'[INFO] SELEÇÃO PARA TABELA: linha selecionada (coluna {column})')
                self.select_test_for_table(item)
            

    
    def on_tree_double_click(self, event):
        """Manipula o duplo-clique para edição in-line da descrição do teste."""
        
        # 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)
        # A coluna Nome do Teste é agora a coluna #2 (segunda coluna)
        if column_id != '#2':
            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)

        # Cria e posiciona o widget de edição
        current_description = self.tree.set(item_id, 'Nome do Teste')
        self._edit_var = tk.StringVar(value=current_description)
        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_description(item_id))
        self._edit_entry.bind("<FocusOut>", lambda e: self.save_edited_description(item_id))
        self._edit_entry.bind("<Escape>", lambda e: self.cancel_edit_description())

    def check_duplicate_description_excluding_current(self, description, current_test_id):
        """
        Verifica se a descrição já existe no histórico, excluindo o teste atual
        
        Args:
            description: Descrição a verificar
            current_test_id: ID do teste atual que está sendo editado
            
        Returns:
            bool: True se a descrição já existe em outro teste, False caso contrário
        """
        if not self.database:
            return False
        
        try:
            history = self.database.get_test_history()
            # NOVO: Compara descrições normalizadas (sem espaços extras, case-insensitive)
            description_normalized = description.strip().lower()
            
            for test in history:
                if test.get('id') != current_test_id:  # Exclui o teste atual
                    existing_description = test.get('description', '').strip().lower()
                    if existing_description == description_normalized:
                        return True
            return False
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao verificar descrições duplicadas: {e}')
            return False

    def save_edited_description(self, item_id):
        """Salva a descrição do teste que foi editada."""
        if not (hasattr(self, '_edit_entry') and self._edit_entry.winfo_exists()):
            return
            
        try:
            new_description = self._edit_var.get().strip()
            self.cancel_edit_description() # Destrói o widget de edição

            if not new_description:
                # A descrição não pode ser vazia
                messagebox.showerror(t('threshold.error_description_empty'), t('threshold.error_description_empty_msg'))
                return

            test = self._test_for_history_tree_item(item_id)
            
            if test:
                # NOVO: Verifica se a nova descrição já existe (excluindo o teste atual)
                if self.check_duplicate_description_excluding_current(new_description, test['id']):
                    messagebox.showerror(t('threshold.error_description_duplicate'), 
                                       t('threshold.error_description_duplicate_msg').format(description=new_description), 
                                       parent=self)
                    return
                
                # Atualiza na interface
                self.tree.set(item_id, 'Nome do Teste', new_description)
                
                # Atualiza no banco de dados
                success = self.database.update_test_description(test['id'], new_description)
                if success:
                    fc_log("threshold", f"[OK] Descrição atualizada: '{test['description']}' → '{new_description}'")
                    # Atualiza o objeto local
                    test['description'] = new_description
                    
                    # Marca que o estado foi alterado
                    self._mark_state_changed()
                    fc_log("threshold", f'[OK] Descrição salva no banco de dados')                   
                    # NOVO: Atualiza o gráfico imediatamente após editar o nome
                    self.update_graphs_from_history()
                else:
                    fc_log("threshold", f'[ERRO] Erro ao salvar descrição no banco de dados')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao salvar descrição editada: {e}')

    def cancel_edit_description(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')

    def select_test_for_table(self, item):
        """Seleciona um teste específico para visualização de tabela"""
        try:
            description = self.tree.set(item, 'Nome do Teste') if item else ''
            test = self._test_for_history_tree_item(item)
            
            if test:
                # Seleciona o teste para visualização de tabela
                self.selected_test_for_table = test
                
                # Destaca visualmente o teste selecionado
                self._highlight_selected_test(item)
                
                fc_log("threshold", f"[OK] Teste selecionado para tabela: {test.get('description', 'N/A')} (ID: {test.get('id', 'N/A')})")
                
                # Habilita o botão de mostrar tabela
                if hasattr(self, 'show_table_button'):
                    self.show_table_button.config(state='normal')
                
                # Mostra mensagem de confirmação (mais discreta)
                fc_log("threshold", f"[OK] Teste '{description}' selecionado para visualização de tabela")
            else:
                fc_log("threshold", f'[ERRO] Teste não encontrado para seleção (iid={item!r})')
                messagebox.showerror(t('threshold.error_test_not_found_select'), t('threshold.error_test_not_found_select_msg'))
                
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao selecionar teste para tabela: {e}')
            messagebox.showerror(
                t('threshold.error'),
                t('threshold.error_selecting_test_exception_msg').format(error=str(e)))
    
    def _highlight_selected_test(self, item):
        """Destaca visualmente o teste selecionado para tabela"""
        try:
            # Remove destaque anterior
            for existing_item in self.tree.get_children():
                self.tree.item(existing_item, tags=())
            
            # Aplica destaque ao item selecionado
            self.tree.item(item, tags=('selected',))
            self.tree.tag_configure('selected', background='#e6f3ff')  # Azul claro
            
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro ao destacar teste selecionado: {e}')

    def edit_test_attenuator_inline(self, item):
        """Edita o atenuador do teste usando uma janela popup simples"""
        try:
            # Obtém o atenuador atual
            current_attenuator = self.tree.set(item, 'Atenuador')
            
            # Cria uma janela popup simples
            edit_window = tk.Toplevel(self)
            edit_window.title("Editar Atenuador")
            edit_window.geometry("400x150")
            edit_window.transient(self)
            edit_window.grab_set()
            
            # Centraliza a janela
            edit_window.geometry("+%d+%d" % (self.winfo_rootx() + 200, self.winfo_rooty() + 200))
            
            # Frame principal
            main_frame = tk.Frame(edit_window)
            main_frame.pack(fill='both', expand=True, padx=20, pady=20)
            
            # Informações do teste
            epc = self.tree.set(item, 'EPC')
            date_time = self.tree.set(item, 'Date Time')
            test_info = tk.Label(main_frame, text=f"Teste: {epc} | {date_time}", 
                                font=("Arial", 9), fg="gray")
            test_info.pack(pady=(0, 5))
            
            # Label
            label = tk.Label(main_frame, text="Novo valor do atenuador (dB):", font=("Arial", 10))
            label.pack(pady=(0, 10))
            
            # Campo de entrada
            entry = tk.Entry(main_frame, font=("Arial", 10), width=40)
            entry.insert(0, current_attenuator)
            entry.select_range(0, tk.END)
            entry.pack(pady=(0, 20))
            entry.focus_set()
            
            # Frame para botões
            button_frame = tk.Frame(main_frame)
            button_frame.pack()
            
            def save_attenuator():
                """Salva o novo valor do atenuador"""
                try:
                    new_attenuator = entry.get().strip()
                    
                    # Valida se é um número válido
                    try:
                        float(new_attenuator)
                    except ValueError:
                        messagebox.showerror(t('threshold.error_invalid_attenuator'), t('threshold.error_invalid_attenuator_msg'))
                        return
                    
                    test = self._test_for_history_tree_item(item)
                    
                    if test:
                        # Atualiza na interface
                        self.tree.set(item, 'Atenuador', new_attenuator)
                        
                        # Atualiza no banco de dados
                        success = self.database.update_test_attenuator(test['id'], float(new_attenuator))
                        if success:
                            fc_log("threshold", f"[OK] Atenuador atualizado: '{current_attenuator}' → '{new_attenuator}'")
                            # Atualiza o objeto local
                            test['attenuator'] = float(new_attenuator)
                            
                            # Atualiza imediatamente os gráficos se necessário
                            self.update_graphs_from_history()
                            fc_log("threshold", f'[INFO] Gráficos atualizados automaticamente')
                        else:
                            fc_log("threshold", f'[ERRO] Erro ao salvar atenuador no banco de dados')
                    
                    edit_window.destroy()
                except Exception as e:
                    fc_log("threshold", f'[ERRO] Erro ao salvar atenuador: {e}')
                    edit_window.destroy()
            
            def cancel_edit():
                """Cancela a edição"""
                edit_window.destroy()
            
            # Botões
            save_button = tk.Button(button_frame, text="Salvar", command=save_attenuator, 
                                  bg='green', fg='white', font=("Arial", 9))
            save_button.pack(side='left', padx=10)
            
            cancel_button = tk.Button(button_frame, text="Cancelar", command=cancel_edit,
                                    bg='red', fg='white', font=("Arial", 9))
            cancel_button.pack(side='left')
            
            # Bind eventos
            entry.bind('<Return>', lambda e: save_attenuator())
            entry.bind('<Escape>', lambda e: cancel_edit())
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao iniciar edição do atenuador: {e}')   
    def edit_test_distance_inline(self, item):
        """Edita a distância do teste usando uma janela popup simples"""
        try:
            # Obtém a distância atual
            current_distance = self.tree.set(item, 'Distância')
            
            # Cria uma janela popup simples
            edit_window = tk.Toplevel(self)
            edit_window.title("Editar Distância")
            edit_window.geometry("400x150")
            edit_window.transient(self)
            edit_window.grab_set()
            
            # Centraliza a janela
            edit_window.geometry("+%d+%d" % (self.winfo_rootx() + 200, self.winfo_rooty() + 200))
            
            # Frame principal
            main_frame = tk.Frame(edit_window)
            main_frame.pack(fill='both', expand=True, padx=20, pady=20)
            
            # Informações do teste
            epc = self.tree.set(item, 'EPC')
            date_time = self.tree.set(item, 'Date Time')
            test_info = tk.Label(main_frame, text=f"Teste: {epc} | {date_time}", 
                                font=("Arial", 9), fg="gray")
            test_info.pack(pady=(0, 5))
            
            # Label
            label = tk.Label(main_frame, text="Nova distância (m):", font=("Arial", 10))
            label.pack(pady=(0, 10))
            
            # Campo de entrada
            entry = tk.Entry(main_frame, font=("Arial", 10), width=40)
            entry.insert(0, current_distance)
            entry.select_range(0, tk.END)
            entry.pack(pady=(0, 20))
            entry.focus_set()
            
            # Frame para botões
            button_frame = tk.Frame(main_frame)
            button_frame.pack()
            
            def save_distance():
                """Salva a nova distância"""
                try:
                    new_distance = entry.get().strip()
                    
                    # Valida se é um número válido
                    try:
                        float(new_distance)
                    except ValueError:
                        messagebox.showerror(t('threshold.error_invalid_distance'), t('threshold.error_invalid_distance_msg'))
                        return
                    
                    test = self._test_for_history_tree_item(item)
                    
                    if test:
                        # Atualiza na interface
                        self.tree.set(item, 'Distância', new_distance)
                        
                        # Atualiza no banco de dados
                        success = self.database.update_test_distance(test['id'], float(new_distance))
                        if success:
                            fc_log("threshold", f"[OK] Distância atualizada: '{current_distance}' → '{new_distance}'")
                            # Atualiza o objeto local
                            test['distance'] = float(new_distance)
                            
                            # Atualiza imediatamente os gráficos se necessário
                            self.update_graphs_from_history()
                            fc_log("threshold", f'[INFO] Gráficos atualizados automaticamente')
                        else:
                            fc_log("threshold", f'[ERRO] Erro ao salvar distância no banco de dados')
                    edit_window.destroy()
                except Exception as e:
                    fc_log("threshold", f'[ERRO] Erro ao salvar distância: {e}')
                    edit_window.destroy()
            
            
            def save_description():
                """Salva a nova descrição"""
                try:
                    new_description = entry.get().strip()
                    
                    # Valida se não está vazio
                    if not new_description:
                        messagebox.showerror(t('threshold.error_description_empty'), t('threshold.error_description_empty_msg'))
                        return
                    
                    test = self._test_for_history_tree_item(item)
                    
                    if test:
                        # Atualiza na interface
                        self.tree.set(item, 'Nome do Teste', new_description)
                        
                        # Atualiza no banco de dados
                        success = self.database.update_test_description(test['id'], new_description)
                        if success:
                            fc_log("threshold", f"[OK] Descrição atualizada para: '{new_description}'")
                            # Atualiza o objeto local
                            test['description'] = new_description
                            
                            # Marca que o estado foi alterado
                            self._mark_state_changed()
                            fc_log("threshold", f'[OK] Descrição salva no banco de dados')
                        else:
                            fc_log("threshold", f'[ERRO] Erro ao salvar descrição no banco de dados')
                    edit_window.destroy()
                except Exception as e:
                    fc_log("threshold", f'[ERRO] Erro ao salvar descrição: {e}')
                    edit_window.destroy()
            
            def cancel_edit():
                """Cancela a edição"""
                edit_window.destroy()
            
            # Botões
            save_button = tk.Button(button_frame, text="Salvar", command=save_description, 
                                  bg='green', fg='white', font=("Arial", 9))
            save_button.pack(side='left', padx=10)
            
            cancel_button = tk.Button(button_frame, text="Cancelar", command=cancel_edit,
                                    bg='red', fg='white', font=("Arial", 9))
            cancel_button.pack(side='left')
            
            # Bind eventos
            entry.bind('<Return>', lambda e: save_description())
            entry.bind('<Escape>', lambda e: cancel_edit())
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao iniciar edição da descrição: {e}') 


    # Função measure_vswr_action removida - desnecessária e dá valores errados
    
    def cleanup_on_exit(self):
        """Limpa recursos ao sair do módulo (NOVO - modo seguro)"""
        try:
            fc_log("threshold", '🧹 Limpando recursos do módulo Threshold...')         
            # Para monitoramento de segurança se estiver rodando
            if hasattr(self, 'safety_monitoring') and self.safety_monitoring:
                self._stop_safety_monitoring()
                fc_log("threshold", '[OK] Monitoramento de segurança parado')            
            # Fecha conexões de hardware se estiverem abertas
            if HARDWARE_AVAILABLE and rfid_sdk:
                try:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    fc_log("threshold", '[OK] Conexão de hardware fechada')
                except:
                    pass
                if getattr(self, '_stopped_by_reflected_protection', False) and getattr(self, 'app_shell', None) and hasattr(self.app_shell, 'perform_strong_reader_reset_async'):
                    self.after(300, lambda: self.app_shell.perform_strong_reader_reset_async() if getattr(self, 'app_shell', None) else None)
                self._stopped_by_reflected_protection = False
            if hasattr(self, '_save_current_state'):
                try:
                    self._save_current_state()
                    fc_log("threshold", '[OK] Estado salvo')
                except Exception:
                    pass
            
            fc_log("threshold", '[OK] Limpeza de recursos concluída')            
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro durante limpeza: {e}')
    
    def show_selected_test_table(self):
        """Mostra a tabela do teste selecionado pelo usuário - VERSÃO CORRIGIDA"""
        fc_log("threshold", f'[INFO] DEBUG: show_selected_test_table chamado')
        fc_log("threshold", f"[INFO] DEBUG: selected_test_for_table existe: {hasattr(self, 'selected_test_for_table')}")
        if hasattr(self, 'selected_test_for_table'):
            fc_log("threshold", f'[INFO] DEBUG: selected_test_for_table valor: {self.selected_test_for_table}')
            if self.selected_test_for_table:
                fc_log("threshold", f"[INFO] DEBUG: selected_test_for_table descrição: {self.selected_test_for_table.get('description', 'N/A')}")
                fc_log("threshold", f"[INFO] DEBUG: selected_test_for_table ID: {self.selected_test_for_table.get('id', 'N/A')}")
        
        # PRIMEIRO: Verifica se há um teste selecionado especificamente para visualização de tabela
        if hasattr(self, 'selected_test_for_table') and self.selected_test_for_table:
            fc_log("threshold", f"[DATA] Usando teste selecionado para tabela: {self.selected_test_for_table.get('description', 'N/A')}")
            
            test = self.selected_test_for_table
            
            # Verifica se o teste tem resultados
            results = test.get('results', [])
            fc_log("threshold", f'[INFO] DEBUG: Teste tem {(len(results) if results else 0)} resultados')
            if not results:
                test_desc = test.get('description', 'N/A')
                messagebox.showwarning(
                    t('threshold.no_results_title'),
                    t('threshold.no_results_msg').format(test_desc=test_desc))
                return
            
            # Verifica se o teste tem tag_info
            tag_info = test.get('tag_info', {})
            if not tag_info:
                tag_info = {
                    'name': test.get('epc', 'N/A'),
                    'epc': test.get('epc', 'N/A')
                }
            
            fc_log("threshold", f"[OK] Mostrando tabela do teste selecionado: {test.get('description', 'N/A')}")
            self.show_test_table(results, tag_info, test.get('description', ''))
            return
        
        # SEGUNDO: Se não há teste selecionado, verifica se há apenas um teste marcado para gráficos
        marked_tests = [t for t in self.test_history if t.get('show_in_graphs', False)]
        
        if len(marked_tests) == 0:
            # NENHUM teste marcado
            messagebox.showwarning(t('threshold.no_test_selected_title'), 
                                 t('threshold.no_test_selected_msg'))
            return
        
        elif len(marked_tests) == 1:
            # APENAS 1 teste marcado - mostra tabela
            test = marked_tests[0]
            
            # Verifica se o teste tem resultados
            results = test.get('results', [])
            if not results:
                test_desc = test.get('description', 'N/A')
                messagebox.showwarning(t('threshold.no_results_title'), 
                                     t('threshold.no_results_msg').format(test_desc=test_desc))
                return
            
            # Verifica se o teste tem tag_info
            tag_info = test.get('tag_info', {})
            if not tag_info:
                tag_info = {
                    'name': test.get('epc', 'N/A'),
                    'epc': test.get('epc', 'N/A')
                }
            
            fc_log("threshold", f"[OK] Mostrando tabela do único teste marcado: {test.get('description', 'N/A')}")
            self.show_test_table(results, tag_info, test.get('description', ''))
            return
        
        else:
            # 2+ testes marcados - oferece opções para o usuário
            marked_list = "\n".join([f"• {t.get('description', 'N/A')}" for t in marked_tests[:5]])
            msg = t('threshold.multiple_tests_msg').format(count=len(marked_tests), marked_list=marked_list)
            response = self._ask_yes_no(t('threshold.multiple_tests_title'), msg)
            
            if response:
                fc_log("threshold", '[INFO] Usuário escolheu desmarcar todos os testes')
                self._clear_all_test_selections()
            return
    
    def show_test_table(self, results, tag_info, description=""):
        """Mostra tabela de resultados do teste"""
        fc_log("threshold", f'[INFO] DEBUG: show_test_table chamado com {len(results)} resultados')
        fc_log("threshold", f'[INFO] DEBUG: tag_info: {tag_info}')
        fc_log("threshold", f'[INFO] DEBUG: description: {description}')
        
        if not results:
            messagebox.showwarning(
                t('threshold.no_results_title'),
                t('threshold.no_results_empty_msg'))
            return
        
        # Cria janela de resultados
        fc_log("threshold", f'[INFO] DEBUG: Criando janela de resultados...')
        results_window = tk.Toplevel(self)
        results_window.title(f"Resultados do Teste - {tag_info.get('name', tag_info.get('epc', 'N/A'))}")
        results_window.geometry("600x500")
        results_window.transient(self)
        results_window.grab_set()
        fc_log("threshold", f'[INFO] DEBUG: Janela criada com sucesso')
        
        # Frame principal
        main_frame = tk.Frame(results_window)
        main_frame.pack(fill='both', expand=True, padx=20, pady=20)
        
        # Título
        title_label = tk.Label(main_frame, text=t('threshold.results_title'), 
                              font=("Arial", 14, "bold"))
        title_label.pack(pady=(0, 10))
        
        subtitle_label = tk.Label(main_frame, text=f"Tag: {tag_info.get('name', tag_info.get('epc', 'N/A'))} | Descrição: {description or '(nenhuma)'}", 
                                 font=("Arial", 10))
        subtitle_label.pack(pady=(0, 20))
        
        # Frame para a tabela
        table_frame = tk.Frame(main_frame)
        table_frame.pack(fill='both', expand=True)
        
        # Cria Treeview para a tabela
        columns = ('Frequência (MHz)', 'Module Power (dBm)', 'RSSI (dBm)', 'Irradiated Power (dBm)', 'Backscatter (dBm)', 'Power on Tag Forward (dBm)', 'Power on Tag Reversed (dBm)', 'Conversion Loss (dBm)', 'Max FCC Link Forward (m)', 'Max FCC Link Reversed (m)', 'RCS (dBm²)')
        tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=15)
        
        # Configura as colunas
        tree.heading('Frequência (MHz)', text='Frequência (MHz)')
        tree.heading('Module Power (dBm)', text='Module Power (dBm)')
        tree.heading('RSSI (dBm)', text='RSSI (dBm)')
        tree.heading('Irradiated Power (dBm)', text='Irradiated Power (dBm)')
        tree.heading('Backscatter (dBm)', text='Backscatter (dBm)')
        tree.heading('Power on Tag Forward (dBm)', text='Power on Tag Forward (dBm)')
        tree.heading('Power on Tag Reversed (dBm)', text='Power on Tag Reversed (dBm)')
        tree.heading('Conversion Loss (dBm)', text='Conversion Loss (dBm)')
        tree.heading('Max FCC Link Forward (m)', text='Max FCC Link Forward (m)')
        tree.heading('Max FCC Link Reversed (m)', text='Max FCC Link Reversed (m)')
        tree.heading('RCS (dBm²)', text='RCS (dBm²)')
        
        # Configura largura das colunas
        tree.column('Frequência (MHz)', width=120, anchor='center')
        tree.column('Module Power (dBm)', width=120, anchor='center')
        tree.column('RSSI (dBm)', width=100, anchor='center')
        tree.column('Irradiated Power (dBm)', width=140, anchor='center')
        tree.column('Backscatter (dBm)', width=120, anchor='center')
        tree.column('Power on Tag Forward (dBm)', width=160, anchor='center')
        tree.column('Power on Tag Reversed (dBm)', width=160, anchor='center')
        tree.column('Conversion Loss (dBm)', width=140, anchor='center')
        tree.column('Max FCC Link Forward (m)', width=160, anchor='center')
        tree.column('Max FCC Link Reversed (m)', width=160, anchor='center')
        tree.column('RCS (dBm²)', width=120, anchor='center')
        
        # Adiciona scrollbar
        scrollbar = ttk.Scrollbar(table_frame, orient='vertical', command=tree.yview)
        tree.configure(yscrollcommand=scrollbar.set)
        
        # Organiza os widgets
        tree.pack(side='left', fill='both', expand=True)
        scrollbar.pack(side='right', fill='y')
        
        # Insere os dados na tabela
        fc_log("threshold", f'[INFO] DEBUG: Iniciando inserção de {len(results)} resultados na tabela')       
        for i, result in enumerate(results):
            fc_log("threshold", f'[INFO] DEBUG: Processando resultado {i + 1}: {result}')
            
            # Verifica se o resultado tem a estrutura esperada
            if not isinstance(result, dict):
                fc_log("threshold", f'[AVISO] DEBUG: Resultado {i + 1} não é um dicionário: {type(result)}')
                continue
                
            freq = result.get('freq_mhz')
            module_power = result.get('module_power')
            if module_power is None:
                module_power = result.get('threshold_power')
            rssi = result.get('rssi')
            if rssi is None:
                rssi = result.get('threshold_rssi')
            
            fc_log("threshold", f'[INFO] DEBUG: Resultado {i + 1} - freq: {freq}, power: {module_power}, rssi: {rssi}')
            
            # Verifica se os campos obrigatórios existem
            if freq is None or module_power is None or rssi is None:
                fc_log("threshold", f'[AVISO] DEBUG: Resultado {i + 1} com campos obrigatórios ausentes')
                continue
                
            irradiated_power = result.get('irradiated_power', module_power)
            backscatter = result.get('backscatter', rssi)
            power_on_tag_forward = result.get('power_on_tag_forward', module_power)
            power_on_tag_reversed = result.get('power_on_tag_reversed', rssi)
            conversion_loss = result.get('conversion_loss', 0)
            max_fcc_link_forward = result.get('max_fcc_link_forward', 0)
            max_fcc_link_reversed = result.get('max_fcc_link_reversed', 0)
            
            # Insere na tabela
            rcs = result.get('rcs', 0)
            item = tree.insert('', 'end', values=(
                f"{freq:.1f}", 
                f"{module_power:.1f}", 
                f"{rssi:.1f}",
                f"{irradiated_power:.1f}",
                f"{backscatter:.1f}",
                f"{power_on_tag_forward:.1f}",
                f"{power_on_tag_reversed:.1f}",
                f"{conversion_loss:.1f}",
                f"{max_fcc_link_forward:.1f}",
                f"{max_fcc_link_reversed:.1f}",
                f"{rcs:.1f}" if rcs > -999 else "N/A"
            ))
            
            # Aplica cor verde aos valores de threshold
            tree.tag_configure('threshold', background='#90EE90')  # Verde claro
            tree.item(item, tags=('threshold',))
        
        # Frame para estatísticas
        stats_frame = tk.Frame(main_frame)
        stats_frame.pack(fill='x', pady=(20, 0))
        
        # Calcula estatísticas (compatível com chaves module_power/rssi ou threshold_power/threshold_rssi)
        def _row_mp(r):
            v = r.get('module_power')
            return v if v is not None else r.get('threshold_power')
        def _row_rssi(r):
            v = r.get('rssi')
            return v if v is not None else r.get('threshold_rssi')
        threshold_powers = []
        threshold_rssi = []
        irradiated_powers = []
        backscatters = []
        power_on_tag_forwards = []
        power_on_tag_reverseds = []
        for r in results:
            if not isinstance(r, dict) or r.get('freq_mhz') is None:
                continue
            mp, rs = _row_mp(r), _row_rssi(r)
            if mp is None or rs is None:
                continue
            threshold_powers.append(mp)
            threshold_rssi.append(rs)
            irradiated_powers.append(r.get('irradiated_power', mp))
            backscatters.append(r.get('backscatter', rs))
            power_on_tag_forwards.append(r.get('power_on_tag_forward', mp))
            power_on_tag_reverseds.append(r.get('power_on_tag_reversed', rs))
        conversion_losses = [r.get('conversion_loss', 0) for r in results]
        max_fcc_link_forwards = [r.get('max_fcc_link_forward', 0) for r in results]
        max_fcc_link_reverseds = [r.get('max_fcc_link_reversed', 0) for r in results]
        
        if threshold_powers:
            stats_text = (
                f"Estatísticas:\n"
                f"• Total de frequências: {len(threshold_powers)}\n"
                f"• Module Power - Mín: {min(threshold_powers):.1f} dBm, Máx: {max(threshold_powers):.1f} dBm, Média: {np.mean(threshold_powers):.1f} dBm\n"
                f"• RSSI - Mín: {min(threshold_rssi):.1f} dBm, Máx: {max(threshold_rssi):.1f} dBm, Média: {np.mean(threshold_rssi):.1f} dBm\n"
                f"• Irradiated Power - Mín: {min(irradiated_powers):.1f} dBm, Máx: {max(irradiated_powers):.1f} dBm, Média: {np.mean(irradiated_powers):.1f} dBm\n"
                f"• Backscatter - Mín: {min(backscatters):.1f} dBm, Máx: {max(backscatters):.1f} dBm, Média: {np.mean(backscatters):.1f} dBm\n"
                f"• Power on Tag Forward - Mín: {min(power_on_tag_forwards):.1f} dBm, Máx: {max(power_on_tag_forwards):.1f} dBm, Média: {np.mean(power_on_tag_forwards):.1f} dBm\n"
                f"• Power on Tag Reversed - Mín: {min(power_on_tag_reverseds):.1f} dBm, Máx: {max(power_on_tag_reverseds):.1f} dBm, Média: {np.mean(power_on_tag_reverseds):.1f} dBm\n"
                f"• Conversion Loss - Mín: {min(conversion_losses):.1f} dBm, Máx: {max(conversion_losses):.1f} dBm, Média: {np.mean(conversion_losses):.1f} dBm\n"
                f"• Max FCC Link Forward - Mín: {min(max_fcc_link_forwards):.1f} m, Máx: {max(max_fcc_link_forwards):.1f} m, Média: {np.mean(max_fcc_link_forwards):.1f} m\n"
                f"• Max FCC Link Reversed - Mín: {min(max_fcc_link_reverseds):.1f} m, Máx: {max(max_fcc_link_reverseds):.1f} m, Média: {np.mean(max_fcc_link_reverseds):.1f} m"
            )
        else:
            stats_text = "[ERRO] Nenhum threshold encontrado!"
        
        stats_label = tk.Label(stats_frame, text=stats_text, font=("Arial", 9), 
                              justify='left', anchor='w')
        stats_label.pack(fill='x')
        
        # Botão de fechar
        close_button = tk.Button(main_frame, text="Fechar", command=results_window.destroy,
                                font=("Arial", 10), width=15)
        close_button.pack(pady=(20, 0))
    
    def show_test_summary(self, results, tag_info):
        """Função mantida para compatibilidade - agora usa show_test_table"""
        self.show_test_table(results, tag_info, "")
    
    def show_latest_test_data(self):
        """Mostra os dados do teste mais recente em uma tabela"""
        if not self.test_history:
            messagebox.showinfo(
                t('threshold.no_tests_yet_title'),
                t('threshold.no_tests_yet_msg'))
            return
        
        # Pega o teste mais recente
        latest_test = self.test_history[-1]
        

        fc_log("threshold", f"   ID: {latest_test['id']}")
        fc_log("threshold", f"   Descrição: {latest_test['description']}")
        fc_log("threshold", f"   Data/Hora: {latest_test['date_time']}")
        fc_log("threshold", f"   EPC: {latest_test['epc']}")
        fc_log("threshold", f"   Resultados: {len(latest_test['results'])} pontos")
        
        # Mostra a tabela com os dados
        self.show_test_table(latest_test['results'], latest_test['tag_info'], latest_test['description'])
    
    def show_all_test_data(self):
        """Mostra todos os dados de todos os testes em uma tabela consolidada"""
        if not self.test_history:
            messagebox.showinfo(
                t('threshold.no_tests_yet_title'),
                t('threshold.no_tests_yet_msg'))
            return
        
        # Cria janela de resultados consolidados
        results_window = tk.Toplevel(self)
        results_window.title("Todos os Dados de Testes")
        results_window.geometry("800x600")
        results_window.transient(self)
        results_window.grab_set()
        
        # Frame principal
        main_frame = tk.Frame(results_window)
        main_frame.pack(fill='both', expand=True, padx=20, pady=20)
        
        # Título
        title_label = tk.Label(main_frame, text=f"Dados de Todos os Testes ({len(self.test_history)} testes)", 
                              font=("Arial", 14, "bold"))
        title_label.pack(pady=(0, 10))
        
        # Frame para a tabela
        table_frame = tk.Frame(main_frame)
        table_frame.pack(fill='both', expand=True)
        
        # Cria Treeview para a tabela consolidada
        columns = ('Teste ID', 'EPC', 'Descrição', 'Data/Hora', 'Frequência (MHz)', 'Module Power (dBm)', 'RSSI (dBm)', 'RCS (dBm²)')
        tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=20)
        
        # Configura as colunas
        tree.heading('Teste ID', text='Teste ID')
        tree.heading('EPC', text='EPC')
        tree.heading('Descrição', text='Descrição')
        tree.heading('Data/Hora', text='Data/Hora')
        tree.heading('Frequência (MHz)', text='Frequência (MHz)')
        tree.heading('Module Power (dBm)', text='Module Power (dBm)')
        tree.heading('RSSI (dBm)', text='RSSI (dBm)')
        tree.heading('RCS (dBm²)', text='RCS (dBm²)')
        
        # Configura largura das colunas
        tree.column('Teste ID', width=80, anchor='center')
        tree.column('EPC', width=120, anchor='center')
        tree.column('Descrição', width=150, anchor='w')
        tree.column('Data/Hora', width=120, anchor='center')
        tree.column('Frequência (MHz)', width=120, anchor='center')
        tree.column('Module Power (dBm)', width=120, anchor='center')
        tree.column('RSSI (dBm)', width=120, anchor='center')
        tree.column('RCS (dBm²)', width=120, anchor='center')
        
        # Adiciona scrollbar
        scrollbar = ttk.Scrollbar(table_frame, orient='vertical', command=tree.yview)
        tree.configure(yscrollcommand=scrollbar.set)
        
        # Organiza os widgets
        tree.pack(side='left', fill='both', expand=True)
        scrollbar.pack(side='right', fill='y')
        
        # Insere todos os dados de todos os testes
        total_points = 0
        for test in self.test_history:
            test_id = test['id']
            epc = test['epc']
            description = test['description']
            date_time_raw = test['date_time']
            
            # Formata timestamp conforme idioma
            if date_time_raw and isinstance(date_time_raw, str) and date_time_raw != '-':
                try:
                    from datetime import datetime
                    if 'T' in date_time_raw:
                        date_time_dt = datetime.fromisoformat(date_time_raw.replace('Z', '+00:00'))
                    else:
                        try:
                            date_time_dt = datetime.strptime(date_time_raw, '%d/%m/%Y %H:%M:%S')
                        except:
                            try:
                                date_time_dt = datetime.strptime(date_time_raw, '%Y-%m-%d %H:%M:%S')
                            except:
                                try:
                                    date_time_dt = datetime.strptime(date_time_raw, '%d-%m-%Y %H:%M:%S')
                                except:
                                    date_time_dt = None
                    if date_time_dt:
                        translator = _get_translator()
                        if translator.get_language() == 'en':
                            date_time = date_time_dt.strftime('%m/%d/%y %H:%M:%S')
                        else:
                            date_time = date_time_dt.strftime('%d/%m/%Y %H:%M:%S')
                    else:
                        date_time = date_time_raw
                except:
                    date_time = date_time_raw
            else:
                date_time = date_time_raw
            
            for result in test['results']:
                freq = result['freq_mhz']
                module_power = result['module_power']
                rssi = result['rssi']
                
                # Insere na tabela
                rcs = result.get('rcs', 0)
                item = tree.insert('', 'end', values=(
                    test_id,
                    epc,
                    description,
                    date_time,  # Usa data formatada conforme idioma
                    f"{freq:.1f}",
                    f"{module_power:.1f}",
                    f"{rssi:.1f}",
                    f"{rcs:.1f}" if rcs > -999 else "N/A"
                ))
                total_points += 1
        
        # Calcula estatísticas gerais
        all_powers = []
        all_rssi = []
        for test in self.test_history:
            for result in test['results']:
                all_powers.append(result['module_power'])
                all_rssi.append(result['rssi'])
        
        # Estatísticas
        if all_powers:
            avg_power = sum(all_powers) / len(all_powers)
            min_power = min(all_powers)
            max_power = max(all_powers)
            
            avg_rssi = sum(all_rssi) / len(all_rssi)
            min_rssi = min(all_rssi)
            max_rssi = max(all_rssi)
            
            # Frame para estatísticas
            stats_frame = tk.Frame(main_frame)
            stats_frame.pack(fill='x', pady=(20, 0))
            
            # Estatísticas de potência
            power_stats = tk.LabelFrame(stats_frame, text="Estatísticas de Potência", padx=10, pady=5)
            power_stats.pack(side='left', fill='x', expand=True, padx=10)
            
            tk.Label(power_stats, text=f"Média: {avg_power:.1f} dBm").pack(anchor='w')
            tk.Label(power_stats, text=f"Mín: {min_power:.1f} dBm").pack(anchor='w')
            tk.Label(power_stats, text=f"Máx: {max_power:.1f} dBm").pack(anchor='w')
            
            # Estatísticas de RSSI
            rssi_stats = tk.LabelFrame(stats_frame, text="Estatísticas de RSSI", padx=10, pady=5)
            rssi_stats.pack(side='left', fill='x', expand=True, padx=10)
            
            tk.Label(rssi_stats, text=f"Média: {avg_rssi:.1f} dBm").pack(anchor='w')
            tk.Label(rssi_stats, text=f"Mín: {min_rssi:.1f} dBm").pack(anchor='w')
            tk.Label(rssi_stats, text=f"Máx: {max_rssi:.1f} dBm").pack(anchor='w')
            
            # Resumo geral
            summary_stats = tk.LabelFrame(stats_frame, text="Resumo Geral", padx=10, pady=5)
            summary_stats.pack(side='left', fill='x', expand=True)
            
            tk.Label(summary_stats, text=f"Total de Testes: {len(self.test_history)}").pack(anchor='w')
            tk.Label(summary_stats, text=f"Total de Pontos: {total_points}").pack(anchor='w')
            tk.Label(summary_stats, text=f"Tags Únicas: {len(set(test['epc'] for test in self.test_history))}").pack(anchor='w')
        
        # Botão de exportar
        export_frame = tk.Frame(main_frame)
        export_frame.pack(fill='x', pady=(20, 0))
        
        def export_data():
            filename = filedialog.asksaveasfilename(
                title=t('threshold.export_consolidated_data_title'),
                defaultextension=".csv",
                filetypes=[
                    (t('export_popup.filetype_csv'), "*.csv"),
                    (t('export_popup.filetype_all'), "*.*"),
                ],
                parent=self,
            )
            if filename:
                try:
                    # Prepara dados para CSV
                    export_data = []
                    for test in self.test_history:
                        for result in test['results']:
                            export_data.append({
                                'Test_ID': test['id'],
                                'EPC': test['epc'],
                                'Description': test.get('description', ''),
                                'Date_Time': test['date_time'],
                                'Frequency_MHz': result['freq_mhz'],
                                'Module_Power_dBm': result['module_power'],
                                'RSSI_dBm': result['rssi']
                            })
                    
                    # Salva CSV
                    df = pd.DataFrame(export_data)
                    df.to_csv(filename, index=False)
                    messagebox.showinfo(t('threshold.export_completed'), t('threshold.export_completed_msg').format(filename=filename))
                    
                except Exception as e:
                    messagebox.showerror(t('threshold.export_error'), t('threshold.export_error_msg').format(error=str(e)))
        
        tk.Button(export_frame, text="Exportar Dados", command=export_data).pack(side='right')
    


    def update_graphs_from_history(self, immediate=False):
        """Atualiza gráficos conforme checkboxes do histórico. Por defeito agrega chamadas rápidas (evita reentrância e travamentos)."""
        if not MATPLOTLIB_OK:
            fc_log("threshold", '[ERRO] Matplotlib não disponível')
            return
        if immediate:
            self._cancel_pending_graph_refresh()
            self._update_graphs_from_history_impl()
            return
        if getattr(self, '_graph_refresh_after_id', None) is not None:
            try:
                self.after_cancel(self._graph_refresh_after_id)
            except Exception:
                pass
            self._graph_refresh_after_id = None
        self._graph_refresh_after_id = self.after(50, self._flush_graph_refresh)

    def _update_graphs_from_history_impl(self):
        """Implementação real de update_graphs_from_history (executada imediatamente ou após coalescência)."""
        # PERMITE interação com gráficos durante execução de teste
        # (Removido bloqueio para permitir marcar/desmarcar testes durante execução)
        
        # SISTEMA ÚNICO: Usa apenas o estado visual dos checkboxes
        fc_log("threshold", '[INFO] SISTEMA ÚNICO: Verificando estado dos checkboxes na interface...')        
        # Coleta estado atual dos checkboxes da interface
        selected_tests = []
        for item in self.tree.get_children():
            checkbox_state = self.tree.set(item, 'Plot')
            if checkbox_state == "☑":
                test = self._test_for_history_tree_item(item)
                if test:
                    selected_tests.append(test)
                    fc_log("threshold", f"[OK] SISTEMA ÚNICO: Teste {test['id']} ({test['description']}) marcado na interface")        
        fc_log("threshold", f'[INFO] SISTEMA ÚNICO: {len(selected_tests)} testes marcados na interface')        
        if not selected_tests:
            fc_log("threshold", '[AVISO] SISTEMA ÚNICO: Nenhum teste marcado - criando gráficos vazios com posição fixa')
            # CRIA GRÁFICOS VAZIOS COM POSIÇÃO FIXA
            self._update_graph1_from_history([], [])  # Lista vazia para manter posição
            self._update_graph2_from_history([], [])  # Lista vazia para manter posição
            return
        
        # CORREÇÃO: Sistema de cores fixas por teste para evitar mudança de cores
        # Cores com alto contraste - sem cores claras
        base_colors = ['#1f77b4', '#d62728', '#2ca02c', '#ff7f0e', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf']
        
        # Atribui cores fixas baseadas no ID do teste para manter consistência
        test_colors = {}
        fc_log("threshold", f"[INFO] DEBUG COR HISTÓRICO: current_test_id = {getattr(self, 'current_test_id', 'NÃO EXISTE')}")
        for i, test in enumerate(selected_tests):
            test_id = test.get('id', i)
            # Usa o ID do teste para determinar a cor, garantindo que seja sempre a mesma
            color_index = test_id % len(base_colors)
            test_colors[test_id] = base_colors[color_index]
            fc_log("threshold", f"🎨 Teste {test_id} ({test.get('description', 'N/A')}) - Cor fixa: {test_colors[test_id]}")
            if hasattr(self, 'current_test_id') and self.current_test_id == test_id:
                fc_log("threshold", f'[INFO] DEBUG COR HISTÓRICO: Teste {test_id} é o teste atual - cor deve ser consistente')       
        # Atualiza gráficos apenas com testes marcados na interface
        self._update_graph1_from_history(selected_tests, test_colors)
        self._update_graph2_from_history(selected_tests, test_colors)
    
    def _update_graph1_from_history(self, selected_tests, colors):
        """Atualiza o gráfico 1 com dados dos testes selecionados"""
        self._teardown_mpl_graph(1)
        
        # Cria nova figura com tamanho FIXO
        self.fig1 = Figure(figsize=(6, 2.5), dpi=100)
        self.ax1 = self.fig1.add_subplot(111)
        
        # Configura o gráfico
        graph_type = self.graph1_var.get()
        self.ax1.grid(True, alpha=0.3)
        
        # Verifica se há dados para plotar
        if not selected_tests:
            # GRÁFICO VAZIO - MANTÉM POSIÇÃO FIXA
            # Eixo X: banda da licença (ou 800–1000 MHz)
            self.ax1.set_xlim(*self._threshold_graph_freq_limits())
            
            # Define limites fixos do eixo Y baseado no tipo de gráfico
            graph_type = self.graph1_var.get()
            if "Module Power" in graph_type:
                self.ax1.set_ylim(0, 30)
            elif "RSSI" in graph_type:
                self.ax1.set_ylim(-80, -20)
            elif "Irradiated Power" in graph_type:
                self.ax1.set_ylim(-30, 30)
            elif "Backscatter" in graph_type:
                self.ax1.set_ylim(-80, -20)
            elif "Power on Tag Forward" in graph_type:
                self.ax1.set_ylim(-50, 30)
            elif "Power on Tag Reversed" in graph_type:
                self.ax1.set_ylim(-80, -20)
            elif "Conversion Loss" in graph_type:
                self.ax1.set_ylim(-40, 0)
            elif "Max FCC Link Forward" in graph_type:
                self.ax1.set_ylim(0, 30)
            elif "Max FCC Link Reversed" in graph_type:
                self.ax1.set_ylim(0, 50)
            elif "RCS" in graph_type:
                self.ax1.set_ylim(-80, -20)  # RCS em dBm²
            else:
                self.ax1.set_ylim(0, 30)  # Padrão
            
            # NOVO: Adiciona faixas azuis de frequência (como no Antenna Check)
            self.ax1.axvspan(865, 868, color='lightblue', alpha=0.3)
            self.ax1.axvspan(902, 907, color='lightblue', alpha=0.3)
            self.ax1.axvspan(915.5, 928, color='lightblue', alpha=0.3)
            
            # Gráfico vazio sem texto indicativo
        else:
            # Plota cada teste selecionado
            for i, test in enumerate(selected_tests):
                test_id = test.get('id', i)
                # CORREÇÃO: Usa cor fixa baseada no ID do teste
                color = colors.get(test_id, colors.get(i, '#1f77b4'))  # Fallback para cor padrão
                description = test.get('description', f'Teste {test_id}')
                
                fc_log("threshold", f'🎨 Plotando teste {test_id}: {description}')              
                # Extrai dados do teste
                results = test.get('results', [])
                if not results:
                    fc_log("threshold", f'   [AVISO] Teste {test_id} sem resultados')
                    continue
                
                fc_log("threshold", f'   [GRAPH] Processando {len(results)} pontos de dados')
                
                # Prepara dados para plotagem
                frequencies = []
                values = []
                
                # Normaliza o tipo de gráfico antes de processar
                graph_type_normalized = self._normalize_graph_type(graph_type)
                
                for result in results:
                    if isinstance(result, dict):
                        freq = result.get('freq_mhz')
                        if freq is not None:
                            frequencies.append(freq)
                            
                            # Seleciona valor baseado no tipo de gráfico
                            if graph_type_normalized == "Module Power (dBm)":
                                value = result.get('module_power', result.get('threshold_power'))
                            elif graph_type_normalized == "Module RSSI (dBm)":
                                value = result.get('rssi', result.get('threshold_rssi'))
                            elif graph_type_normalized == "Irradiated Power (dBm)":
                                value = result.get('irradiated_power', result.get('threshold_power'))
                            elif graph_type_normalized == "Backscatter (dBm)":
                                value = result.get('backscatter', result.get('threshold_rssi'))
                            elif graph_type_normalized == "Power on Tag Forward (dBm)":
                                value = result.get('power_on_tag_forward', result.get('threshold_power'))
                            elif graph_type_normalized == "Power on Tag Reversed (dBm)":
                                value = result.get('power_on_tag_reversed', result.get('threshold_rssi'))
                            elif graph_type_normalized == "Conversion Loss (dBm)":
                                value = result.get('conversion_loss', 0)
                            elif graph_type_normalized == "Max FCC Link Forward (m)":
                                value = result.get('max_fcc_link_forward', 0)
                            elif graph_type_normalized == "Max FCC Link Reversed (m)":
                                value = result.get('max_fcc_link_reversed', 0)
                            elif graph_type_normalized == "RCS (dBm²)":
                                value = result.get('rcs', 0)
                            else:
                                value = result.get('threshold_power', 0)  # Padrão
                            
                            if value is not None:
                                values.append(value)
                
                fc_log("threshold", f'   [INFO] Frequências: {len(frequencies)}, Valores: {len(values)}')
                fc_log("threshold", f'   [INFO] Frequências: {frequencies}')
                fc_log("threshold", f'   [INFO] Valores: {values}')
                
                if frequencies and values:
                    # CORREÇÃO: Plota segmentos separados para evitar linha contínua em faixas excluídas
                    self._plot_segmented_data(self.ax1, frequencies, values, color, description, test_id)
                    fc_log("threshold", f'   [OK] Teste {test_id} plotado com sucesso')
                else:
                    fc_log("threshold", f'   [ERRO] Teste {test_id} sem dados válidos para plotagem')        
        # Legendas dos gráficos (histórico)
        handles, labels = self.ax1.get_legend_handles_labels()
        if labels:
            self.ax1.legend(
                loc='center left',
                bbox_to_anchor=(1.02, 0.5),
                fontsize=8,
                frameon=True,
            )
        
        # NOVO: Adiciona faixas azuis de frequência (como no Antenna Check)
        self.ax1.axvspan(865, 868, color='lightblue', alpha=0.3)
        self.ax1.axvspan(902, 907, color='lightblue', alpha=0.3)
        self.ax1.axvspan(915.5, 928, color='lightblue', alpha=0.3)
        
        # ANTI-FLASH: Escala fixa REMOVIDA - deixa autoscale ajustar aos dados
        # Os limites Y serão ajustados automaticamente pelo relim() e autoscale_view() abaixo
        # (mantido comentado caso precise restaurar limites fixos no futuro)
        # graph_type = self.graph1_var.get()
        # if "Module Power" in graph_type:
        #     self.ax1.set_ylim(0, 30)
        # elif "RSSI" in graph_type:
        #     self.ax1.set_ylim(-80, -20)
        # ...etc
        
        # Define labels dos eixos
        self.ax1.set_xlabel('')
        self.ax1.set_ylabel('')  # Label do eixo Y removido
        graph_type = self.graph1_var.get()
        
        # Layout fixo - não ajusta baseado no conteúdo
        self.fig1.subplots_adjust(left=0.1, right=0.75, top=0.95, bottom=0.1)
        
        # Cria tooltip annotation para o gráfico 1
        self.annot1 = self.ax1.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.annot1.set_visible(False)
        
        # Cria o canvas com tamanho fixo
        self.canvas1 = FigureCanvasTkAgg(self.fig1, self.graph1_container)
        self.canvas1.draw()
        self.canvas1.get_tk_widget().pack(fill="both", expand=True)
        
        # Controles de zoom na parte superior esquerda do gráfico 1
        self._setup_zoom_controls_graph1(self.graph1_container)
        
        # Configura interatividade do tooltip
        self._graph1_motion_cid = self.canvas1.mpl_connect('motion_notify_event', self._on_hover_graph1)
        
        # Com dados: Y automático à série; sem dados: mantém limites Y fixos do ramo vazio
        if selected_tests:
            self._apply_threshold_autoscale_y(self.ax1)
        self.canvas1.draw()  # Redesenha o canvas
        self.canvas1.flush_events()  # Força atualização imediata
        
        # Força redimensionamento para manter posição fixa
        self.graph1_container.update_idletasks()
    
    def _update_graph2_from_history(self, selected_tests, colors):
        """Atualiza o gráfico 2 com dados dos testes selecionados"""
        self._teardown_mpl_graph(2)
        
        # Cria nova figura com tamanho FIXO
        self.fig2 = Figure(figsize=(6, 2.5), dpi=100)
        self.ax2 = self.fig2.add_subplot(111)
        
        # Configura o gráfico
        graph_type = self.graph2_var.get()
        self.ax2.grid(True, alpha=0.3)
        
        # Define labels dos eixos
        self.ax2.set_xlabel('')
        self.ax2.set_ylabel('')  # Label do eixo Y removido
        
        # Verifica se há dados para plotar
        if not selected_tests:
            # GRÁFICO VAZIO - MANTÉM POSIÇÃO FIXA
            # Define limites do eixo X (banda da licença ou padrão 800–1000 MHz)
            self.ax2.set_xlim(*self._threshold_graph_freq_limits())
            # Define limites baseado no tipo de gráfico
            graph_type = self.graph2_var.get()
            if "Module Power" in graph_type:
                self.ax2.set_ylim(0, 30)
            elif "RSSI" in graph_type:
                self.ax2.set_ylim(-80, -20)
            elif "Irradiated Power" in graph_type:
                self.ax2.set_ylim(-30, 30)
            elif "Backscatter" in graph_type:
                self.ax2.set_ylim(-80, -20)
            elif "Power on Tag Forward" in graph_type:
                self.ax2.set_ylim(-50, 30)
            elif "Power on Tag Reversed" in graph_type:
                self.ax2.set_ylim(-80, -20)
            elif "Conversion Loss" in graph_type:
                self.ax2.set_ylim(-40, 0)
            elif "Max FCC Link Forward" in graph_type:
                self.ax2.set_ylim(0, 30)
            elif "Max FCC Link Reversed" in graph_type:
                self.ax2.set_ylim(0, 50)
            elif "RCS" in graph_type:
                self.ax2.set_ylim(-80, -20)  # RCS em dBm²
            else:
                self.ax2.set_ylim(-80, 20)  # Padrão
            
            # NOVO: Adiciona faixas azuis de frequência (como no Antenna Check)
            self.ax2.axvspan(865, 868, color='lightblue', alpha=0.3)
            self.ax2.axvspan(902, 907, color='lightblue', alpha=0.3)
            self.ax2.axvspan(915.5, 928, color='lightblue', alpha=0.3)
            
            # Gráfico vazio sem texto indicativo
        else:
            # Plota cada teste selecionado
            for i, test in enumerate(selected_tests):
                test_id = test.get('id', i)
                # CORREÇÃO: Usa cor fixa baseada no ID do teste
                color = colors.get(test_id, colors.get(i, '#1f77b4'))  # Fallback para cor padrão
                description = test.get('description', f'Teste {test_id}')
                
                # Extrai dados do teste
                results = test.get('results', [])
                if not results:
                    continue
                
                # Prepara dados para plotagem
                frequencies = []
                values = []
                
                # Normaliza o tipo de gráfico antes de processar
                graph_type_normalized = self._normalize_graph_type(graph_type)
                
                for result in results:
                    if isinstance(result, dict):
                        freq = result.get('freq_mhz')
                        if freq is not None:
                            frequencies.append(freq)
                            
                            # Seleciona valor baseado no tipo de gráfico
                            if graph_type_normalized == "Module Power (dBm)":
                                value = result.get('module_power', result.get('threshold_power'))
                            elif graph_type_normalized == "Module RSSI (dBm)":
                                value = result.get('rssi', result.get('threshold_rssi'))
                            elif graph_type_normalized == "Irradiated Power (dBm)":
                                value = result.get('irradiated_power', result.get('threshold_power'))
                            elif graph_type_normalized == "Backscatter (dBm)":
                                value = result.get('backscatter', result.get('threshold_rssi'))
                            elif graph_type_normalized == "Power on Tag Forward (dBm)":
                                value = result.get('power_on_tag_forward', result.get('threshold_power'))
                            elif graph_type_normalized == "Power on Tag Reversed (dBm)":
                                value = result.get('power_on_tag_reversed', result.get('threshold_rssi'))
                            elif graph_type_normalized == "Conversion Loss (dBm)":
                                value = result.get('conversion_loss', 0)
                            elif graph_type_normalized == "Max FCC Link Forward (m)":
                                value = result.get('max_fcc_link_forward', 0)
                            elif graph_type_normalized == "Max FCC Link Reversed (m)":
                                value = result.get('max_fcc_link_reversed', 0)
                            elif graph_type_normalized == "RCS (dBm²)":
                                value = result.get('rcs', 0)
                            else:
                                value = result.get('threshold_power', 0)  # Padrão
                            
                            if value is not None:
                                values.append(value)
                
                if frequencies and values:
                    # CORREÇÃO: Plota segmentos separados para evitar linha contínua em faixas excluídas
                    self._plot_segmented_data(self.ax2, frequencies, values, color, description, test_id)
        
        # Legendas dos gráficos (histórico)
        handles, labels = self.ax2.get_legend_handles_labels()
        if labels:
            self.ax2.legend(
                loc='center left',
                bbox_to_anchor=(1.02, 0.5),
                fontsize=8,
                frameon=True,
            )
        
        # NOVO: Adiciona faixas azuis de frequência (como no Antenna Check)
        self.ax2.axvspan(865, 868, color='lightblue', alpha=0.3)
        self.ax2.axvspan(902, 907, color='lightblue', alpha=0.3)
        self.ax2.axvspan(915.5, 928, color='lightblue', alpha=0.3)
        
        # ANTI-FLASH: Escala fixa REMOVIDA - deixa autoscale ajustar aos dados
        # Os limites Y serão ajustados automaticamente pelo relim() e autoscale_view() abaixo
        # (mantido comentado caso precise restaurar limites fixos no futuro)
        
        # Define labels dos eixos
        self.ax2.set_xlabel('')
        self.ax2.set_ylabel('')  # Label do eixo Y removido
        graph_type = self.graph2_var.get()
        
        # Layout fixo - não ajusta baseado no conteúdo
        self.fig2.subplots_adjust(left=0.1, right=0.75, top=0.95, bottom=0.1)
        
        # Cria tooltip annotation para o gráfico 2
        self.annot2 = self.ax2.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.annot2.set_visible(False)
        
        # Cria o canvas com tamanho fixo
        self.canvas2 = FigureCanvasTkAgg(self.fig2, self.graph2_container)
        self.canvas2.draw()
        self.canvas2.get_tk_widget().pack(fill="both", expand=True)
        
        # Controles de zoom na parte superior esquerda do gráfico 2
        self._setup_zoom_controls_graph2(self.graph2_container)
        
        # Configura interatividade do tooltip
        self._graph2_motion_cid = self.canvas2.mpl_connect('motion_notify_event', self._on_hover_graph2)
        
        if selected_tests:
            self._apply_threshold_autoscale_y(self.ax2)
        
        self.canvas2.draw()  # Redesenha o canvas
        self.canvas2.flush_events()  # Força atualização imediata
        
        # Força redimensionamento para manter posição fixa
        self.graph2_container.update_idletasks()

    def on_table_action_selected(self, event=None):
        """Manipula as ações selecionadas na dropdown do histórico de testes - VERSÃO SIMPLIFICADA"""
        selected_action = self.table_action_var.get()
        fc_log("threshold", f'[INFO] DROPDOWN ACTION: Ação selecionada: {selected_action}')       
        # Ignora se for o texto padrão
        if selected_action == t('threshold.select_an_action'):
            return
        
        # Compara com valores traduzidos e originais para compatibilidade
        select_all_text = t('threshold.table_select_all')
        deselect_all_text = t('threshold.table_deselect_all')
        statistical_text = t('threshold.table_statistical')
        delete_text = t('threshold.table_delete_selected')
        export_text = t('threshold.table_export_excel')
        
        if selected_action == select_all_text or selected_action == "Select All Tests":
            fc_log("threshold", '[OK] Executando: Select All Tests')
            self.select_all_tests()
        elif selected_action == deselect_all_text or selected_action == "Deselect All Tests":
            fc_log("threshold", '[OK] Executando: Deselect All Tests')
            self.deselect_all_tests()
        elif selected_action == statistical_text or selected_action == "Cálculo Estatístico dos Selecionados":
            fc_log("threshold", '[OK] Executando: Cálculo Estatístico dos Selecionados')
            self.calc_complete_statistical_analysis()
        elif selected_action == delete_text or selected_action == "Delete Selected Tests":
            fc_log("threshold", '[OK] Executando: Delete Selected Tests')
            self.delete_selected_tests()
        elif selected_action == export_text or selected_action == "Export Selected to Excel":
            fc_log("threshold", '[OK] Executando: Export Selected to Excel')
            self.export_selected_to_excel()
        else:
            fc_log("threshold", f'[AVISO] Ação não reconhecida: {selected_action}')      
        # Reseta a dropdown
        self.table_action_var.set(t('threshold.select_an_action'))
        fc_log("threshold", f"[INFO] Dropdown resetado para '{t('threshold.select_an_action')}'")
    
    def select_all_tests(self):
        """Marca todos os testes no histórico"""
        # Carrega dados frescos do banco
        self.test_history = self.database.get_test_history()
        
        # Marca todos os testes
        for test in self.test_history:
            test_id = test.get('id')
            if test_id:
                self.database.update_test_visibility(test_id, True)
        
        # Recarrega dados do banco
        self.test_history = self.database.get_test_history()
        
        # Atualiza a interface
        self.update_history_tree()
        self.update_graphs_from_history()
        fc_log("threshold", '[OK] Todos os testes marcados')
    
    def deselect_all_tests(self):
        """Desmarca todos os testes no histórico"""
        # Carrega dados frescos do banco
        self.test_history = self.database.get_test_history()
        
        # Desmarca todos os testes
        for test in self.test_history:
            test_id = test.get('id')
            if test_id:
                self.database.update_test_visibility(test_id, False)
        
        # Recarrega dados do banco
        self.test_history = self.database.get_test_history()
        
        # Atualiza a interface
        self.update_history_tree()
        self.update_graphs_from_history()
        fc_log("threshold", '[OK] Todos os testes desmarcados')
    
    def calc_average_on_graph(self):
        """Calcula a média dos testes selecionados e plota nos gráficos"""
        fc_log("threshold", '[INFO] INICIANDO CÁLCULO DE MÉDIA...')       
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        fc_log("threshold", f'[INFO] Testes selecionados: {len(selected_tests)}')
        
        if not selected_tests:
            messagebox.showwarning(t('threshold.no_test_selected_average'), t('threshold.no_test_selected_average_msg'))
            return
        
        if len(selected_tests) == 1:
            messagebox.showinfo(t('threshold.only_one_test'), t('threshold.only_one_test_msg'))
            return
        
        # Calcula a média dos dados
        all_frequencies = set()
        freq_data = {}
        
        fc_log("threshold", '[INFO] COLETANDO DADOS DOS TESTES...')
        fc_log("threshold", '🚨 TRACKING REVERSO INICIADO - RASTREANDO CÁLCULO DO VALOR ABSURDO')     
        # Coleta todas as frequências e dados (CORRIGIDO - todos os tipos de resultados)
        for i, test in enumerate(selected_tests):
            test_id = test.get('id', 'N/A')
            test_desc = test.get('description', 'N/A')
            results = test.get('results', [])
            fc_log("threshold", f"[DATA] Teste {i + 1}: ID={test_id}, Descrição='{test_desc}', Resultados={len(results)}")           
            if not results:
                fc_log("threshold", f'[AVISO] Teste {i + 1} não tem resultados')
                continue
                
            for j, result in enumerate(results):
                if isinstance(result, dict):
                    freq = result.get('freq_mhz')
                    if freq is not None:
                        all_frequencies.add(freq)
                        if freq not in freq_data:
                            # [OK] COLETA TODOS OS TIPOS DE DADOS DISPONÍVEIS
                            freq_data[freq] = {
                                'threshold_power': [], 'threshold_rssi': [],      # Dados básicos
                                'module_power': [], 'rssi': [],                  # Dados originais
                                'irradiated_power': [], 'backscatter': [],       # Dados corrigidos
                                'power_on_tag_forward': [], 'power_on_tag_reversed': [], # Power on Tag
                                'conversion_loss': [],                           # Conversion Loss
                                'max_fcc_link_forward': [], 'max_fcc_link_reversed': [], # FCC Link
                                'rcs': [],                                       # RCS (Radar Cross Section)
                                'correction_factor': []                          # [OK] ADICIONADO PARA PADRONIZAÇÃO
                            }
                        
                        # [OK] COLETA TODOS OS CAMPOS DISPONÍVEIS
                        fields_to_collect = [
                            'threshold_power', 'threshold_rssi', 'module_power', 'rssi',
                            'irradiated_power', 'backscatter', 'power_on_tag_forward', 
                            'power_on_tag_reversed', 'conversion_loss', 
                            'max_fcc_link_forward', 'max_fcc_link_reversed', 'rcs',
                            'correction_factor'  # [OK] ADICIONADO PARA PADRONIZAÇÃO
                        ]
                        
                        for field in fields_to_collect:
                            value = result.get(field)
                            if value is not None:
                                freq_data[freq][field].append(value)
                                # Log específico para RCS
                                if field == 'rcs':
                                    fc_log("threshold", f'    [INFO] RCS coletado: {value} para frequência {freq}MHz')                        
                        fc_log("threshold", f"  [INFO] Frequência {freq}MHz: Power={result.get('threshold_power')}, RSSI={result.get('threshold_rssi')}, RCS={result.get('rcs')}")                        
                        # 🚨 TRACKING REVERSO - rastreia valores críticos para 920MHz
                        if freq == 920.0:
                            fc_log("threshold", f'    🚨 TRACKING 920MHz - Teste {test_id}:')
                            fc_log("threshold", f"      module_power: {result.get('module_power')}")
                            fc_log("threshold", f"      rssi: {result.get('rssi')}")
                            fc_log("threshold", f"      power_on_tag_reversed: {result.get('power_on_tag_reversed')}")
                            fc_log("threshold", f"      max_fcc_link_reversed: {result.get('max_fcc_link_reversed')}")
                            fc_log("threshold", f"      attenuator: {result.get('attenuator')}")
                            fc_log("threshold", f"      distance: {result.get('distance')}")
                            if result.get('max_fcc_link_reversed') and result.get('max_fcc_link_reversed') > 50:
                                fc_log("threshold", f'      🚨 VALOR ABSURDO ENCONTRADO NO TESTE INDIVIDUAL!')
                else:
                    fc_log("threshold", f'[AVISO] Resultado {j} não é um dicionário: {type(result)}')
        fc_log("threshold", f'[TARGET] Frequências encontradas: {sorted(all_frequencies)}')
        fc_log("threshold", f'[INFO] Dados coletados por frequência: {freq_data}')        
        if not all_frequencies:
            messagebox.showerror(t('threshold.error_no_freq'), t('threshold.error_no_freq_msg'))
            return
        
        # Calcula médias (CORRIGIDO - todos os tipos de resultados)
        avg_frequencies = sorted(all_frequencies)
        
        fc_log("threshold", '[CALC] CALCULANDO MÉDIAS PARA TODOS OS TIPOS DE RESULTADOS...')        
        # [OK] CALCULA MÉDIAS PARA TODOS OS CAMPOS
        avg_results_by_freq = {}
        for freq in avg_frequencies:
            if freq in freq_data:
                avg_results_by_freq[freq] = {}
                
                # Calcula média para cada campo disponível
                for field, values in freq_data[freq].items():
                    if values:  # Se há valores para este campo
                        avg_value = np.mean(values)
                        avg_results_by_freq[freq][field] = avg_value
                        fc_log("threshold", f'[INFO] {freq}MHz - {field}: {avg_value:.2f}')
                        # Log crítico para campos problemáticos
                        if field == 'max_fcc_link_reversed' and freq == 920.0:
                            fc_log("threshold", f'🚨 DEBUG CRÍTICO: max_fcc_link_reversed para 920MHz = {avg_value:.2f}')
                            fc_log("threshold", f'🚨 DEBUG CRÍTICO: Valores originais: {values}')
                            fc_log("threshold", f'🚨 DEBUG CRÍTICO: Quantidade de valores: {len(values)}')
                            fc_log("threshold", f'🚨 DEBUG CRÍTICO: Média calculada: {avg_value}')
                            if avg_value > 50:
                                fc_log("threshold", f'🚨 VALOR ABSURDO CALCULADO NA MÉDIA!')
                                fc_log("threshold", f'🚨 Investigando valores individuais:')
                                for idx, val in enumerate(values):
                                    fc_log("threshold", f'      Valor {idx + 1}: {val}')
                    else:
                        avg_results_by_freq[freq][field] = 0  # Valor padrão se não há dados
        
        # Cria dados de média (CORRIGIDO - estrutura completa para compatibilidade)
        avg_results = []
        for freq in avg_frequencies:
            # [OK] CRIA RESULTADO COMPLETO COM TODOS OS CAMPOS CALCULADOS
            avg_result = {
                'freq_mhz': freq,
                'timestamp': datetime.now().strftime("%d-%m-%Y %H:%M"),
                'test_id': f"AVG_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
                'description': "Média Calculada",
                'epc': 'AVG'
            }
            
            # [OK] ADICIONA TODOS OS CAMPOS CALCULADOS
            if freq in avg_results_by_freq:
                for field, avg_value in avg_results_by_freq[freq].items():
                    avg_result[field] = avg_value
            
            # [OK] GARANTE COMPATIBILIDADE COM SIMULADOR - campos obrigatórios
            if 'module_power' not in avg_result and 'threshold_power' in avg_result:
                avg_result['module_power'] = avg_result['threshold_power']
                fc_log("threshold", f"[INFO] DEBUG: Adicionado module_power = {avg_result['module_power']} para {freq}MHz")
            if 'rssi' not in avg_result and 'threshold_rssi' in avg_result:
                avg_result['rssi'] = avg_result['threshold_rssi']
                fc_log("threshold", f"[INFO] DEBUG: Adicionado rssi = {avg_result['rssi']} para {freq}MHz")
            
            # [OK] ADICIONA CAMPOS DE PARÂMETROS (mesmo padrão dos testes individuais)
            avg_result['attenuator'] = avg_attenuator
            avg_result['distance'] = avg_distance
            
            # [OK] LOG DE DEBUG - verifica campos finais
            fc_log("threshold", f'[INFO] DEBUG: Resultado final para {freq}MHz:')
            fc_log("threshold", f"  - module_power: {avg_result.get('module_power', 'N/A')}")
            fc_log("threshold", f"  - rssi: {avg_result.get('rssi', 'N/A')}")
            fc_log("threshold", f'  - Todos os campos: {list(avg_result.keys())}')
            
            # 🚨 TRACKING REVERSO FINAL - verifica se o valor absurdo foi gerado
            if freq == 920.0:
                max_reverse = avg_result.get('max_fcc_link_reversed')
                power_reverse = avg_result.get('power_on_tag_reversed')
                fc_log("threshold", f'🚨 TRACKING FINAL 920MHz:')
                fc_log("threshold", f'  max_fcc_link_reversed: {max_reverse}')
                fc_log("threshold", f'  power_on_tag_reversed: {power_reverse}')
                if max_reverse and max_reverse > 50:
                    fc_log("threshold", f'  🚨 VALOR ABSURDO CONFIRMADO NO RESULTADO FINAL!')
                    fc_log("threshold", f'  🚨 Este é o valor que será exportado para o simulador!')        
            avg_results.append(avg_result)
        
        fc_log("threshold", f'[DATA] Resultados de média criados: {len(avg_results)} pontos')        
        # [OK] CALCULA MÉDIAS DOS PARÂMETROS DOS TESTES SELECIONADOS
        avg_attenuator = np.mean([test.get('attenuator', 0.0) for test in selected_tests if test.get('attenuator') is not None])
        avg_distance = np.mean([test.get('distance', 1.0) for test in selected_tests if test.get('distance') is not None])
        
        # [OK] USA O EPC DO PRIMEIRO TESTE SELECIONADO (padrão individual)
        first_test_epc = selected_tests[0].get('epc', 'AVG') if selected_tests else 'AVG'
        
        # Cria teste de média (PADRONIZADO - mesmo padrão dos testes individuais)
        avg_test = {
            'id': f"AVG_{datetime.now().strftime('%Y%m%d_%H%M%S')}",  # ID único baseado em timestamp
            'epc': first_test_epc,  # [OK] USA EPC REAL DO PRIMEIRO TESTE
            'description': "Média Calculada",  # [OK] TÍTULO CORRETO CONFORME SOLICITADO
            'date_time': datetime.now().strftime("%d-%m-%Y %H:%M"),
            'show_in_graphs': True,  # [OK] MARCADO PARA APARECER NOS GRÁFICOS
            'results': avg_results,
            'tag_info': {'name': 'Média Calculada', 'epc': first_test_epc},  # [OK] USA EPC REAL
            'attenuator': avg_attenuator,  # [OK] MÉDIA DOS ATENUADORES DOS TESTES SELECIONADOS
            'distance': avg_distance,      # [OK] MÉDIA DAS DISTÂNCIAS DOS TESTES SELECIONADOS
            'is_average': True  # [OK] FLAG PARA IDENTIFICAR COMO TESTE DE MÉDIA
        }
        
        fc_log("threshold", f'[OK] Teste de média criado: {avg_test}')        
        # [OK] SALVA NO BANCO DE DADOS (CORRIGIDO)
        success = self.database.add_test_to_history(avg_test)
        if success:
            fc_log("threshold", '[SAVE] Teste de média salvo no banco de dados')
            # Recarrega dados do banco para sincronização
            self.test_history = self.database.get_test_history()
        else:
            fc_log("threshold", '[ERRO] Erro ao salvar teste de média no banco de dados')
            messagebox.showerror(t('threshold.error_save_average'), t('threshold.error_save_average_msg'))
            return
        
        # Atualiza interface
        self.update_history_tree()
        fc_log("threshold", '[INFO] Árvore de histórico atualizada')       
        self.update_graphs_from_history()
        fc_log("threshold", '[GRAPH] Gráficos atualizados')        
        messagebox.showinfo(t('threshold.average_calculated'), t('threshold.average_calculated_msg').format(count=len(selected_tests)))
        
        fc_log("threshold", '[OK] CÁLCULO DE MÉDIA CONCLUÍDO COM SUCESSO!')  
    def calc_complete_statistical_analysis(self):
        """Calcula análise estatística COMPLETA dos testes selecionados (média + estatísticas)"""
        fc_log("threshold", '[INFO] INICIANDO ANÁLISE ESTATÍSTICA COMPLETA...')       
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        fc_log("threshold", f'[INFO] Testes selecionados: {len(selected_tests)}')
        
        if not selected_tests:
            messagebox.showwarning(t('threshold.no_test_selected_statistics'), t('threshold.no_test_selected_statistics_msg'))
            return
        
        if len(selected_tests) == 1:
            messagebox.showinfo(t('threshold.only_one_test'), t('threshold.only_one_test_msg'))
            return
        
        # Calcula estatísticas dos dados
        all_frequencies = set()
        freq_data = {}
        
        fc_log("threshold", '[INFO] COLETANDO DADOS DOS TESTES...')
        
        # Coleta todas as frequências e dados
        # NOVO: validação de parâmetros comuns (distância e atenuador)
        distances_set = set()
        attenuators_set = set()
        for i, test in enumerate(selected_tests):
            test_id = test.get('id', 'N/A')
            test_desc = test.get('description', 'N/A')
            results = test.get('results', [])
            fc_log("threshold", f"[DATA] Teste {i + 1}: ID={test_id}, Descrição='{test_desc}', Resultados={len(results)}")
            # Coleta distância/atenuador no nível do teste (usados pelo Simulador)
            try:
                if 'distance' in test and test['distance'] not in (None, ""):
                    distances_set.add(float(test.get('distance')))
                if 'attenuator' in test and test['attenuator'] not in (None, ""):
                    attenuators_set.add(float(test.get('attenuator')))
            except Exception as _e:
                fc_log("threshold", f'[AVISO] Erro ao ler distance/attenuator do teste {test_id}: {_e}')
            
            if not results:
                fc_log("threshold", f'[AVISO] Teste {i + 1} não tem resultados')
                continue
                
            for j, result in enumerate(results):
                if isinstance(result, dict):
                    freq = result.get('freq_mhz')
                    if freq is not None:
                        all_frequencies.add(freq)
                        if freq not in freq_data:
                            # [OK] COLETA TODOS OS TIPOS DE DADOS DISPONÍVEIS
                            freq_data[freq] = {
                                'threshold_power': [], 'threshold_rssi': [],      # Dados básicos
                                'module_power': [], 'rssi': [],                  # Dados originais
                                'irradiated_power': [], 'backscatter': [],       # Dados corrigidos
                                'power_on_tag_forward': [], 'power_on_tag_reversed': [], # Power on Tag
                                'conversion_loss': [],                           # Conversion Loss
                                'max_fcc_link_forward': [], 'max_fcc_link_reversed': [], # FCC Link
                                'rcs': [],                                       # RCS (Radar Cross Section)
                                'correction_factor': []                          # [OK] ADICIONADO PARA PADRONIZAÇÃO
                            }
                        
                        # [OK] COLETA TODOS OS CAMPOS DISPONÍVEIS
                        fields_to_collect = [
                            'threshold_power', 'threshold_rssi', 'module_power', 'rssi',
                            'irradiated_power', 'backscatter', 'power_on_tag_forward', 
                            'power_on_tag_reversed', 'conversion_loss', 
                            'max_fcc_link_forward', 'max_fcc_link_reversed', 'rcs',
                            'correction_factor'  # [OK] ADICIONADO PARA PADRONIZAÇÃO
                        ]
                        
                        for field in fields_to_collect:
                            value = result.get(field)
                            if value is not None:
                                freq_data[freq][field].append(value)
                                # Log específico para RCS
                                if field == 'rcs':
                                    fc_log("threshold", f'    [INFO] RCS coletado: {value} para frequência {freq}MHz')                        
                        fc_log("threshold", f"  [INFO] Frequência {freq}MHz: Power={result.get('threshold_power')}, RSSI={result.get('threshold_rssi')}, RCS={result.get('rcs')}")                        
                        # 🚨 TRACKING REVERSO - rastreia valores críticos para 920MHz
                        if freq == 920.0:
                            fc_log("threshold", f'    🚨 TRACKING 920MHz - Teste {test_id}:')
                            fc_log("threshold", f"      module_power: {result.get('module_power')}")
                            fc_log("threshold", f"      rssi: {result.get('rssi')}")
                            fc_log("threshold", f"      power_on_tag_reversed: {result.get('power_on_tag_reversed')}")
                            fc_log("threshold", f"      max_fcc_link_reversed: {result.get('max_fcc_link_reversed')}")
                            fc_log("threshold", f"      attenuator: {result.get('attenuator')}")
                            fc_log("threshold", f"      distance: {result.get('distance')}")
                            if result.get('max_fcc_link_reversed') and result.get('max_fcc_link_reversed') > 50:
                                fc_log("threshold", f'      🚨 VALOR ABSURDO ENCONTRADO NO TESTE INDIVIDUAL!')
                else:
                    fc_log("threshold", f'[AVISO] Resultado {j} não é um dicionário: {type(result)}')
        fc_log("threshold", f'[TARGET] Frequências encontradas: {sorted(all_frequencies)}')        
        if not all_frequencies:
            messagebox.showerror(t('threshold.error_no_freq'), t('threshold.error_no_freq_msg'))
            return
        
        # Calcula estatísticas para todos os campos
        fc_log("threshold", '[CALC] CALCULANDO ESTATÍSTICAS COMPLETAS...')        
        # [OK] CALCULA ESTATÍSTICAS PARA TODOS OS CAMPOS
        stats_results_by_freq = {}
        for freq in sorted(all_frequencies):
            if freq in freq_data:
                stats_results_by_freq[freq] = {}
                
                # Calcula estatísticas para cada campo disponível
                for field, values in freq_data[freq].items():
                    if values and len(values) > 1:  # Precisa de pelo menos 2 valores para estatísticas
                        mean_value = np.mean(values)
                        std_value = np.std(values)
                        min_3sigma = mean_value - (3 * std_value)
                        max_3sigma = mean_value + (3 * std_value)
                        
                        stats_results_by_freq[freq][field] = {
                            'mean': mean_value,
                            'std': std_value,
                            'min_3sigma': min_3sigma,
                            'max_3sigma': max_3sigma
                        }
                        
                        fc_log("threshold", f'[INFO] {freq}MHz - {field}:')
                        fc_log("threshold", f'    Média: {mean_value:.2f}')
                        fc_log("threshold", f'    Desvio Padrão: {std_value:.2f}')
                        fc_log("threshold", f'    Média - 3σ: {min_3sigma:.2f}')
                        fc_log("threshold", f'    Média + 3σ: {max_3sigma:.2f}')
                        # Log específico para RCS
                        if field == 'rcs':
                            fc_log("threshold", f'    [INFO] RCS - Valores originais: {values}')
                            fc_log("threshold", f'    [INFO] RCS - Valores válidos: {[v for v in values if v > -999]}')
                    else:
                        # Para RCS, filtra valores inválidos (-999)
                        if field == 'rcs' and values:
                            valid_values = [v for v in values if v > -999]
                            if valid_values:
                                mean_value = np.mean(valid_values)
                                std_value = np.std(valid_values) if len(valid_values) > 1 else 0
                                min_3sigma = mean_value - (3 * std_value)
                                max_3sigma = mean_value + (3 * std_value)
                                stats_results_by_freq[freq][field] = {
                                    'mean': mean_value,
                                    'std': std_value,
                                    'min_3sigma': min_3sigma,
                                    'max_3sigma': max_3sigma
                                }
                                fc_log("threshold", f'[INFO] {freq}MHz - {field} (valores válidos):')
                                fc_log("threshold", f'    Média: {mean_value:.2f}')
                                fc_log("threshold", f'    Desvio Padrão: {std_value:.2f}')
                                fc_log("threshold", f'    Média - 3σ: {min_3sigma:.2f}')
                                fc_log("threshold", f'    Média + 3σ: {max_3sigma:.2f}')
                                continue
                        
                        mean_value = np.mean(values) if values else 0
                        stats_results_by_freq[freq][field] = {
                            'mean': mean_value,
                            'std': 0,
                            'min_3sigma': mean_value,
                            'max_3sigma': mean_value
                        }
        
        # --- Validação final de parâmetros comuns ---
        common_distance = None
        common_attenuator = None
        try:
            if len(distances_set) == 1:
                common_distance = list(distances_set)[0]
            if len(attenuators_set) == 1:
                common_attenuator = list(attenuators_set)[0]
        except Exception as _e:
            fc_log("threshold", f'[AVISO] Erro ao consolidar parâmetros comuns: {_e}')
        if common_distance is None or common_attenuator is None:
            messagebox.showerror(
                "Parâmetros Divergentes",
                "Não é possível calcular estatísticas: os testes selecionados possuem DISTÂNCIA e/ou ATENUADOR diferentes.\n\n"
                "Selecione apenas testes com os MESMOS parâmetros (ex.: distância 0,20 m e atenuador 10 dB)."
            )
            fc_log("threshold", f'[ERRO] Estatística abortada: distances={sorted(list(distances_set))}, attenuators={sorted(list(attenuators_set))}')
            return

        fc_log("threshold", f'[OK] Parâmetros comuns detectados: distance={common_distance} m, attenuator={common_attenuator} dB')

        parent_win = self.winfo_toplevel()
        group_name = simpledialog.askstring(
            t('threshold.statistics_group_name_title'),
            t('threshold.statistics_group_name_prompt'),
            parent=parent_win,
        )
        if group_name is None:
            fc_log("threshold", '[INFO] Análise estatística cancelada (nome do grupo).')
            return
        group_name = group_name.strip()
        if not group_name:
            messagebox.showwarning(
                t('threshold.statistics_group_name_title'),
                t('threshold.statistics_group_name_empty'),
                parent=parent_win,
            )
            return
        if len(group_name) > 120:
            group_name = group_name[:120].rstrip()

        desc_prefix = f"{group_name} — "

        # Cria os QUATRO testes estatísticos (incluindo a média)
        fc_log("threshold", '[DATA] CRIANDO TESTES ESTATÍSTICOS COMPLETOS...')        
        # 1. MÉDIA (como estava funcionando antes)
        mean_results = self._create_statistical_test_results(stats_results_by_freq, 'mean', f"{desc_prefix}Média de {len(selected_tests)} testes", common_distance, common_attenuator)
        
        # 2. Desvio Padrão
        std_results = self._create_statistical_test_results(stats_results_by_freq, 'std', f"{desc_prefix}Desvio Padrao de {len(selected_tests)} testes", common_distance, common_attenuator)
        
        # 3. Limite Mínimo (Média - 3σ)
        min_results = self._create_statistical_test_results(stats_results_by_freq, 'min_3sigma', f"{desc_prefix}Limite Minimo de {len(selected_tests)} testes", common_distance, common_attenuator)
        
        # 4. Limite Máximo (Média + 3σ)
        max_results = self._create_statistical_test_results(stats_results_by_freq, 'max_3sigma', f"{desc_prefix}Limite Maximo de {len(selected_tests)} testes", common_distance, common_attenuator)
        
        # Salva todos os testes no banco de dados
        success_count = 0
        for test_data, test_type in [(mean_results, "Média"), (std_results, "Desvio Padrão"), (min_results, "Limite Mínimo"), (max_results, "Limite Máximo")]:
            success = self.database.add_test_to_history(test_data)
            if success:
                success_count += 1
                fc_log("threshold", f'[OK] {test_type} salvo no banco de dados')
            else:
                fc_log("threshold", f'[ERRO] Erro ao salvar {test_type} no banco de dados')
        
        if success_count > 0:
            # Recarrega dados do banco para sincronização
            self.test_history = self.database.get_test_history()
            
            # Atualiza interface
            self.update_history_tree()
            self.update_graphs_from_history()
            
            messagebox.showinfo(
                t('threshold.statistics_complete'),
                t('threshold.statistics_complete_msg').format(
                    count=len(selected_tests),
                    success=success_count,
                    group_name=group_name,
                ),
            )
            
            fc_log("threshold", '[OK] ANÁLISE ESTATÍSTICA COMPLETA CONCLUÍDA COM SUCESSO!')
        else:
            messagebox.showerror(t('threshold.error_save_statistics'), t('threshold.error_save_statistics_msg'))
    
    def _create_statistical_test_results(self, stats_results_by_freq, stat_type, description, distance_value, attenuator_value):
        """Cria resultados de teste estatístico para um tipo específico de estatística"""
        results = []
        
        for freq in sorted(stats_results_by_freq.keys()):
            result = {
                'freq_mhz': freq,
                'timestamp': datetime.now().strftime("%d-%m-%Y %H:%M"),
                'test_id': f"STAT_{stat_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
                'description': description,
                'epc': 'STAT'
            }
            
            # Adiciona todos os campos calculados
            if freq in stats_results_by_freq:
                for field, stats in stats_results_by_freq[freq].items():
                    if stat_type in stats:
                        result[field] = stats[stat_type]
                    else:
                        # Para RCS, usa -999 se não há dados válidos
                        if field == 'rcs':
                            result[field] = -999
                        else:
                            result[field] = 0
            
            results.append(result)
        
        # Cria o teste estatístico
        test_data = {
            'id': f"STAT_{stat_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
            'epc': f"Estatístico {stat_type}",
            'description': description,
            'date_time': datetime.now().strftime("%d-%m-%Y %H:%M"),
            'show_in_graphs': True,
            'results': results,
            'tag_info': {'name': description, 'epc': 'STAT'},
            # USA PARÂMETROS COMUNS DETECTADOS (em vez de defaults 0/1)
            'attenuator': float(attenuator_value) if attenuator_value is not None else 0.0,
            'distance': float(distance_value) if distance_value is not None else 1.0,
            'is_statistical': True,
            'stat_type': stat_type
        }
        
        return test_data
    
    def clear_graph(self):
        """Limpa os gráficos e deseleciona todos os testes"""
        self.create_empty_graph1()
        self.create_empty_graph2()
        
        # Deseleciona todos os testes que estavam marcados
        self.deselect_all_tests()
        
        fc_log("threshold", '[OK] Gráficos limpos e todos os testes deselecionados')    
    def edit_test_description(self):
        """Edita a descrição de um teste selecionado"""
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        
        if len(selected_tests) != 1:
            messagebox.showwarning(t('threshold.invalid_selection_edit'), t('threshold.invalid_selection_edit_msg'))
            return
        
        test = selected_tests[0]
        current_description = test.get('description', '')
        
        # Cria popup para edição
        edit_window = tk.Toplevel(self)
        edit_window.title("Editar Descrição do Teste")
        edit_window.geometry("400x200")
        edit_window.transient(self)
        edit_window.grab_set()
        
        tk.Label(edit_window, text="Nova descrição:", font=("Arial", 10)).pack(pady=10)
        
        description_var = tk.StringVar(value=current_description)
        entry = tk.Entry(edit_window, textvariable=description_var, width=50)
        entry.pack(pady=10)
        entry.focus()
        
        def save_description():
            """Salva a nova descrição"""
            new_description = description_var.get().strip()
            
            # Atualiza no banco de dados
            test['description'] = new_description
            self.database.update_test_description(test['id'], new_description)
            
            # Atualiza tabela
            self.load_test_history()
            
            edit_window.destroy()
        
        def cancel():
            """Fecha popup sem salvar"""
            edit_window.destroy()
        
        button_frame = tk.Frame(edit_window)
        button_frame.pack(pady=20)
        
        tk.Button(button_frame, text="Salvar", command=save_description).pack(side='left', padx=5)
        tk.Button(button_frame, text="Cancelar", command=cancel).pack(side='left', padx=5)
    
    def delete_selected_tests(self):
        """Remove os testes selecionados - VERSÃO MELHORADA COM LOGS"""
        fc_log("threshold", '[INFO] DEBUG: Iniciando delete_selected_tests...')
        
        # Recarrega dados frescos do banco
        self.test_history = self.database.get_test_history()
        fc_log("threshold", f'[INFO] Total de testes carregados: {len(self.test_history)}')
        
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        fc_log("threshold", f'[OK] Testes selecionados encontrados: {len(selected_tests)}')
        
        for test in selected_tests:
            fc_log("threshold", f"   - Teste {test['id']}: {test['description']} (show_in_graphs: {test.get('show_in_graphs', False)})")
        
        if not selected_tests:
            messagebox.showwarning(
                t('threshold.no_test_selected_title'),
                t('threshold.no_test_selected_remove_msg'))
            fc_log("threshold", '[AVISO] Nenhum teste selecionado para remoção')
            return
        

        
        # Remove os testes selecionados
        deleted_count = 0
        
        fc_log("threshold", f'🗑️ Iniciando remoção de {len(selected_tests)} testes...')  
        for test in selected_tests:
            test_id = test.get('id')
            test_description = test.get('description', 'Sem descrição')
            test_epc = test.get('epc', 'N/A')
            
            fc_log("threshold", f"   [INFO] Removendo teste ID={test_id}, Descrição='{test_description}', EPC={test_epc}")           
            try:
                # Remove do banco de dados primeiro
                db_success = self.database.delete_test_from_history(test_id)
                
                if db_success:
                    fc_log("threshold", f'   [OK] Teste {test_id} removido do banco com sucesso')
                    deleted_count += 1
                else:
                    fc_log("threshold", f'   [ERRO] Falha ao remover teste {test_id} do banco')
                    
            except Exception as e:
                fc_log("threshold", f'   [ERRO] Erro ao remover teste {test_id}: {e}')
        
        # Recarrega dados do banco após remoção
        self.test_history = self.database.get_test_history()
        fc_log("threshold", f'[INFO] Testes restantes após remoção: {len(self.test_history)}')      
        # Atualiza a interface
        self.update_history_tree()
        self.update_graphs_from_history()
        
        # Marca que o estado foi alterado
        if deleted_count > 0:
            self._mark_state_changed()
            fc_log("threshold", f'[SAVE] Estado marcado como alterado')
        
        if deleted_count > 0:
            messagebox.showinfo(
                t('threshold.success'),
                t('threshold.success_tests_removed_msg').format(count=deleted_count))
            fc_log("threshold", f'[OK] Remoção concluída: {deleted_count} testes removidos')
        else:
            messagebox.showerror(
                t('threshold.error'),
                t('threshold.error_no_tests_removed_msg'))
            fc_log("threshold", f'[ERRO] Remoção falhou: nenhum teste removido')   
    def save_selected_tests_to_json(self):
        """Salva os testes selecionados em arquivos JSON separados"""
        fc_log("threshold", '[INFO] DEBUG: Iniciando save_selected_tests_to_json...')
        
        # Recarrega dados frescos do banco
        self.test_history = self.database.get_test_history()
        fc_log("threshold", f'[INFO] Total de testes carregados: {len(self.test_history)}')
        
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        fc_log("threshold", f'[OK] Testes selecionados encontrados: {len(selected_tests)}')
        
        if not selected_tests:
            messagebox.showwarning(
                t('threshold.no_test_selected_save'),
                t('threshold.no_test_selected_save_msg'))
            fc_log("threshold", '[AVISO] Nenhum teste selecionado para salvar')
            return
        
        # Permite ao usuário escolher o diretório para salvar os arquivos
        export_dir = filedialog.askdirectory(
            title=t('threshold.choose_folder_save_selected_tests'),
            initialdir=os.getcwd()
        )
        
        if not export_dir:  # Usuário cancelou a seleção
            fc_log("threshold", '[AVISO] Usuário cancelou a seleção do diretório')
            return
        
        fc_log("threshold", f'📁 Diretório selecionado pelo usuário: {export_dir}')    
        # Verifica se o diretório existe, se não, cria
        if not os.path.exists(export_dir):
            try:
                os.makedirs(export_dir)
                fc_log("threshold", f'📁 Diretório criado: {export_dir}')
            except Exception as e:
                messagebox.showerror(t('threshold.error_create_dir'), t('threshold.error_create_dir_msg').format(error=str(e)))
                fc_log("threshold", f'[ERRO] Erro ao criar diretório: {e}')
                return
        
        # Salva cada teste em arquivo separado
        saved_count = 0
        saved_files = []
        
        fc_log("threshold", f'[SAVE] Iniciando salvamento de {len(selected_tests)} testes...')
        
        for test in selected_tests:
            test_id = test.get('id')
            test_description = test.get('description', 'Sem descrição')
            test_epc = test.get('epc', 'N/A')
            test_date = test.get('date_time', 'N/A')
            
            fc_log("threshold", f"   [INFO] Salvando teste ID={test_id}, Descrição='{test_description}', EPC={test_epc}")           
            try:
                # Cria nome do arquivo baseado no teste
                safe_description = "".join(c for c in test_description if c.isalnum() or c in (' ', '-', '_')).rstrip()
                safe_description = safe_description.replace(' ', '_')
                
                # Formata data para nome de arquivo
                try:
                    if test_date != 'N/A':
                        # Tenta converter formato "30-08-2025 01:33" para "20250830_0133"
                        date_parts = test_date.split(' ')
                        if len(date_parts) == 2:
                            date_part = date_parts[0].split('-')
                            time_part = date_parts[1].split(':')
                            if len(date_part) == 3 and len(time_part) == 2:
                                formatted_date = f"{date_part[2]}{date_part[1]}{date_part[0]}_{time_part[0]}{time_part[1]}"
                            else:
                                formatted_date = "unknown_date"
                        else:
                            formatted_date = "unknown_date"
                    else:
                        formatted_date = "unknown_date"
                except:
                    formatted_date = "unknown_date"
                
                filename = f"teste_{test_id}_{safe_description}_{test_epc}_{formatted_date}.json"
                filepath = os.path.join(export_dir, filename)
                
                # Prepara dados para salvar (inclui todos os parâmetros necessários)
                export_data = {
                    # Parâmetros principais no nível raiz para fácil acesso
                    "description": test.get('description', 'Sem Descrição'),
                    "test_name": test.get('description', 'Sem Descrição'),
                    "attenuator": test.get('attenuator'),
                    "distance": test.get('distance'),
                    "min_frequency": getattr(self, 'min_freq_var', tk.StringVar(value='800')).get(),
                    "max_frequency": getattr(self, 'max_freq_var', tk.StringVar(value='1000')).get(),
                    "max_power": getattr(self, 'max_power_var', tk.StringVar(value='20')).get(),
                    "freq_step": getattr(self, 'freq_step_var', tk.StringVar(value='5')).get(),
                    "power_step": getattr(self, 'power_step_var', tk.StringVar(value='0.5')).get(),
                    
                    "test_info": {
                        "id": test.get('id'),
                        "epc": test.get('epc'),
                        "tag_name": test.get('tag_name'),
                        "description": test.get('description'),
                        "date_time": test.get('date_time'),
                        "test_start_time": test.get('test_start_time'),
                        "test_end_time": test.get('test_end_time'),
                        "test_duration_seconds": test.get('test_duration_seconds'),
                        "attenuator": test.get('attenuator'),
                        "distance": test.get('distance')
                    },
                    "test_parameters": {
                        "attenuator": test.get('attenuator'),
                        "distance": test.get('distance'),
                        "min_frequency": getattr(self, 'min_freq_var', tk.StringVar(value='800')).get(),
                        "max_frequency": getattr(self, 'max_freq_var', tk.StringVar(value='1000')).get(),
                        "max_power": getattr(self, 'max_power_var', tk.StringVar(value='20')).get(),
                        "freq_step": getattr(self, 'freq_step_var', tk.StringVar(value='5')).get(),
                        "power_step": getattr(self, 'power_step_var', tk.StringVar(value='0.5')).get()
                    },
                    "results": self._prepare_results_with_parameters(test.get('results', []), test),
                    "export_info": {
                        "exported_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                        "exported_by": "FastChecker Threshold Module",
                        "total_results": len(test.get('results', []))
                    }
                }
                
                # Log detalhado dos parâmetros sendo salvos
                fc_log("threshold", f'   [INFO] Parâmetros sendo salvos:')
                fc_log("threshold", f"      - Description: {export_data.get('description', 'N/A')}")
                fc_log("threshold", f"      - Test Name: {export_data.get('test_name', 'N/A')}")
                fc_log("threshold", f"      - Attenuator: {export_data.get('attenuator', 'N/A')}")
                fc_log("threshold", f"      - Distance: {export_data.get('distance', 'N/A')}")
                fc_log("threshold", f"      - Min Frequency: {export_data.get('min_frequency', 'N/A')}")
                fc_log("threshold", f"      - Max Frequency: {export_data.get('max_frequency', 'N/A')}")
                
                # Salva o arquivo
                with open(filepath, 'w', encoding='utf-8') as f:
                    json.dump(export_data, f, indent=2, ensure_ascii=False)
                
                saved_count += 1
                saved_files.append(filename)
                fc_log("threshold", f'   [OK] Teste {test_id} salvo em: {filename}')
                
            except Exception as e:
                fc_log("threshold", f'   [ERRO] Erro ao salvar teste {test_id}: {e}')
        
        # Mostra resultado final
        if saved_count > 0:
            files_list = "\n".join([f"• {filename}" for filename in saved_files])
            success_message = t('threshold.save_success_detail').format(
                count=saved_count,
                dir=os.path.abspath(export_dir),
                files=files_list
            )
            messagebox.showinfo(t('threshold.save_completed'), success_message)
            fc_log("threshold", f'[OK] Salvamento concluído: {saved_count} testes salvos em {export_dir}')            
            # Abre o diretório no explorador de arquivos
            if open_path_with_default_app(export_dir, log_tag="threshold"):
                fc_log("threshold", f'📁 Diretório aberto no explorador')
            else:
                fc_log("threshold", f'[AVISO] Não foi possível abrir o diretório automaticamente')
        else:
            messagebox.showerror(
                t('threshold.error_save_no_tests'),
                t('threshold.error_save_no_tests_msg'))
            fc_log("threshold", f'[ERRO] Salvamento falhou: nenhum teste salvo')
    
    def _prepare_results_with_parameters(self, results, test):
        """Prepara os resultados garantindo que todos tenham os parâmetros necessários"""
        prepared_results = []
        
        # Obtém valores padrão dos parâmetros
        default_attenuator = test.get('attenuator', 0.0)
        default_distance = test.get('distance', 1.0)
        
        for result in results:
            if isinstance(result, dict):
                # Garante que cada resultado tenha os parâmetros de teste
                prepared_result = result.copy()
                
                # SEMPRE adiciona os parâmetros principais (mesmo que sejam None)
                prepared_result['attenuator'] = prepared_result.get('attenuator', default_attenuator)
                prepared_result['distance'] = prepared_result.get('distance', default_distance)
                
                # Adiciona descrição do teste em cada resultado
                prepared_result['test_description'] = test.get('description', 'Sem Descrição')
                prepared_result['test_name'] = test.get('description', 'Sem Descrição')
                
                # Adiciona parâmetros de frequência se não existirem
                if 'freq_mhz' in prepared_result and 'frequency' not in prepared_result:
                    prepared_result['frequency'] = prepared_result['freq_mhz']
                
                # Garante que os valores não sejam None
                if prepared_result['attenuator'] is None:
                    prepared_result['attenuator'] = default_attenuator
                if prepared_result['distance'] is None:
                    prepared_result['distance'] = default_distance
                
                prepared_results.append(prepared_result)
            else:
                prepared_results.append(result)
        
        return prepared_results
    
    def export_selected_tests(self):
        """Exporta os testes selecionados"""
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        
        if not selected_tests:
            messagebox.showwarning(
                t('threshold.no_test_selected_export'),
                t('threshold.no_test_selected_export_msg'))
            return
        
        # Cria nome do arquivo
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"exported_tests_{timestamp}.json"
        
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(selected_tests, f, indent=4, ensure_ascii=False)
            
            messagebox.showinfo(t('threshold.export_completed'), t('threshold.export_completed_msg').format(filename=filename))
        except Exception as e:
            messagebox.showerror(t('threshold.export_error'), t('threshold.export_error_msg').format(error=str(e)))
    
    def export_selected_to_excel(self):
        """Exporta os testes selecionados para Excel (.xlsx)"""
        from tkinter import filedialog
        
        # Filtra apenas testes selecionados (show_in_graphs = True)
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        
        if not selected_tests:
            messagebox.showwarning(t('threshold.no_test_selected_export'), t('threshold.no_test_selected_export_msg'), parent=self)
            return
        
        # Dialog para salvar arquivo
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        default_filename = f"Threshold_Full_Database_{timestamp}.xlsx"
        
        filename = filedialog.asksaveasfilename(
            title=t('threshold.export_full_db_excel_title'),
            defaultextension=".xlsx",
            filetypes=[
                (t('fastthreshold.excel_files'), "*.xlsx"),
                (t('export_popup.filetype_all'), "*.*"),
            ],
            initialfile=default_filename,
            parent=self
        )
        
        if not filename:
            return  # Usuário cancelou
        
        try:
            fc_log("threshold", f'[INFO] Iniciando exportacao completa de {len(selected_tests)} teste(s)...')
            
            # Prepara dados COMPLETOS para exportação - UMA LINHA POR FREQUÊNCIA
            export_data = []
            total_rows = 0
            
            for test in selected_tests:
                test_id = test.get('id', 'N/A')
                test_name = test.get('description', '') or test.get('tag_name', '') or f"Teste {test_id}"
                epc = test.get('epc', 'N/A')
                attenuator = test.get('attenuator', 'N/A')
                distance = test.get('distance', 'N/A')
                test_start = test.get('test_start_time', test.get('date_time', 'N/A'))
                test_end = test.get('test_end_time', 'N/A')
                
                # Formata tempo decorrido sem casas decimais
                test_duration = test.get('test_duration_seconds', 'N/A')
                if test_duration != 'N/A' and test_duration is not None:
                    try:
                        test_duration = int(float(test_duration))
                    except (ValueError, TypeError):
                        pass
                
                # Obtém TODOS os resultados (cada frequência testada)
                results = test.get('results', [])
                
                if not results:
                    fc_log("threshold", f'[AVISO] Teste ID {test_id} sem resultados - pulando')
                    continue
                
                # Função auxiliar para formatar números
                def format_value(value, decimals=1):
                    """Formata valor numérico com número específico de casas decimais"""
                    if value == 'N/A' or value is None or value == '':
                        return 'N/A'
                    try:
                        num = float(value)
                        return round(num, decimals)
                    except (ValueError, TypeError):
                        return value
                
                # Cria UMA LINHA para CADA FREQUÊNCIA testada
                for result in results:
                    if isinstance(result, dict):
                        row = {
                            'ID Teste': test_id,
                            'Nome do Teste': test_name,
                            'EPC': epc,
                            'Atenuador (dB)': format_value(attenuator, 1),
                            'Distância (m)': format_value(distance, 1),
                            'Início': test_start,
                            'Fim': test_end,
                            'Duração (s)': test_duration,
                            'Frequência (MHz)': format_value(result.get('freq_mhz', 'N/A'), 2),
                            'Threshold Power (dBm)': format_value(result.get('threshold_power', 'N/A'), 1),
                            'Threshold RSSI (dBm)': format_value(result.get('threshold_rssi', 'N/A'), 1),
                            'Module Power (dBm)': format_value(result.get('module_power', 'N/A'), 1),
                            'Module RSSI (dBm)': format_value(result.get('rssi', 'N/A'), 1),
                            'Irradiated Power (dBm)': format_value(result.get('irradiated_power', 'N/A'), 1),
                            'Backscatter (dBm)': format_value(result.get('backscatter', 'N/A'), 1),
                            'Power on Tag Forward (dBm)': format_value(result.get('power_on_tag_forward', 'N/A'), 1),
                            'Power on Tag Reversed (dBm)': format_value(result.get('power_on_tag_reversed', 'N/A'), 1),
                            'Conversion Loss (dB)': format_value(result.get('conversion_loss', 'N/A'), 1),
                            'FCC Link Forward (m)': format_value(result.get('max_fcc_link_forward', 'N/A'), 1),
                            'FCC Link Reversed (m)': format_value(result.get('max_fcc_link_reversed', 'N/A'), 1),
                            'RCS (dBsm)': format_value(result.get('rcs', 'N/A'), 1)
                        }
                        export_data.append(row)
                        total_rows += 1
            
            if not export_data:
                messagebox.showwarning(
                    t('threshold.excel_no_valid_data_title'),
                    t('threshold.excel_no_valid_data_msg'),
                    parent=self)
                return
            
            fc_log("threshold", f'[INFO] Preparados {total_rows} linhas de dados (todas as frequencias testadas)')
            
            # Verifica se pandas está disponível
            try:
                import pandas as pd
                
                # Cria DataFrame e exporta para Excel
                df = pd.DataFrame(export_data)
                df.to_excel(filename, index=False, sheet_name='Dados Completos', engine='openpyxl')
                
                messagebox.showinfo(t('threshold.export_completed'), 
                                  t('threshold.export_excel_success').format(
                                      count=len(selected_tests),
                                      rows=total_rows,
                                      filename=filename
                                  ),
                                  parent=self)
                fc_log("threshold", f'[OK] {len(selected_tests)} testes, {total_rows} linhas exportadas para Excel: {filename}')
                
            except ImportError:
                # Fallback: Se pandas não estiver disponível, usa openpyxl diretamente
                try:
                    from openpyxl import Workbook
                    from openpyxl.styles import Font, Alignment, PatternFill
                    
                    wb = Workbook()
                    ws = wb.active
                    ws.title = "Dados Completos"
                    
                    # Cabeçalhos com formatação
                    headers = list(export_data[0].keys())
                    ws.append(headers)
                    
                    # Formata cabeçalho
                    header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
                    header_font = Font(bold=True, color="FFFFFF")
                    for cell in ws[1]:
                        cell.fill = header_fill
                        cell.font = header_font
                        cell.alignment = Alignment(horizontal='center', vertical='center')
                    
                    # Dados
                    for row_data in export_data:
                        ws.append(list(row_data.values()))
                    
                    # Ajusta largura das colunas
                    for column in ws.columns:
                        max_length = 0
                        column_letter = column[0].column_letter
                        for cell in column:
                            try:
                                if len(str(cell.value)) > max_length:
                                    max_length = len(str(cell.value))
                            except:
                                pass
                        adjusted_width = min(max_length + 2, 50)
                        ws.column_dimensions[column_letter].width = adjusted_width
                    
                    wb.save(filename)
                    
                    messagebox.showinfo(t('threshold.export_completed'), 
                                      t('threshold.export_excel_success').format(
                                          count=len(selected_tests),
                                          rows=total_rows,
                                          filename=filename
                                      ),
                                      parent=self)
                    fc_log("threshold", f'[OK] {len(selected_tests)} testes, {total_rows} linhas exportadas para Excel: {filename}')
                    
                except ImportError as import_err:
                    messagebox.showerror(t('threshold.error_library_required'), 
                                       t('threshold.error_library_required_msg').format(error=str(import_err)),
                                       parent=self)
                    fc_log("threshold", f'[ERRO] Bibliotecas necessárias não encontradas: {import_err}')                   
        except Exception as e:
            messagebox.showerror(t('threshold.error_export_excel'), t('threshold.error_export_excel_msg').format(error=str(e)), parent=self)
            fc_log("threshold", f'[ERRO] Erro ao exportar para Excel: {e}')
            import traceback
            traceback.print_exc()
    
    def import_test_data(self):
        """Importa dados de teste - SEMPRE ADICIONA aos testes existentes"""
        fc_log("threshold", '[INFO] DEBUG: Iniciando importação de testes...')       
        filepath = filedialog.askopenfilename(
            title=t('threshold.select_tests_file_title'),
            filetypes=(("JSON files", "*.json"), ("Todos os arquivos", "*.*"))
        )
        
        if not filepath:
            fc_log("threshold", '[AVISO] DEBUG: Nenhum arquivo selecionado')
            return
        
        fc_log("threshold", f'📁 DEBUG: Arquivo selecionado: {filepath}')      
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                imported_tests = json.load(f)
            
            fc_log("threshold", f'[INFO] DEBUG: Arquivo carregado com {len(imported_tests)} testes')
            
            # Verifica se é uma lista de testes
            if not isinstance(imported_tests, list):
                fc_log("threshold", '[ERRO] DEBUG: Arquivo não é uma lista de testes')
                messagebox.showerror(t('threshold.error_invalid_file_list'), t('threshold.error_invalid_file_list_msg'))
                return
            
            # Carrega dados atuais do banco para evitar conflitos de ID
            current_tests = self.database.get_test_history()
            fc_log("threshold", f'[DATA] DEBUG: Testes atuais no banco: {len(current_tests)}')
            
            max_id = max([test.get('id', 0) for test in current_tests], default=0)
            fc_log("threshold", f'🔢 DEBUG: Maior ID atual: {max_id}')          
            imported_count = 0
            
            # Adiciona os testes importados (SEMPRE ADICIONA, NUNCA SUBSTITUI)
            for i, test in enumerate(imported_tests):
                fc_log("threshold", f"[INFO] DEBUG: Processando teste {i + 1}/{len(imported_tests)}: {test.get('description', 'Sem descrição')}")
                fc_log("threshold", f'[DATA] DEBUG: Estrutura do teste: {list(test.keys())}')
                fc_log("threshold", f"[DATA] DEBUG: EPC: {test.get('epc', 'N/A')}")
                fc_log("threshold", f"[DATA] DEBUG: Data: {test.get('date_time', 'N/A')}")
                
                # Gera ID único baseado no maior ID existente
                max_id += 1
                test['id'] = max_id
                # Preserva show_in_graphs do arquivo importado, ou usa True para mostrar automaticamente
                if 'show_in_graphs' not in test:
                    test['show_in_graphs'] = True  # Mostra automaticamente testes importados
                
                fc_log("threshold", f"🆔 DEBUG: Teste recebeu ID: {test['id']}")              
                # Adiciona ao banco de dados
                success = self.database.add_test_to_history(test)
                fc_log("threshold", f"[SAVE] DEBUG: Resultado do banco para teste {test['id']}: {success}")
                
                if success:
                    imported_count += 1
                    fc_log("threshold", f"[OK] Teste {test['id']} importado: {test.get('description', 'Sem descrição')}")
                else:
                    fc_log("threshold", f"[ERRO] Erro ao importar teste: {test.get('description', 'Sem descrição')}")
                    fc_log("threshold", f'[ERRO] DEBUG: Possível causa: teste duplicado ou erro no banco')            
            fc_log("threshold", f'[INFO] DEBUG: Total importado: {imported_count}/{len(imported_tests)}')
            
            # Recarrega dados do banco após importação
            self.test_history = self.database.get_test_history()
            self.test_counter = len(self.test_history)
            fc_log("threshold", f'[INFO] DEBUG: Histórico recarregado: {len(self.test_history)} testes')            
            # Atualiza a interface
            self.update_history_tree()
            fc_log("threshold", '[INFO] DEBUG: Interface atualizada')
            
            # Atualiza os gráficos se houver testes marcados para exibição
            self.update_graphs_from_history()
            fc_log("threshold", '[INFO] DEBUG: Gráficos atualizados após importação')         
            messagebox.showinfo(t('threshold.import_completed'), 
                              t('threshold.import_success_detail').format(
                                  imported=imported_count,
                                  total=len(imported_tests),
                                  history_count=len(self.test_history)
                              ))
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] DEBUG: Erro na importação: {e}')
            import traceback
            traceback.print_exc()
            messagebox.showerror(t('threshold.error_import'), t('threshold.error_import_msg').format(error=str(e)))
    
    def save_test_results(self):
        """Salva os resultados dos testes selecionados"""
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        
        if not selected_tests:
            messagebox.showwarning(
                t('threshold.no_test_selected_save'),
                t('threshold.no_test_selected_save_msg'))
            return
        
        # Cria nome do arquivo
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"test_results_{timestamp}.csv"
        
        try:
            # Prepara dados para CSV
            all_data = []
            for test in selected_tests:
                results = test.get('results', [])
                for result in results:
                    if isinstance(result, dict):
                        all_data.append({
                            'Test_ID': test['id'],
                            'Description': test.get('description', ''),
                            'EPC': test.get('epc', ''),
                            'Date_Time': test.get('date_time', ''),
                            'Frequency_MHz': result.get('freq_mhz', ''),
                            'Module_Power_dBm': result.get('threshold_power', ''),
                            'RSSI_dBm': result.get('threshold_rssi', '')
                        })
            
            # Salva CSV
            df = pd.DataFrame(all_data)
            df.to_csv(filename, index=False)
            
            messagebox.showinfo(t('threshold.save_completed'), t('threshold.save_completed_msg').format(filename=filename))
            
        except Exception as e:
            messagebox.showerror(
                t('threshold.error_saving_results'),
                t('threshold.error_saving_results_msg').format(error=str(e)))
    
    def load_test_results(self):
        """Carrega resultados de teste de arquivo"""
        filename = filedialog.askopenfilename(
            title=t('threshold.load_test_results_file_title'),
            filetypes=[
                (t('export_popup.filetype_json'), "*.json"),
                (t('export_popup.filetype_all'), "*.*"),
            ],
            parent=self,
        )
        
        if filename:
            try:
                with open(filename, 'r', encoding='utf-8') as f:
                    test_data = json.load(f)
                
                # Valida estrutura dos dados
                if not isinstance(test_data, dict) or 'results' not in test_data:
                    messagebox.showerror(t('threshold.invalid_file'), t('threshold.invalid_file_msg'))
                    return
                
                # Adiciona ao histórico
                if self.database.add_test_to_history(test_data):
                    self.update_history_tree()
                    messagebox.showinfo(t('threshold.data_loaded_success'), t('threshold.data_loaded_success_msg'))
                else:
                    messagebox.showerror(t('threshold.error_load_data'), t('threshold.error_load_data_msg'))
                    
            except Exception as e:
                messagebox.showerror(t('threshold.error_load_data_exception'), t('threshold.error_load_data_exception_msg').format(error=str(e)))
    
    def configure_test_parameters(self):
        """Configura parâmetros de teste em janela separada"""
        config_window = tk.Toplevel(self)
        config_window.title("Configuração do Teste")
        config_window.geometry("400x500")
        config_window.transient(self)
        config_window.grab_set()
        
        # Frame principal
        main_frame = tk.Frame(config_window)
        main_frame.pack(fill='both', expand=True, padx=20, pady=20)
        
        # Título
        tk.Label(main_frame, text="Configurações de Teste", font=("Arial", 14, "bold")).pack(pady=(0, 20))
        
        # Variáveis temporárias
        temp_min_freq = tk.StringVar(value=self.min_freq_var.get())
        temp_max_freq = tk.StringVar(value=self.max_freq_var.get())
        temp_max_power = tk.StringVar(value=self.max_power_var.get())
        temp_freq_step = tk.StringVar(value=self.freq_step_var.get())
        temp_power_step = tk.StringVar(value=self.power_step_var.get())
        temp_distance = tk.StringVar(value=self.distance_var.get())
        temp_attenuator = tk.StringVar(value=self.attenuator_var.get())
        
        # Frame para configurações
        config_frame = tk.Frame(main_frame)
        config_frame.pack(fill='x', pady=10)
        
        # Frequência
        freq_frame = tk.LabelFrame(config_frame, text="Configurações de Frequência", padx=10, pady=5)
        freq_frame.pack(fill='x', pady=5)
        
        tk.Label(freq_frame, text="Frequência Mínima (MHz):").pack(anchor='w')
        tk.Entry(freq_frame, textvariable=temp_min_freq, width=15).pack(anchor='w', pady=2)
        
        tk.Label(freq_frame, text="Frequência Máxima (MHz):").pack(anchor='w')
        tk.Entry(freq_frame, textvariable=temp_max_freq, width=15).pack(anchor='w', pady=2)
        
        tk.Label(freq_frame, text="Passo de Frequência (MHz):").pack(anchor='w')
        tk.Entry(freq_frame, textvariable=temp_freq_step, width=15).pack(anchor='w', pady=2)
        
        # Potência
        power_frame = tk.LabelFrame(config_frame, text="Configurações de Potência", padx=10, pady=5)
        power_frame.pack(fill='x', pady=5)
        
        tk.Label(power_frame, text="Potência Máxima (dBm):").pack(anchor='w')
        tk.Entry(power_frame, textvariable=temp_max_power, width=15).pack(anchor='w', pady=2)
        
        tk.Label(power_frame, text="Passo de Potência (dBm):").pack(anchor='w')
        tk.Entry(power_frame, textvariable=temp_power_step, width=15).pack(anchor='w', pady=2)
        
        # Distância e Atenuador
        other_frame = tk.LabelFrame(config_frame, text="Outras Configurações", padx=10, pady=5)
        other_frame.pack(fill='x', pady=5)
        
        tk.Label(other_frame, text="Distância (m):").pack(anchor='w')
        tk.Entry(other_frame, textvariable=temp_distance, width=15).pack(anchor='w', pady=2)
        
        tk.Label(other_frame, text="Atenuador (dB):").pack(anchor='w')
        tk.Entry(other_frame, textvariable=temp_attenuator, width=15).pack(anchor='w', pady=2)
        
        def apply_config():
            """Aplica as configurações"""
            try:
                # Valida valores
                min_freq = self._parse_float(temp_min_freq.get())
                max_freq = self._parse_float(temp_max_freq.get())
                max_power = self._parse_float(temp_max_power.get())
                freq_step = self._parse_float(temp_freq_step.get())
                power_step = self._parse_float(temp_power_step.get())
                distance = self._parse_float(temp_distance.get())
                attenuator = self._parse_float(temp_attenuator.get())
                if abs(freq_step - 0.5) < 1e-9:
                    freq_step = 1.0
                
                # Validações básicas
                if min_freq >= max_freq:
                    messagebox.showerror(t('threshold.error_freq_min_max'), t('threshold.error_freq_min_max_msg'))
                    return
                
                if freq_step <= 0 or power_step <= 0:
                    messagebox.showerror(t('threshold.error_steps'), t('threshold.error_steps_msg'))
                    return
                
                # Aplica configurações
                self.min_freq_var.set(str(min_freq))
                self.max_freq_var.set(str(max_freq))
                self.max_power_var.set(str(max_power))
                self.freq_step_var.set(str(freq_step))
                self.power_step_var.set(str(power_step))
                self.distance_var.set(str(distance))
                self.attenuator_var.set(str(attenuator))
                
                messagebox.showinfo(t('threshold.settings_applied'), t('threshold.settings_applied_msg'))
                config_window.destroy()
                
            except ValueError:
                messagebox.showerror(t('threshold.error_invalid_values'), t('threshold.error_invalid_values_msg'))
        
        def cancel():
            """Fecha janela sem aplicar"""
            config_window.destroy()
        
        # Botões
        button_frame = tk.Frame(main_frame)
        button_frame.pack(fill='x', pady=20)
        tk.Button(button_frame, text="Aplicar", command=apply_config).pack(side='left', padx=5)
        tk.Button(button_frame, text="Cancelar", command=cancel).pack(side='left', padx=5)
    
    def view_test_details(self):
        """Visualiza detalhes dos testes selecionados"""
        selected_tests = [test for test in self.test_history if test.get('show_in_graphs', False)]
        
        if not selected_tests:
            messagebox.showwarning(
                t('threshold.no_test_selected_title'),
                t('threshold.no_test_selected_details_msg'))
            return
        
        if len(selected_tests) == 1:
            # Mostra detalhes de um teste específico
            test = selected_tests[0]
            self.show_test_table(test['results'], test['tag_info'], test['description'])
        else:
            # Mostra resumo de múltiplos testes
            details_window = tk.Toplevel(self)
            details_window.title("Detalhes dos Testes Selecionados")
            details_window.geometry("600x400")
            details_window.transient(self)
            details_window.grab_set()
            
            # Cria Treeview para detalhes
            columns = ('ID', 'EPC', 'Description', 'Date Time', 'Points')
            tree = ttk.Treeview(details_window, columns=columns, show='headings', height=15)
            
            for col in columns:
                tree.heading(col, text=col)
                tree.column(col, width=120, anchor='w')
            
            # Adiciona scrollbar
            scrollbar = ttk.Scrollbar(details_window, orient='vertical', command=tree.yview)
            tree.configure(yscrollcommand=scrollbar.set)
            
            # Organiza widgets
            tree.pack(side='left', fill='both', expand=True, padx=10, pady=10)
            scrollbar.pack(side='right', fill='y', pady=10)
            
            # Insere dados
            for test in selected_tests:
                points = len(test.get('results', []))
                tree.insert('', 'end', values=(
                    test['id'],
                    test.get('epc', ''),
                    test.get('description', ''),
                    test.get('date_time', ''),
                    points
                ))
            
            # Botão de fechar
            tk.Button(details_window, text="Fechar", command=details_window.destroy).pack(pady=10)

    def quick_tag_detection(self, freq, power, epc):
        """Detecção rápida de tag SEM medição RSSI - para otimização de performance
        
        OBJETIVO: Apenas confirmar se a tag foi detectada (True/False)
        PERFORMANCE: 1 tentativa vs. 10 tentativas anteriores
        REDUÇÃO DE TEMPO: ~90% por medição normal
        PRECISÃO: Baixa (só importa detecção, não valor RSSI)
        OTIMIZAÇÃO: Porta COM já está aberta pela função principal
        
        RETORNO: True se tag detectada, False caso contrário
        """
        if not HARDWARE_AVAILABLE:
            fc_log("threshold", 'Hardware não disponível para detecção rápida')
            return False
        
        try:
            # OTIMIZAÇÃO: Não abre/fecha porta COM (já está aberta)
            output_buffer = ctypes.create_string_buffer(256)
            output_len = ctypes.c_uint(0)
            
            # Configura potência e frequência com validação rigorosa
            if not self._execute_rfid_command_with_validation(
                RFID_CMD_SET_TXPOWER,
                bytes([0x00, 0x00]) + int(power * 100).to_bytes(2, 'big') * 2,
                6,
                f"Configuração de potência {power}dBm para detecção rápida"
            ):
                fc_log("threshold", '[ERRO] Falha ao configurar potência para detecção rápida')
                return False
            
            # CORREÇÃO: Não reconfigura frequência - já foi configurada pela função principal
            
            # MODIFICAÇÃO 3: Loop com timeout de 2.0 segundos (aumentado de 0.5s para maior persistência)
            TIMEOUT_SEGUNDOS = 2.0
            start_time = time.time()
            
            while time.time() - start_time < TIMEOUT_SEGUNDOS:
                # Executa inventário para detecção
                inv_tag_input_data = bytes([0x00, 0x64])  # 100ms de inventário (como EPC_RSSI)
                result = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, 
                                             ctypes.c_char_p(inv_tag_input_data), 2, 
                                             output_buffer, ctypes.byref(output_len))
                
                if result == 0:
                    detected_epc = self._extract_epc_from_inv_response(output_buffer.raw, output_len.value)
                    if not detected_epc:
                        time.sleep(0.05)
                        continue
                    
                    # Verifica se é a tag esperada
                    if epc and epc.upper() == detected_epc:
                        return True  # Tag detectada com sucesso
                    else:
                        # NOVO: Continua tentando até encontrar a tag correta (como EPC_RSSI)
                        fc_log("threshold", f'[AVISO] Tag diferente detectada: {detected_epc}')
                
                # Pequena pausa para não sobrecarregar a CPU
                time.sleep(0.05)
            
            return False  # Tag não detectada no tempo limite
                    
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro na detecção rápida: {e}')
            return False

    def quick_rssi_measurement(self, freq, power, epc):
        """Medição rápida de RSSI durante detecção de tag
        
        OBJETIVO: Medir RSSI de forma rápida quando a tag é detectada
        NOVA FUNCIONALIDADE: Usa estabilização RSSI para melhor repetitibilidade
        PERFORMANCE: Otimizada com estabilização automática
        OTIMIZAÇÃO: Porta COM já está aberta pela função principal
        RETORNO: Valor RSSI estável em dBm ou None se falhar
        """
        if not HARDWARE_AVAILABLE:
            return None
        
        try:
            # NOVA IMPLEMENTAÇÃO: Usa estabilização RSSI para melhor repetitibilidade
            stable_rssi = self._check_rssi_stability(freq, power, epc)
            
            if stable_rssi is not None:
                return stable_rssi
            else:
                # NOVA FUNCIONALIDADE: Aumenta potência e repete medição
                max_power = 25.0  # Limite máximo de potência
                increased_power_rssi = self._increase_power_and_retry_rssi(freq, power, epc, max_power)
                
                if increased_power_rssi is not None:
                    return increased_power_rssi
                else:
                    # FALLBACK: Medição rápida tradicional se estabilização falhar
                    output_buffer = ctypes.create_string_buffer(256)
                    output_len = ctypes.c_uint(0)
                    
                    # Configura potência e frequência com validação rigorosa
                    if not self._execute_rfid_command_with_validation(
                        RFID_CMD_SET_TXPOWER,
                        bytes([0x00, 0x00]) + int(power * 100).to_bytes(2, 'big') * 2,
                        6,
                        f"Configuração de potência {power}dBm para RSSI rápido"
                    ):
                        return None
                    
                    if not self._execute_rfid_command_with_validation(
                        RFID_CMD_SET_FREQ_TABLE,
                        b'\x01' + int(freq * 1000).to_bytes(3, 'big'),
                        4,
                        f"Configuração de frequência {freq}MHz para RSSI rápido"
                    ):
                        return None
                    
                    # MODIFICAÇÃO 3: Loop com timeout de 2.0 segundos (aumentado de 0.5s para maior persistência)
                    TIMEOUT_SEGUNDOS = 2.0
                    start_time = time.time()
                    
                    while time.time() - start_time < TIMEOUT_SEGUNDOS:
                        # Executa inventário para medição rápida de RSSI
                        inv_tag_input_data = bytes([0x00, 0x64])  # 100ms de inventário (como EPC_RSSI)
                        result = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, 
                                                     ctypes.c_char_p(inv_tag_input_data), 2, 
                                                     output_buffer, ctypes.byref(output_len))
                        
                        if result == 0:
                            detected_epc = self._extract_epc_from_inv_response(output_buffer.raw, output_len.value)
                            if not detected_epc:
                                time.sleep(0.05)
                                continue
                            
                            # Verifica se é a tag esperada
                            if epc and epc.upper() == detected_epc:
                                # Extrai RSSI usando posição relativa (como outros módulos)
                                rssi_bytes = output_buffer.raw[output_len.value - 3:output_len.value - 1]
                                
                                # Usa o método dos outros módulos (struct.unpack)
                                rssi_raw = struct.unpack('>h', rssi_bytes)[0]
                                
                                # Aplica a mesma divisão do FastChecker: cast para int primeiro, depois divide por 10
                                rssi = float(int(rssi_raw)) / 10.0
                                
                                return rssi
                            else:
                                # NOVO: Continua tentando até encontrar a tag correta (como EPC_RSSI)
                                fc_log("threshold", f'[AVISO] Tag diferente detectada no RSSI rápido: {detected_epc}')                        
                        # Pequena pausa para não sobrecarregar a CPU
                        time.sleep(0.05)
                    
                    return None
                    
        except Exception as e:
            return None

    def _verify_threshold_stability(self, freq, threshold_power, epc):
        """Verifica a estabilidade do threshold antes de medir RSSI preciso
        
        OBJETIVO: Garantir que a tag ainda é detectável na potência do threshold
        PROBLEMA RESOLVIDO: Evitar falhas de RSSI quando a tag "desaparece" entre detecção e medição
        
        RETORNO: 
        - threshold_power se estável
        - power_above se threshold instável (usa potência acima)
        - None se não conseguir verificar
        """
        if not HARDWARE_AVAILABLE:
            fc_log("threshold", 'Hardware não disponível para verificação de estabilidade')
            return None
        
        try:
            with self.com_port_lock:
                # Tenta abrir conexão
                if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) != 0:
                    fc_log("threshold", f'[ERRO] Não foi possível abrir COM{self.com_port} para verificação de estabilidade')
                    return None
                
                try:
                    output_buffer = ctypes.create_string_buffer(256)
                    output_len = ctypes.c_uint(0)
                    
                    # Configura potência do threshold
                    power_val = int(threshold_power * 100)
                    power_data = bytes([0x00, 0x00]) + power_val.to_bytes(2, 'big') * 2
                    result = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, 
                                                  ctypes.c_char_p(power_data), 6, 
                                                  output_buffer, ctypes.byref(output_len))
                    
                    if result != 0:
                        fc_log("threshold", f'[AVISO] Erro ao configurar potência para verificação: {result}')
                        return None
                    
                    # Configura frequência
                    freq_data = b'\x01' + int(freq * 1000).to_bytes(3, 'big')
                    result = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_FREQ_TABLE, 
                                                  ctypes.c_char_p(freq_data), 4, 
                                                  output_buffer, ctypes.byref(output_len))
                    
                    if result != 0:
                        fc_log("threshold", f'[AVISO] Erro ao configurar frequência para verificação: {result}')
                        return None
                    
                    # VERIFICAÇÃO DE ESTABILIDADE: 3 tentativas para confirmar
                    stable_detections = 0
                    total_attempts = 3
                    
                    for attempt in range(total_attempts):
                        inv_tag_input_data = bytes([0x00, 0x64])  # 100ms de inventário (como EPC_RSSI)
                        result = rfid_sdk.UHF_RFID_Set(RFID_CMD_INV_TAG, 
                                                      ctypes.c_char_p(inv_tag_input_data), 2, 
                                                      output_buffer, ctypes.byref(output_len))
                        
                        if result == 0:
                            detected_epc = self._extract_epc_from_inv_response(output_buffer.raw, output_len.value)
                            if not detected_epc:
                                continue
                            
                            # Verifica se é a tag esperada
                            if epc and epc.upper() == detected_epc:
                                stable_detections += 1
                        
                        # Pequeno delay entre tentativas
                        if attempt < total_attempts - 1:
                            self._sleep_with_cancellation_check(0.05)
                            if self.global_test_cancelled:
                                break
                    
                    # ANÁLISE DE ESTABILIDADE
                    stability_ratio = stable_detections / total_attempts
                    
                    if stability_ratio >= 0.67:  # 2 ou mais detecções em 3 tentativas
                        fc_log("threshold", f'[OK] Threshold estável: {stable_detections}/{total_attempts} detecções em {threshold_power:.1f}dBm')
                        return threshold_power
                    
                    elif stability_ratio >= 0.33:  # 1 detecção em 3 tentativas
                        fc_log("threshold", f'[AVISO] Threshold instável: {stable_detections}/{total_attempts} detecções em {threshold_power:.1f}dBm')
                        # Tenta potência acima (mais estável)
                        power_above = threshold_power + 0.5
                        if power_above <= 25.0:  # Limite máximo
                            fc_log("threshold", f'[INFO] Tentando potência acima: {power_above:.1f}dBm para maior estabilidade')
                            return power_above
                        else:
                            fc_log("threshold", f'[AVISO] Potência acima excede limite máximo, usando threshold original')
                            return threshold_power
                    
                    else:  # 0 detecções em 3 tentativas
                        fc_log("threshold", f'[ERRO] Threshold muito instável: {stable_detections}/{total_attempts} detecções em {threshold_power:.1f}dBm')
                        # Tenta potência acima (mais estável)
                        power_above = threshold_power + 1.0
                        if power_above <= 25.0:  # Limite máximo
                            fc_log("threshold", f'[INFO] Tentando potência acima: {power_above:.1f}dBm para maior estabilidade')
                            return power_above
                        else:
                            fc_log("threshold", f'[AVISO] Potência acima excede limite máximo, usando threshold original')
                            return threshold_power
                        
                finally:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro na verificação de estabilidade: {e}')
            return None

    def _get_license_limits(self):
        """Obtém os limites de licença do app_shell"""
        if self.app_shell:
            limits = self.app_shell._calculate_license_limits(["Threshold", "FastChecker"])
            fc_log("threshold", f'[INFO] Threshold: Licença obtida do app_shell: {limits}')
            return limits
        else:
            # Limites padrão se não houver app_shell
            fc_log("threshold", '[INFO] Threshold: App_shell não disponível, usando limites padrão')
            return {
                'min_freq': 800,
                'max_freq': 1000,
                'min_power': 5,
                'max_power': 25,
                'is_licensed': True
            }

    def _max_link_forward_eirp_dbm_for_active_license(self) -> float:
        """Teto EIRP (dBm) para Max Link Forward: 33 dBm se licença ETSI, senão 36,02 dBm."""
        limits = getattr(self, "license_limits", None) or {}
        return max_link_forward_eirp_dbm_for_license(limits.get("license_name"))
    
    def update_license_from_app_shell(self):
        """Atualiza a licença do app_shell quando disponível"""
        if self.app_shell:
            new_limits = self.app_shell._calculate_license_limits(["Threshold", "FastChecker"])
            fc_log("threshold", f'[INFO] Threshold: Licença atualizada do app_shell: {new_limits}')
            license_limits = new_limits
            # Atualiza o estado da UI com a nova licença
            self.update_ui_state()
            return True
        return False
    
    def update_license_status(self, new_license_status):
        """NOVO: Atualiza o status da licença e a interface do usuário"""
        try:
            fc_log("threshold", f'[INFO] ThresholdModule: Atualizando status da licença para {new_license_status}')            
            # Atualiza o status interno
            if new_license_status:
                # Com licença: obtém limites do app_shell
                if self.app_shell:
                    new_limits = self.app_shell._calculate_license_limits(["Threshold", "FastChecker"])
                    self.license_limits = new_limits
                else:
                    # Fallback: licença padrão
                    self.license_limits = {
                        'min_freq': 800, 'max_freq': 1000, 
                        'min_power': 5, 'max_power': 25, 
                        'is_licensed': True
                    }
            else:
                # Sem licença: modo browser
                self.license_limits = {
                    'min_freq': 800, 'max_freq': 1000, 
                    'min_power': 5, 'max_power': 25, 
                    'is_licensed': False
                }
            
            # Atualiza a interface
            self.update_ui_state()
            
            fc_log("threshold", f'[OK] ThresholdModule: Status da licença atualizado com sucesso')            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao atualizar status da licença no ThresholdModule: {e}')    
    def update_ui_state(self):
        """Atualiza o estado dos botões baseado na licença"""
        try:
            is_licensed = self.license_limits.get('is_licensed', False)
            fc_log("threshold", f'[INFO] Threshold: update_ui_state - is_licensed: {is_licensed}')
            fc_log("threshold", f'[INFO] Threshold: license_limits: {self.license_limits}')
            
            # CORREÇÃO: Se o teste foi cancelado, força habilitação do botão
            if hasattr(self, 'global_test_cancelled') and self.global_test_cancelled:
                fc_log("threshold", '[INFO] Threshold: Teste foi cancelado - forçando habilitação do botão')
                self._set_test_button_idle()
                return
            
            if is_licensed:
                # Com licença, botão único em estado Iniciar Teste
                self._set_test_button_idle()
                self.show_table_button.config(state='normal')
                # Botão VSWR removido
                self.save_project_button.config(state='normal')
                self.delete_project_button.config(state='normal')
                
                # CORREÇÃO: Todos os campos de entrada habilitados com licença
                self.min_freq_entry.config(state='normal')
                self.max_freq_entry.config(state='normal')
                self.max_power_entry.config(state='normal')
                self.freq_step_combobox.config(state='readonly')
                self.power_step_combobox.config(state='readonly')
                self.distance_entry.config(state='normal')
                self.attenuator_entry.config(state='normal')
                self.description_entry.config(state='normal')
                
                # CORREÇÃO: Campos de projeto habilitados com licença
                self.project_name_entry.config(state='normal')
                self.client_name_entry.config(state='normal')
                
                # CORREÇÃO: Dropdown de ações habilitado com licença
                self.action_dropdown.config(state='readonly')
                # Habilita eventos
                self.action_dropdown.bind('<<ComboboxSelected>>', self.on_action_selected)
                
                # CORREÇÃO: Oculta mensagem de modo browser quando há licença
                self.browser_frame.grid_remove()
                
                # CORREÇÃO: Treeview de tags habilitado com licença
                self.tags_tree.config(selectmode='browse')
                # Habilita eventos de clique
                self.tags_tree.bind('<Button-1>', self.on_tree_click)
                self.tags_tree.bind('<Button-3>', self.show_context_menu)
                # Habilita visualmente removendo tags de desabilitado
                self.tags_tree.tag_configure('normal', foreground='black')
                for item in self.tags_tree.get_children():
                    self.tags_tree.item(item, tags=('normal',))
                
                try:
                    self._apply_calibration_distance_and_attenuator_if_active()
                except Exception:
                    pass
                
            else:
                # Sem licença: bloquear apenas ações que acionam o reader
                self.start_button.config(state='disabled')
                # Liberar ações de browser
                self.show_table_button.config(state='normal')
                self.save_project_button.config(state='normal')
                self.delete_project_button.config(state='normal')
                
                # Permitir edição/campos de projeto em modo browser
                self.min_freq_entry.config(state='normal')
                self.max_freq_entry.config(state='normal')
                self.max_power_entry.config(state='normal')
                self.freq_step_combobox.config(state='readonly')
                self.power_step_combobox.config(state='readonly')
                self.distance_entry.config(state='normal')
                self.attenuator_entry.config(state='normal')
                self.description_entry.config(state='normal')
                
                # Campos de projeto liberados
                self.project_name_entry.config(state='normal')
                self.client_name_entry.config(state='normal')
                
                # Dropdown de ações liberado
                self.action_dropdown.config(state='readonly')
                # Garante que evento permaneça ativo
                self.action_dropdown.bind('<<ComboboxSelected>>', self.on_action_selected)
                
                # CORREÇÃO: Mostra mensagem de modo browser quando não há licença
                self.browser_frame.grid()
                
                # CORREÇÃO: Treeview de tags desabilitado sem licença
                self.tags_tree.config(selectmode='none')
                # Remove eventos de clique para bloquear interação
                self.tags_tree.unbind('<Button-1>')
                self.tags_tree.unbind('<Button-3>')
                # Bloqueia visualmente alterando cores
                self.tags_tree.tag_configure('disabled', foreground='gray')
                for item in self.tags_tree.get_children():
                    self.tags_tree.item(item, tags=('disabled',))
                
                try:
                    self._apply_calibration_distance_and_attenuator_if_active()
                except Exception:
                    pass
                
        except Exception as e:
            fc_log("threshold", f'[ERRO] Threshold: Erro ao atualizar estado da UI: {e}')

    def _apply_license_limits(self):
        """Aplica os limites de licença à interface"""
        fc_log("threshold", f'[INFO] Threshold: _apply_license_limits - license_limits: {self.license_limits}')
        if not self.license_limits.get('is_licensed', False):
            fc_log("threshold", '[INFO] Threshold: Sem licença válida, atualizando estado da UI')
            # CORREÇÃO: Atualiza o estado dos botões quando não há licença
            self.update_ui_state()
            return
        
        # Obtém limites da licença
        min_freq = self.license_limits.get('min_freq', 800)
        max_freq = self.license_limits.get('max_freq', 1000)
        
        # Aplica apenas limites de potência (frequências são validadas em tempo real)
        min_power = self.license_limits.get('min_power', 5)
        max_power = self.license_limits.get('max_power', 25)
        
        # NÃO força valores de frequência - permite entrada livre dentro da banda
        # Apenas valida se os valores atuais estão dentro dos limites
        try:
            current_min_freq = self._parse_float(self.min_freq_var.get())
            current_max_freq = self._parse_float(self.max_freq_var.get())
            
            # Se os valores atuais estão fora dos limites, apenas mostra aviso
            if current_min_freq < min_freq:
                fc_log("threshold", f'[AVISO] Frequência mínima atual ({current_min_freq} MHz) está abaixo do limite da licença ({min_freq} MHz)')
                fc_log("threshold", '💡 O usuário pode inserir qualquer valor dentro da banda {min_freq}-{max_freq} MHz')
            if current_max_freq > max_freq:
                fc_log("threshold", f'[AVISO] Frequência máxima atual ({current_max_freq} MHz) está acima do limite da licença ({max_freq} MHz)')
                fc_log("threshold", '💡 O usuário pode inserir qualquer valor dentro da banda {min_freq}-{max_freq} MHz')
        except ValueError:
            # Se valores inválidos, aplica valores padrão da licença
            self.min_freq_var.set(str(min_freq))
            self.max_freq_var.set(str(max_freq))
            fc_log("threshold", f'[OK] Valores de frequência inicializados com limites da licença: {min_freq}-{max_freq} MHz')       
        # Aplica apenas limites de potência
        try:
            current_max_power = self._parse_float(self.max_power_var.get())
            if current_max_power > max_power:
                self.max_power_var.set(str(max_power))
                fc_log("threshold", f'[AVISO] Potência máxima ajustada para {max_power} dBm (limite da licença)')
        except ValueError:
            self.max_power_var.set(str(max_power))
        
        fc_log("threshold", f'[OK] Limites de licença aplicados: Freq {min_freq}-{max_freq}MHz (validação no Start), Power {min_power}-{max_power}dBm')
        fc_log("threshold", f'💡 Usuário pode digitar livremente - validação ocorre apenas no botão Start Test')
        fc_log("threshold", f'[INFO] Eixos X dos gráficos configurados para {min_freq}-{max_freq} MHz (limites da licença)')       
        # Atualiza os gráficos para refletir os novos limites da licença
        self._update_graphs_license_limits()
        
        # CORREÇÃO: Atualiza o estado dos botões quando há licença
        self.update_ui_state()
    
    def _update_frequency_limits_label(self):
        """Atualiza o label de limites de frequência na interface - DESABILITADO"""
        pass
    


    def _plot_segmented_data(self, ax, frequencies, values, color, description, test_id):
        """Plota dados em segmentos separados para evitar linha contínua em faixas excluídas.
        Também configura um rótulo para uso na legenda (apenas uma entrada por teste)."""
        try:
            if not frequencies or not values or len(frequencies) != len(values):
                fc_log("threshold", f'[ERRO] Threshold: Dados inválidos para plotagem segmentada - test_id: {test_id}')
                return
            
            fc_log("threshold", f'[INFO] Threshold PLOTAGEM: Iniciando plotagem segmentada para test_id {test_id}')
            fc_log("threshold", f'[INFO] Threshold PLOTAGEM: Frequências originais: {frequencies}')            
            # CORREÇÃO: Visualização mostra TODOS os dados históricos (não filtra por licença)
            # A licença apenas limita EXECUÇÃO de novos testes, não visualização
            valid_frequencies = frequencies
            valid_values = values
            
            fc_log("threshold", f'[INFO] Threshold PLOTAGEM: Plotando TODOS os {len(frequencies)} pontos (sem filtro de licença)')            
            # Divide os dados em segmentos contínuos
            segments = self._create_frequency_segments_simple(valid_frequencies, valid_values)
            
            fc_log("threshold", f'[INFO] Threshold PLOTAGEM: Criados {len(segments)} segmentos para test_id {test_id}')
            
            # Plota cada segmento separadamente.
            # Apenas o primeiro segmento recebe label para aparecer na legenda.
            legend_label = description or f"Teste {test_id}"
            for i, (seg_freqs, seg_values) in enumerate(segments):
                if seg_freqs and seg_values:
                    linestyle = '-' if len(seg_freqs) > 1 else ''
                    if i == 0:
                        ax.plot(
                            seg_freqs,
                            seg_values,
                            color=color,
                            marker='o',
                            linestyle=linestyle,
                            linewidth=0.8,
                            markersize=2,
                            label=legend_label,
                        )
                    else:
                        # Entradas adicionais não aparecem na legenda
                        ax.plot(
                            seg_freqs,
                            seg_values,
                            color=color,
                            marker='o',
                            linestyle=linestyle,
                            linewidth=0.8,
                            markersize=2,
                            label="_nolegend_",
                        )
                    
                    fc_log("threshold", f'   [INFO] Segmento {i + 1}: {len(seg_freqs)} pontos de {min(seg_freqs):.1f} a {max(seg_freqs):.1f} MHz')
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Threshold: Erro na plotagem segmentada: {e}')
            # Fallback: plota TODOS os pontos sem linha (sem filtro de licença)
            legend_label = description or f"Teste {test_id}"
            added_label = False
            for i, freq in enumerate(frequencies):
                if not added_label:
                    ax.plot([freq], [values[i]], 'o', color=color, markersize=2, label=legend_label)
                    added_label = True
                else:
                    ax.plot([freq], [values[i]], 'o', color=color, markersize=2, label="_nolegend_")

    def _create_frequency_segments_simple(self, frequencies, values):
        """Conecta pontos dentro da mesma faixa e quebra na transição entre faixas.
        Também quebra quando o gap entre pontos excede o step atual.
        """
        if not frequencies or not values:
            return []

        # CORREÇÃO: Para dados históricos, usa gap_threshold fixo e tolerante
        # Não depende do passo de frequência atual, mas sim de um threshold razoável
        # que funciona para a maioria dos casos de dados históricos
        gap_threshold = 50.0  # 50 MHz é um gap muito tolerante para dados históricos

        # CORREÇÃO: Não usa mais faixas de licença para segmentação visual
        # Segmentação é baseada apenas em gaps entre frequências
        def in_same_range(a, b):
            # Sempre retorna True - não quebra por faixas de licença
            return True

        segments = []
        seg_f = []
        seg_v = []
        for i, f in enumerate(frequencies):
            v = values[i]
            if not seg_f:
                seg_f = [f]
                seg_v = [v]
                continue
            prev = seg_f[-1]
            gap = f - prev
            if gap > gap_threshold:
                # Gap muito grande - cria novo segmento
                segments.append((seg_f, seg_v))
                seg_f = [f]
                seg_v = [v]
            else:
                # Frequência próxima - continua segmento atual
                seg_f.append(f)
                seg_v.append(v)

        if seg_f:
            segments.append((seg_f, seg_v))
        return segments

    def _create_frequency_segments(self, frequencies, values):
        """Cria segmentos contínuos de frequências baseados em gaps, sem filtro de licença"""
        if not frequencies or not values:
            return []
        
        # CORREÇÃO: Para dados históricos, usa gap_threshold fixo e tolerante
        # Não depende do passo de frequência atual, mas sim de um threshold razoável
        # que funciona para a maioria dos casos de dados históricos
        gap_threshold = 50.0  # 50 MHz é um gap muito tolerante para dados históricos

        segments = []
        seg_f = []
        seg_v = []
        
        fc_log("threshold", f'[INFO] Threshold SEGMENTAÇÃO: Processando {len(frequencies)} frequências (sem filtro de licença)')     
        for i, f in enumerate(frequencies):
            v = values[i]
            if not seg_f:
                seg_f = [f]
                seg_v = [v]
                continue
            prev = seg_f[-1]
            gap = f - prev
            if gap > gap_threshold:
                # Gap muito grande - cria novo segmento
                segments.append((seg_f, seg_v))
                seg_f = [f]
                seg_v = [v]
                fc_log("threshold", f'   🔚 Gap {gap:.1f} MHz > {gap_threshold:.1f} - Novo segmento')
            else:
                # Frequência próxima - continua segmento atual
                seg_f.append(f)
                seg_v.append(v)

        if seg_f:
            segments.append((seg_f, seg_v))
        
        fc_log("threshold", f'[INFO] Threshold SEGMENTAÇÃO: Criados {len(segments)} segmentos por gaps')
        return segments

    def _validate_license_limits(self, freq, power):
        """Valida se frequência e potência estão dentro dos limites da licença"""
        if not self.license_limits.get('is_licensed', False):
            return False, "Módulo não licenciado"
        
        min_freq = self.license_limits.get('min_freq', 800)
        max_freq = self.license_limits.get('max_freq', 1000)
        min_power = self.license_limits.get('min_power', 5)
        max_power = self.license_limits.get('max_power', 25)
        
        # CORREÇÃO: Validação contra múltiplas faixas de frequência
        freq_valid = False
        freq_ranges = self.license_limits.get('freq_ranges', [])
        excluded_ranges = self.license_limits.get('excluded_ranges', [])
        
        if freq_ranges:
            # Valida contra faixas específicas
            for range_min, range_max in freq_ranges:
                if range_min <= freq <= range_max:
                    # Verifica se não está em uma faixa excluída
                    in_excluded = False
                    for excl_min, excl_max in excluded_ranges:
                        if excl_min <= freq <= excl_max:
                            in_excluded = True
                            fc_log("threshold", f'[AVISO] Threshold: Frequência {freq} MHz está na faixa excluída {excl_min}-{excl_max} MHz')
                            break
                    
                    if not in_excluded:
                        freq_valid = True
                        fc_log("threshold", f'[OK] Threshold: Frequência {freq} MHz ACEITA na faixa {range_min}-{range_max} MHz')
                        break
                    else:
                        fc_log("threshold", f'[ERRO] Threshold: Frequência {freq} MHz REJEITADA - faixa excluída')
        else:
            # Fallback para validação simples
            freq_valid = (min_freq <= freq <= max_freq)
        
        if not freq_valid:
            ranges_str = " ou ".join([f"{r[0]}-{r[1]} MHz" for r in freq_ranges]) if freq_ranges else f"{min_freq}-{max_freq} MHz"
            excluded_str = " e ".join([f"{r[0]}-{r[1]} MHz" for r in excluded_ranges]) if excluded_ranges else "nenhuma"
            return False, f"Frequência {freq}MHz fora das faixas permitidas ({ranges_str}) ou em faixa excluída ({excluded_str})"
        
        if not (min_power <= power <= max_power):
            return False, f"Potência {power}dBm fora dos limites ({min_power}-{max_power}dBm)"
        
        return True, "OK"
    
    def _update_graphs_license_limits(self):
        """Mantém os limites dos eixos X fixos em 800-1000 MHz independente da licença"""
        # NOTA: Gráficos sempre mostram 800-1000 MHz
        # A licença apenas limita os TESTES, não a visualização
        fc_log("threshold", f'[INFO] Limites dos gráficos mantidos em 800-1000 MHz (independente da licença)')
        pass

    def _pre_test_safety_verification(self, selected_tag_info, min_freq, max_freq, freq_step, max_power, power_step, distance, attenuator):
        """Verificação pré-teste — igual ao FastSurance: adquirir porta, abrir, ler refletido e temp com porta já aberta, fechar e liberar."""
        try:
            fc_log("threshold", '[SAFETY] VERIFICAÇÃO PRÉ-TESTE: Iniciando (fluxo FastSurance)...')
            fc_log("threshold", f'[SAFETY] Parâmetros - freq: {min_freq}-{max_freq}, power: {max_power}')
            if not HARDWARE_AVAILABLE or rfid_sdk is None:
                self._verification_result_queue.put(('failed', "Hardware RFID não disponível."))
                return
            if not (hasattr(self, 'port_manager') and self.port_manager and self.port_manager.acquire_port("Threshold", timeout=5.0)):
                self._verification_result_queue.put(('failed', f"Timeout ao adquirir a porta COM{self.com_port}."))
                return
            try:
                try:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                    time.sleep(0.2)
                except Exception:
                    pass
                open_result = rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE)
                if open_result != 0:
                    try:
                        rfid_sdk.UHF_RFID_Close(self.com_port)
                        time.sleep(0.35)
                    except Exception:
                        pass
                    open_result = rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE)
                if open_result != 0:
                    self._verification_result_queue.put(('failed', f"Falha ao abrir a porta COM{self.com_port}."))
                    return
                time.sleep(0.5)
                power_val = int(HEATING_POWER_DBM * 100)
                power_data = bytes([0x00, 0x00]) + power_val.to_bytes(2, 'big') * 2
                out_buf = ctypes.create_string_buffer(64)
                out_len = ctypes.c_uint(0)
                rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, out_buf, ctypes.byref(out_len))
                time.sleep(0.15)
                current_reflected = self._read_reflected_power_direct(tx_power_dbm=HEATING_POWER_DBM)
                if current_reflected is None:
                    time.sleep(0.3)
                    current_reflected = self._read_reflected_power_direct(tx_power_dbm=HEATING_POWER_DBM)
                if current_reflected is None:
                    self._verification_result_queue.put(('failed', "Falha na comunicação: Potência Refletida (verifique ligação COM e leitor)."))
                    return
                current_temp = self._get_temperature()
                if current_temp is None:
                    time.sleep(0.15)
                    current_temp = self._get_temperature()
                if current_temp is None:
                    self._verification_result_queue.put(('failed', "Falha na comunicação: Temperatura"))
                    return
                if current_temp >= TEMPERATURE_LIMIT:
                    self._verification_result_queue.put(('failed', f"Temperatura crítica: {current_temp:.1f}°C. Teste bloqueado por segurança."))
                    return
                if current_reflected > REFLECTED_POWER_LIMIT:
                    self._verification_result_queue.put(('failed', f"Potência refletida alta: {current_reflected:.1f} dBm. Verifique o sistema irradiante."))
                    return
                if current_temp < 25.0:
                    success = self._simple_heating()
                    if not success:
                        self._verification_result_queue.put(('failed', "Falha durante aquecimento"))
                        return
                    current_temp = self._get_temperature()
                    if current_temp is not None and current_temp >= TEMPERATURE_LIMIT:
                        self._verification_result_queue.put(('failed', f"Temperatura crítica: {current_temp:.1f}°C. Teste bloqueado por segurança."))
                        return
                fc_log("threshold", '[OK] VERIFICAÇÃO PRÉ-TESTE: Todas as verificações passaram!')
                self._verification_result_queue.put(('success', (
                    selected_tag_info, min_freq, max_freq, freq_step,
                    max_power, power_step, distance, attenuator
                )))
            finally:
                try:
                    rfid_sdk.UHF_RFID_Close(self.com_port)
                except Exception:
                    pass
                if hasattr(self, 'port_manager') and self.port_manager:
                    self.port_manager.release_port("Threshold")
        except Exception as e:
            error_msg = f"Erro inesperado na verificação pré-teste: {e}"
            fc_log("threshold", f'[ERRO] {error_msg}')
            traceback.print_exc()
            try:
                self._verification_result_queue.put(('failed', error_msg))
            except Exception:
                pass
            if hasattr(self, 'port_manager') and self.port_manager:
                try:
                    self.port_manager.release_port("Threshold")
                except Exception:
                    pass
            try:
                rfid_sdk.UHF_RFID_Close(self.com_port)
            except Exception:
                pass

    def _verification_failed(self, error_msg):
        """Trata falha na verificação pré-teste"""
        try:
            fc_log("threshold", f'[ERRO] VERIFICAÇÃO PRÉ-TESTE FALHOU: {error_msg}')
            messagebox.showerror(t('threshold.safety_check_failed'),
                                 t('threshold.safety_check_failed_msg').format(error=error_msg))
            self._set_test_button_idle()
            if hasattr(self, 'safety_status_label') and self.safety_status_label:
                self.safety_status_label.config(text="Falha na Verificação", fg="red")
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao tratar falha na verificação: {e}')

    def _update_verification_status(self, status_text):
        """Atualiza status da verificação na interface"""
        try:
            if hasattr(self, 'safety_status_label') and self.safety_status_label:
                self.safety_status_label.config(text=status_text, fg="orange")
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro ao atualizar status de verificação: {e}')

    def _start_test_after_verification(self, selected_tag_info, min_freq, max_freq, freq_step, max_power, power_step, distance, attenuator):
        """Inicia o teste após verificação de segurança bem-sucedida"""
        try:
            fc_log("threshold", '[START] INICIANDO TESTE APÓS VERIFICAÇÃO DE SEGURANÇA...')         
            # CORREÇÃO: NÃO limpa current_test_id aqui - ele já foi definido em start_test_action()
            # O current_test_id deve ser mantido durante todo o teste para consistência de cores
            fc_log("threshold", f"[INFO] DEBUG COR: Mantendo current_test_id = {getattr(self, 'current_test_id', 'NÃO EXISTE')} durante o teste")            
            # Reseta o estado do teste para garantir início limpo
            self._reset_test_state()
            
            # MOSTRA LEGENDA NO INÍCIO (antes do teste)
            self.current_test_description = self.description_input_var.get() or f'Teste {self.test_counter + 1}'
            
            # CORREÇÃO: current_test_id já foi definido em start_test_action() - não redefinir aqui
            # Isso evita mudança de cor durante o teste
            if not hasattr(self, 'current_test_id') or not self.current_test_id:
                # Fallback: define apenas se não existir
                self.current_test_id = self.test_counter + 1
                fc_log("threshold", f'[INFO] DEBUG COR: Fallback - Definindo current_test_id = {self.current_test_id}')
            else:
                fc_log("threshold", f'[INFO] DEBUG COR: Mantendo current_test_id existente = {self.current_test_id}')
            
            # CORREÇÃO CRÍTICA: Salva o ID que será usado para garantir consistência
            self.expected_test_id = self.current_test_id
            self.is_test_running = True  # MARCA QUE TESTE ESTÁ RODANDO
            # Poll no thread principal drena a fila de redesenho (add_realtime_point corre na thread do teste)
            try:
                self.after(0, self._realtime_queue_poll_tick)
            except tk.TclError:
                pass
            
            # --- INICIALIZA MEDIÇÃO DE TEMPO ---
            self.test_start_time = time.time()
            fc_log("threshold", f'[TIME] Inicializando medição de tempo para teste {self.current_test_id}')           
            self._show_realtime_legend()
            
            # SISTEMA DE SEGURANÇA DESLIGADO DURANTE TESTE
            fc_log("threshold", '🔓 SISTEMA DE SEGURANÇA: DESLIGADO durante teste para máxima performance')
            if hasattr(self, 'safety_status_label') and self.safety_status_label:
                self.safety_status_label.config(text=t('threshold.safety_off_during_test'), fg="gray")
            
            # Botão único: mostra Cancelar Teste para permitir cancelar
            self._set_test_button_running()
            
            # EXECUTA TESTE EM THREAD SEPARADA PARA PERMITIR CANCELAMENTO REAL
            self.active_test_thread = threading.Thread(
                target=self.execute_threshold_test,
                args=(selected_tag_info, min_freq, max_freq, freq_step, 
                      max_power, power_step, distance, attenuator),
                daemon=True
                )
            self.active_test_thread.start()
            
            fc_log("threshold", '[OK] Teste iniciado com sucesso após verificação de segurança!')         
        except Exception as e:
            error_msg = f"Erro ao iniciar teste após verificação: {e}"
            fc_log("threshold", f'[ERRO] {error_msg}')
            traceback.print_exc()
            
            # Restaura botão de start em caso de erro
            self._set_test_button_idle()
            
            messagebox.showerror(t('threshold.error_start_test'), error_msg)
    
    def _update_safety_status_display(self):
        """Atualiza o status de segurança baseado na licença e hardware"""
        try:
            if (hasattr(self, 'safety_status_label') and 
                self.safety_status_label and 
                hasattr(self.safety_status_label, 'winfo_exists') and 
                self.safety_status_label.winfo_exists()):
                
                is_licensed = self.license_limits.get('is_licensed', False)
                
                if is_licensed and HARDWARE_AVAILABLE:
                    self.safety_status_label.config(text=t('threshold.safety_ready'), fg="green")
                elif is_licensed and not HARDWARE_AVAILABLE:
                    self.safety_status_label.config(text=t('threshold.safety_hardware_not_connected'), fg="orange")
                else:
                    self.safety_status_label.config(text=t('threshold.safety_browser_mode'), fg="blue")
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao atualizar status de segurança: {e}')    
    def _update_license_info_display(self):
        """Atualiza as informações da licença na interface"""
        try:
            if (hasattr(self, 'license_info_label') and 
                self.license_info_label and 
                hasattr(self.license_info_label, 'winfo_exists') and 
                self.license_info_label.winfo_exists()):
                
                if hasattr(self, 'license_limits') and self.license_limits.get('is_licensed', False):
                    min_freq = self.license_limits.get('min_freq', 800)
                    max_freq = self.license_limits.get('max_freq', 1000)
                    min_power = self.license_limits.get('min_power', 5)
                    max_power = self.license_limits.get('max_power', 25)
                    license_name = self.license_limits.get('license_name', 'Desconhecida')
                    
                    info_text = f"Licença: {license_name} | Freq: {min_freq}-{max_freq}MHz | Power: {min_power}-{max_power}dBm"
                    self.license_info_label.config(text=info_text, fg="green")
                else:
                    self.license_info_label.config(text="Modo browser - funcionalidade limitada", fg="blue")
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao atualizar informações da licença: {e}')  
    def _heating_worker(self):
        """Método de aquecimento para configuração de hardware"""
        try:
            fc_log("threshold", f'🔥 HEATING WORKER: Iniciando configuração de hardware...')        
            if not HARDWARE_AVAILABLE or not rfid_sdk:
                fc_log("threshold", '[AVISO] Hardware não disponível para aquecimento')
                return
            
            output_buffer = ctypes.create_string_buffer(1)
            output_len = ctypes.c_uint(0)
            
            # Configura potência
            power_val = int(HEATING_POWER_DBM * 100)
            power_data = bytes([0,0]) + power_val.to_bytes(2, 'big')*2
            fc_log("threshold", f'🔥 HEATING WORKER: Configurando potência {HEATING_POWER_DBM}dBm: {power_data.hex()}')
            power_result = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, output_buffer, ctypes.byref(output_len))
            fc_log("threshold", f'🔥 HEATING WORKER: Resultado configuração potência: {power_result}')       
            # Configura frequência
            freq_data = b'\x01' + int(915 * 1000).to_bytes(3, 'big')
            fc_log("threshold", f'🔥 HEATING WORKER: Configurando frequência 915MHz: {freq_data.hex()}')
            freq_result = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_FREQ_TABLE, ctypes.c_char_p(freq_data), 4, output_buffer, ctypes.byref(output_len))
            fc_log("threshold", f'🔥 HEATING WORKER: Resultado configuração frequência: {freq_result}')       
            # Ativa CW
            fc_log("threshold", f'🔥 HEATING WORKER: Ativando CW...')
            cw_on_result = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_CW_STATUS, ctypes.c_char_p(b'\x01'), 1, output_buffer, ctypes.byref(output_len))
            fc_log("threshold", f'🔥 HEATING WORKER: Resultado ativação CW: {cw_on_result}')        
            fc_log("threshold", f'🔥 HEATING WORKER: Aguardando 3 segundos...')
            time.sleep(3)
            
            # Desativa CW
            fc_log("threshold", f'🔥 HEATING WORKER: Desativando CW...')
            cw_off_result = rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_CW_STATUS, ctypes.c_char_p(b'\x00'), 1, output_buffer, ctypes.byref(output_len))
            fc_log("threshold", f'🔥 HEATING WORKER: Resultado desativação CW: {cw_off_result}')        
            fc_log("threshold", f'🔥 HEATING WORKER: Configuração concluída!')   
        except Exception as e:
            fc_log("threshold", f'[ERRO] Exceção em _heating_worker: {e}')
            traceback.print_exc()
    
    def _get_vswr_worker(self, tx_power_dbm: float) -> Optional[float]:
        """Lê a Potência Refletida (dBm) e converte para VSWR via RFID_CMD_GET_PORT_LOSS"""
        try:
            # CORREÇÃO: Usa port_manager centralizado para evitar conflitos
            if hasattr(self, 'port_manager') and self.port_manager:
                def _get_vswr_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))
                    fc_log("threshold", f'[DEBUG] VSWR: Status={status}, output_len={output_len.value}')
                    
                    if status == 0 and output_len.value >= 3:
                        adc_value = struct.unpack('>H', output_buffer.raw[1:3])[0]
                        fc_log("threshold", f'[DEBUG] VSWR: ADC value={adc_value}')
                        
                        reflected_power_dbm = -25 + next((i for i, val in enumerate(RxAdcTable) if adc_value <= val), len(RxAdcTable) - 1)
                        
                        # Converte potência refletida para VSWR
                        vswr = self._calculate_vswr_from_reflected_power(tx_power_dbm, reflected_power_dbm)
                        
                        fc_log("threshold", f'[INFO] THRESHOLD: Potência Refletida={reflected_power_dbm:.1f}dBm, VSWR={vswr:.2f}')
                        return vswr
                    else:
                        fc_log("threshold", f'[AVISO] VSWR: Status inválido ou buffer insuficiente')
                        return None
                
                # Executa usando port_manager centralizado
                result = self.port_manager.execute_with_port(_get_vswr_operation, "Threshold")
                if result is not None:
                    return result
            else:
                # Fallback: abre porta própria se port_manager não estiver disponível
                if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) == 0:
                    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))
                        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)
                            
                            # Converte potência refletida para VSWR
                            vswr = self._calculate_vswr_from_reflected_power(tx_power_dbm, reflected_power_dbm)
                            return vswr
                    finally:
                        rfid_sdk.UHF_RFID_Close(self.com_port)
                    
        except Exception as e:
            fc_log("threshold", f'[ERRO] Exceção em _get_vswr_worker: {e}')
            traceback.print_exc()
        return None
    
    def _calculate_vswr_from_reflected_power(self, tx_power_dbm, reflected_power_dbm):
        """Converte potência refletida (dBm) para VSWR"""
        try:
            # Converte dBm para watts
            tx_power_w = 10**(tx_power_dbm / 10) / 1000  # dBm para watts
            reflected_power_w = 10**(reflected_power_dbm / 10) / 1000  # dBm para watts
            
            # Calcula coeficiente de reflexão
            if tx_power_w > 0:
                reflection_coefficient = (reflected_power_w / tx_power_w) ** 0.5
            else:
                reflection_coefficient = 0
            
            # Limita o coeficiente de reflexão entre 0 e 1
            reflection_coefficient = max(0, min(1, reflection_coefficient))
            
            # Calcula VSWR
            if reflection_coefficient < 1:
                vswr = (1 + reflection_coefficient) / (1 - reflection_coefficient)
            else:
                vswr = 1.0  # VSWR mínimo
            
            # Limita VSWR entre 1.0 e 10.0 para valores realistas
            vswr = max(1.0, min(10.0, vswr))
            
            fc_log("threshold", f'[DEBUG] VSWR: TX={tx_power_dbm:.1f}dBm, Reflected={reflected_power_dbm:.1f}dBm, VSWR={vswr:.2f}')
            return vswr
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro no cálculo de VSWR: {e}')
            return 1.0  # VSWR mínimo em caso de erro
    
    def _get_temperature(self):
        """Lê a temperatura do hardware (porta já deve estar aberta — como no Fast Threshold)."""
        try:
            if HARDWARE_AVAILABLE and rfid_sdk:
                output_buffer = ctypes.create_string_buffer(64)
                output_len = ctypes.c_uint(0)
                if rfid_sdk.UHF_RFID_Set(RFID_CMD_GET_TEMPERATURE, None, 0, output_buffer, ctypes.byref(output_len)) == 0:
                    if output_len.value >= 3:
                        temp_val = struct.unpack('>h', output_buffer.raw[1:3])[0]
                        return temp_val / 100.0
                    else:
                        fc_log("threshold", f'[AVISO] Temperatura: Dados insuficientes - len: {output_len.value}')
                else:
                    fc_log("threshold", f'[AVISO] Temperatura: Erro na leitura')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao ler temperatura: {e}')
        return None

    def _read_reflected_power_direct(self, tx_power_dbm=25.0):
        """Lê potência refletida (dBm) com a porta já aberta — sem port_manager (igual ao FastSurance).
        Retorna valor em dBm ou None se falhar."""
        try:
            if not HARDWARE_AVAILABLE or not rfid_sdk:
                return None
            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))
            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)
                return float(reflected_power_dbm)
            if status != 0 or output_len.value < 3:
                fc_log("threshold", f'[SAFETY] GET_PORT_LOSS: status={status}, output_len={output_len.value}')
            return None
        except Exception as e:
            fc_log("threshold", f'[ERRO] _read_reflected_power_direct: {e}')
            return None
    
    def _quick_vswr_check(self):
        """Verificação rápida de VSWR sem aquecimento custoso"""
        try:
            fc_log("threshold", '[INFO] VSWR CHECK: Verificação rápida de VSWR...')          
            # CORREÇÃO: Usa o mesmo método _get_vswr_worker que já foi corrigido
            result = self._get_vswr_worker(tx_power_dbm=HEATING_POWER_DBM)
            fc_log("threshold", f'[INFO] VSWR CHECK: Resultado da medição: {result}')           
            # Se o resultado for None ou 0, tenta uma medição alternativa
            if result is None or result == 0:
                fc_log("threshold", '[AVISO] VSWR CHECK: Medição retornou None ou 0, tentando medição alternativa...')
                # Tenta medir com potência diferente
                result = self._get_vswr_worker(tx_power_dbm=20.0)
                fc_log("threshold", f'[INFO] VSWR CHECK: Resultado alternativo: {result}')
            
            # Se ainda não funcionar, retorna um valor padrão para não bloquear o teste
            if result is None or result == 0:
                fc_log("threshold", '[AVISO] VSWR CHECK: Usando valor padrão para não bloquear o teste')
                result = 1.0  # VSWR mínimo (sistema bem casado)
            
            return result
                
        except Exception as e:
            fc_log("threshold", f'[ERRO] Exceção em _quick_vswr_check: {e}')
            # Retorna valor padrão em caso de erro
            return 1.0  # VSWR mínimo
    
    def _simple_heating(self):
        """Aquecimento simples e otimizado"""
        try:
            fc_log("threshold", '🔥 SIMPLE HEATING: Aquecimento otimizado...')          
            if not HARDWARE_AVAILABLE or not rfid_sdk:
                fc_log("threshold", '[AVISO] Hardware não disponível para aquecimento')
                return False
            
            # Configuração simples para aquecimento
            dummy_buffer, dummy_len = ctypes.create_string_buffer(256), ctypes.c_uint(0)
            
            # Potência alta para aquecimento
            power_val = int(HEATING_POWER_DBM * 100)
            power_data = bytes([0x00, 0x00]) + power_val.to_bytes(2, 'big') * 2
            rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, dummy_buffer, ctypes.byref(dummy_len))
            
            # Frequência para aquecimento
            freq_data = b'\x01' + int(915 * 1000).to_bytes(3, 'big')
            rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_FREQ_TABLE, ctypes.c_char_p(freq_data), 4, dummy_buffer, ctypes.byref(dummy_len))
            
            # Ativa CW para aquecimento por tempo fixo
            rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_CW_STATUS, ctypes.c_char_p(b'\x01'), 1, dummy_buffer, ctypes.byref(dummy_len))
            fc_log("threshold", '🔥 SIMPLE HEATING: CW ativo, aguardando 2 segundos...')
            time.sleep(2)  # Aquecimento rápido: 2s em vez de 3s
            
            # Desativa CW
            rfid_sdk.UHF_RFID_Set(RFID_CMD_SET_CW_STATUS, ctypes.c_char_p(b'\x00'), 1, dummy_buffer, ctypes.byref(dummy_len))
            fc_log("threshold", '🔥 SIMPLE HEATING: Aquecimento concluído!')
            return True
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Exceção em _simple_heating: {e}')
            return False
    
    def _update_temperature_display(self, temp):
        """Atualiza o display de temperatura"""
        try:
            if (hasattr(self, 'temperature_label') and 
                self.temperature_label and 
                hasattr(self.temperature_label, 'winfo_exists') and 
                self.temperature_label.winfo_exists()):
                
                self.temperature_label.config(text=f"{temp:.1f}°C")
                
                # Atualiza cor baseada na temperatura
                if temp >= 50:
                    self.temperature_label.config(fg="red")
                elif temp >= 45:
                    self.temperature_label.config(fg="orange")
                else:
                    self.temperature_label.config(fg="green")
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao atualizar temperatura: {e}')
    
    def _update_safety_displays_from_test(self, power_dbm=25.0):
        """Lê temperatura e VSWR (porta já aberta) e agenda atualização da UI na main thread. Chamado durante o teste."""
        try:
            temp = self._get_temperature()
            reflected_dbm = self._read_reflected_power_direct(tx_power_dbm=power_dbm)
            if temp is not None:
                self.after(0, lambda t=temp: self._update_temperature_display(t))
            if reflected_dbm is not None:
                vswr = self._calculate_vswr_from_reflected_power(power_dbm, reflected_dbm)
                self.after(0, lambda v=vswr: self._update_vswr_display(v))
            else:
                self.after(0, lambda: self._update_vswr_display("--"))
        except Exception as e:
            fc_log("threshold", f'[AVISO] _update_safety_displays_from_test: {e}')
    
    def _update_vswr_display(self, vswr):
        """Atualiza o display de VSWR"""
        try:
            # Formata o valor baseado no tipo
            if isinstance(vswr, str):
                display_text = vswr
                fc_log("threshold", f"[INFO] UPDATE VSWR: Tentando atualizar display para '{vswr}'")
            else:
                display_text = f"{vswr:.2f}"
                fc_log("threshold", f'[INFO] UPDATE VSWR: Tentando atualizar display para {vswr:.2f}')
            
            if (hasattr(self, 'vswr_label') and 
                self.vswr_label and 
                hasattr(self.vswr_label, 'winfo_exists') and 
                self.vswr_label.winfo_exists()):
                
                fc_log("threshold", f'[INFO] UPDATE VSWR: Widget válido, atualizando texto...')
                self.vswr_label.config(text=display_text)
                fc_log("threshold", f"[INFO] UPDATE VSWR: Texto atualizado para '{display_text}'")
                
                # Atualiza status do VSWR (só compara quando for número; "--" ou string não compara)
                if (hasattr(self, 'vswr_status_label') and 
                    self.vswr_status_label and 
                    hasattr(self.vswr_status_label, 'winfo_exists') and 
                    self.vswr_status_label.winfo_exists()):
                    try:
                        vswr_num = float(vswr) if not isinstance(vswr, str) else None
                    except (TypeError, ValueError):
                        vswr_num = None
                    if vswr_num is not None and vswr_num > 10:
                        self.vswr_status_label.config(text="ALTO", fg="red")
                        fc_log("threshold", f'[INFO] UPDATE VSWR: Status atualizado para ALTO (vermelho)')
                    elif vswr_num is not None:
                        self.vswr_status_label.config(text="OK", fg="green")
                        fc_log("threshold", f'[INFO] UPDATE VSWR: Status atualizado para OK (verde)')
            else:
                fc_log("threshold", f'[AVISO] UPDATE VSWR: Widget vswr_label não está disponível')              
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao atualizar VSWR: {e}')
            traceback.print_exc()
    
    def _check_critical_conditions(self, temp, vswr):
        """Verifica condições críticas e para testes se necessário"""
        try:
            critical = False
            reason = ""
            
            # Verifica temperatura crítica
            if temp is not None and temp >= 50:
                critical = True
                reason = f"Temperatura crítica: {temp:.1f}°C"
            
            # Verifica VSWR crítico se disponível
            if vswr is not None and vswr > 10:
                critical = True
                if reason:
                    reason += f" | VSWR crítico: {vswr:.2f}"
                else:
                    reason = f"VSWR crítico: {vswr:.2f}"
            
            if critical and hasattr(self, 'is_test_running') and self.is_test_running:
                fc_log("threshold", f'🚨 Condição crítica detectada: {reason}')
                self._stop_test_for_safety(reason)
                
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao verificar condições críticas: {e}')  
    def _stop_test_for_safety(self, reason):
        """Para o teste atual por questões de segurança"""
        try:
            if hasattr(self, 'is_test_running'):
                self.is_test_running = False
            
            messagebox.showerror(t('threshold.safety_stop'), t('threshold.safety_stop_msg').format(reason=reason))
            
            # Atualiza status de segurança
            if (hasattr(self, 'safety_status_label') and 
                self.safety_status_label and 
                hasattr(self.safety_status_label, 'winfo_exists') and 
                self.safety_status_label.winfo_exists()):
                
                self.safety_status_label.config(text="Crítico!", fg="red")
            
            # Notifica fim do teste
            if hasattr(self, '_notify_test_end'):
                self._notify_test_end()
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao parar teste por segurança: {e}')    
    def _notify_test_start(self):
        """Notifica o app_shell que um teste foi iniciado"""
        if self.app_shell:
            self.app_shell.set_test_running(True, self.module_name)
    
    def _notify_test_end(self):
        """Notifica o app_shell que um teste foi finalizado"""
        if self.app_shell:
            self.app_shell.set_test_running(False, self.module_name)
    
    def _get_unit_for_graph_type(self, graph_type):
        """Retorna a unidade correta baseada no tipo de gráfico"""
        # Normaliza o tipo de gráfico para a chave em inglês, para funcionar com idiomas traduzidos
        graph_type_key = self._normalize_graph_type(graph_type)
        if graph_type_key in ["Max FCC Link Forward (m)", "Max FCC Link Reversed (m)"]:
            return "m"
        elif graph_type_key == "RCS (dBm²)":
            return "dBm²"
        else:
            return "dBm"
    
    def _calculate_rcs(self, freq_mhz, distance, irradiated_power, backscatter, antenna_gain_db=6.0):
        """
        Calcula o Radar Cross Section (RCS) usando a equação do radar
        
        Args:
            freq_mhz: Frequência em MHz
            distance: Distância em metros
            irradiated_power: Potência irradiada em dBm
            backscatter: Potência de backscatter em dBm
            antenna_gain_db: Ganho das antenas em dB (padrão: 6.0 dB)
            
        Returns:
            RCS em dBm² (decibéis relativos a 1 m²)
        """
        try:
            # Converte dBm para Watts
            pt_watts = 10**((irradiated_power - 30) / 10)  # dBm para Watts
            pr_watts = 10**((backscatter - 30) / 10)       # dBm para Watts
            
            # Calcula comprimento de onda
            c = 3e8  # Velocidade da luz em m/s
            freq_hz = freq_mhz * 1e6
            wavelength = c / freq_hz
            
            # Ganho das antenas (assumindo ganho igual para transmissão e recepção)
            gt = 10**(antenna_gain_db / 10)
            gr = 10**(antenna_gain_db / 10)
            
            # Fórmula do RCS: RCS = (4π)³ × R⁴ × Pr / (Pt × Gt × Gr × λ²)
            rcs_m2 = ((4 * 3.14159)**3 * (distance**4) * pr_watts) / (pt_watts * gt * gr * (wavelength**2))
            
            # Converte para dBm²: RCS_dBm2 = 10 * log10(RCS_m2)
            if rcs_m2 > 0:
                rcs_dbm2 = 10 * np.log10(rcs_m2)
            else:
                rcs_dbm2 = -999.0  # Valor para RCS zero ou inválido
            
            return rcs_dbm2
            
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro no cálculo do RCS: {e}')
            return -999.0
    
    def _on_hover_graph1(self, event):
        """Callback para hover no gráfico 1"""
        try:
            if not hasattr(self, 'canvas1') or self.canvas1 is None:
                return
            tw = self.canvas1.get_tk_widget()
            if not tw.winfo_exists():
                return
        except tk.TclError:
            return
        now = time.monotonic()
        if now - getattr(self, '_last_hover_graph1', 0) < 0.04:
            return
        self._last_hover_graph1 = now
        if not hasattr(self, 'ax1') or event.inaxes != self.ax1:
            if hasattr(self, 'annot1') and self.annot1 and self.annot1.get_visible():
                self.annot1.set_visible(False)
                try:
                    self.canvas1.draw_idle()
                except Exception:
                    pass
            return
        
        # Obtém o tipo de gráfico selecionado e sua unidade
        graph_type = self.graph1_var.get()
        unit = self._get_unit_for_graph_type(graph_type)
        
        for line in self.ax1.lines:
            cont, ind = line.contains(event)
            if cont:
                pos = line.get_xydata()[ind["ind"][0]]
                x_coord, y_coord = pos[0], pos[1]
                tooltip_text = f"{x_coord:.2f} MHz → {y_coord:.2f} {unit}"
                self._position_tooltip_smartly(event, x_coord, y_coord, tooltip_text, 1)
                return
        
        if hasattr(self, 'annot1') and self.annot1 and self.annot1.get_visible():
            self.annot1.set_visible(False)
            try:
                self.canvas1.draw_idle()
            except Exception:
                pass
    
    def _on_hover_graph2(self, event):
        """Callback para hover no gráfico 2"""
        try:
            if not hasattr(self, 'canvas2') or self.canvas2 is None:
                return
            tw = self.canvas2.get_tk_widget()
            if not tw.winfo_exists():
                return
        except tk.TclError:
            return
        now = time.monotonic()
        if now - getattr(self, '_last_hover_graph2', 0) < 0.04:
            return
        self._last_hover_graph2 = now
        if not hasattr(self, 'ax2') or event.inaxes != self.ax2:
            if hasattr(self, 'annot2') and self.annot2 and self.annot2.get_visible():
                self.annot2.set_visible(False)
                try:
                    self.canvas2.draw_idle()
                except Exception:
                    pass
            return
        
        # Obtém o tipo de gráfico selecionado e sua unidade
        graph_type = self.graph2_var.get()
        unit = self._get_unit_for_graph_type(graph_type)
        
        for line in self.ax2.lines:
            cont, ind = line.contains(event)
            if cont:
                pos = line.get_xydata()[ind["ind"][0]]
                x_coord, y_coord = pos[0], pos[1]
                tooltip_text = f"{x_coord:.2f} MHz → {y_coord:.2f} {unit}"
                self._position_tooltip_smartly(event, x_coord, y_coord, tooltip_text, 2)
                return
        
        if hasattr(self, 'annot2') and self.annot2 and self.annot2.get_visible():
            self.annot2.set_visible(False)
            try:
                self.canvas2.draw_idle()
            except Exception:
                pass

    def _position_tooltip_smartly(self, event, x_pos, y_pos, tooltip_text, graph_num):
        """SOLUÇÃO SIMPLES: Tooltip matplotlib que NUNCA é cortado"""
        try:
            # Seleciona o canvas e annot baseado no número do gráfico
            if graph_num == 1:
                canvas = self.canvas1
                annot = self.annot1
                ax = self.ax1
            elif graph_num == 2:
                canvas = self.canvas2
                annot = self.annot2
                ax = self.ax2
            else:
                return
            
            # Obtém limites do gráfico
            xlim = ax.get_xlim()
            ylim = ax.get_ylim()
            x_range = xlim[1] - xlim[0]
            y_range = ylim[1] - ylim[0]
            
            # SOLUÇÃO SIMPLES: Sempre posiciona o tooltip no centro inferior do gráfico
            # Posição segura que nunca será cortada
            tooltip_x = xlim[0] + x_range * 0.5  # Centro horizontal
            tooltip_y = ylim[0] + y_range * 0.15  # 15% acima do fundo
            
            # Calcula offset para posicionar o tooltip
            x_offset = tooltip_x - x_pos
            y_offset = tooltip_y - y_pos
            
            # Atualiza o tooltip
            annot.xy = (x_pos, y_pos)
            annot.xytext = (x_offset, y_offset)
            annot.set_text(tooltip_text)
            annot.set_ha('center')
            annot.set_va('top')
            annot.set_visible(True)
            
            # CONFIGURAÇÕES PARA NUNCA CORTAR
            annot.set_clip_on(False)  # Desabilita clipping
            annot.set_clip_box(None)  # Remove caixa de clipping
            annot.set_clip_path(None)  # Remove path de clipping
            
            # Configurações para garantir visibilidade total
            annot.set_bbox(dict(
                boxstyle="round,pad=0.8", 
                facecolor="lightblue", 
                alpha=0.95,
                edgecolor="darkblue",
                linewidth=2,
                zorder=1000  # Garante que fique sempre no topo
            ))
            
            # Seta apontando para o ponto de dados
            try:
                annot.arrowprops = dict(
                    arrowstyle="->", 
                    connectionstyle="arc3,rad=0.2",
                    color="darkblue",
                    lw=2,
                    zorder=1000
                )
            except:
                pass  # Ignora erro da seta
            
            # Força redesenho para garantir que o tooltip apareça
            canvas.draw_idle()
        except Exception as e:
            fc_log("threshold", f'[AVISO] Erro no tooltip SIMPLES: {e}')
            # Fallback: tooltip mínimo
            try:
                if graph_num == 1:
                    canvas = self.canvas1
                    annot = self.annot1
                    ax = self.ax1
                elif graph_num == 2:
                    canvas = self.canvas2
                    annot = self.annot2
                    ax = self.ax2
                else:
                    return
                
                # Posição FALLBACK: centro do gráfico
                xlim = ax.get_xlim()
                ylim = ax.get_ylim()
                x_range = xlim[1] - xlim[0]
                y_range = ylim[1] - ylim[0]
                
                center_x = xlim[0] + x_range * 0.5
                center_y = ylim[0] + y_range * 0.1  # 10% acima do fundo
                
                x_offset = center_x - x_pos
                y_offset = center_y - y_pos
                
                annot.xy = (x_pos, y_pos)
                annot.xytext = (x_offset, y_offset)
                annot.set_text(tooltip_text)
                annot.set_ha('center')
                annot.set_va('top')
                annot.set_visible(True)
                annot.set_clip_on(False)
                
                annot.set_bbox(dict(
                    boxstyle="round,pad=0.6", 
                    facecolor="lightblue", 
                    alpha=0.95,
                    edgecolor="darkblue",
                    linewidth=1.5,
                    zorder=1000
                ))
                canvas.draw_idle()
            except Exception as fallback_error:
                fc_log("threshold", f'[AVISO] Erro no fallback: {fallback_error}')
    
    def sort_treeview(self, column):
        """
        Ordena a tabela de histórico 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 == "Plot":
                # Ordenação por estado (☐ primeiro, depois ☑)
                def plot_key(x):
                    return 0 if x[0] == "☐" else 1
                items.sort(key=plot_key, reverse=self.current_sort_reverse)
            elif column == "EPC":
                # Ordenação alfabética para EPC
                items.sort(key=lambda x: x[0].lower(), reverse=self.current_sort_reverse)
            elif column == "Nome do Teste":
                # Ordenação alfabética para nome do teste
                items.sort(key=lambda x: x[0].lower(), reverse=self.current_sort_reverse)
            elif column == "Atenuador":
                # Ordenação numérica decimal
                items.sort(key=lambda x: float(x[0]) if x[0] != "" else 0, reverse=self.current_sort_reverse)
            elif column == "Distância":
                # Ordenação numérica decimal
                items.sort(key=lambda x: float(x[0]) if x[0] != "" else 0, reverse=self.current_sort_reverse)
            elif column == "Calibration":
                # Ordenação alfabética pelo ficheiro / nome da calibração
                items.sort(key=lambda x: (x[0] or "").lower(), reverse=self.current_sort_reverse)
            elif column == "Date Time":
                # Ordenação de data/hora
                items.sort(key=lambda x: x[0], reverse=self.current_sort_reverse)
            elif column == "Tempo":
                # Ordenação numérica para tempo (converte "X.Xs" ou "N/A" para float)
                def time_key(x):
                    try:
                        time_str = x[0].replace('s', '').strip()
                        if time_str == 'N/A':
                            return float('inf')
                        return float(time_str)
                    except:
                        return float('inf')
                items.sort(key=time_key, 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)
            
        except Exception as e:
            fc_log("threshold", f'[ERRO] 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
            for col in ["Plot", "Nome do Teste", "EPC", "Calibration", "Atenuador", "Distância", "Date Time", "Tempo"]:
                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:
            fc_log("threshold", f'[ERRO] Erro ao atualizar indicador de ordenação: {e}')

    def _force_complete_cancellation(self):
        """
        Executa cancelamento completo automático quando não é possível determinar o threshold
        - Cancela o teste
        - Fecha a janela de realtime
        - Habilita o botão de iniciar teste
        """
        try:
            fc_log("threshold", '🛑 EXECUTANDO CANCELAMENTO COMPLETO AUTOMÁTICO...')         
            # 1. MARCA CANCELAMENTO GLOBAL
            self.global_test_cancelled = True
            self.test_in_progress = False
            self.is_test_running = False
            
            # 2. CANCELA THREAD DE TESTE
            if hasattr(self, 'active_test_thread') and self.active_test_thread and self.active_test_thread.is_alive():
                fc_log("threshold", '🛑 Parando thread de teste ativa...')
                self.active_test_thread.join(timeout=0.5)
                self.active_test_thread = None
            
            # 3. CANCELA ATUALIZAÇÃO AUTOMÁTICA DA JANELA DE REALTIME
            if hasattr(self, 'realtime_refresh_after_id') and self.realtime_refresh_after_id is not None:
                try:
                    self.after_cancel(self.realtime_refresh_after_id)
                    fc_log("threshold", '🛑 Cancelado agendamento de atualização automática')
                except Exception:
                    pass
                self.realtime_refresh_after_id = None
            
            # 4. DESABILITA ATUALIZAÇÃO AUTOMÁTICA
            if hasattr(self, 'realtime_auto_refresh_var') and self.realtime_auto_refresh_var is not None:
                try:
                    self.realtime_auto_refresh_var.set(False)
                    fc_log("threshold", '🛑 Atualização automática desabilitada')
                except Exception:
                    pass
            
            # 5. FECHA JANELA DE REALTIME SE ESTIVER ABERTA
            fc_log("threshold", f"🛑 VERIFICANDO janela de realtime: hasattr={hasattr(self, 'realtime_window')}, valor={getattr(self, 'realtime_window', None)}")
            if hasattr(self, 'realtime_window') and self.realtime_window is not None:
                try:
                    fc_log("threshold", '🛑 Fechando janela de realtime...')
                    self.realtime_window.grab_release()
                    self.realtime_window.destroy()
                    self.realtime_window = None
                    fc_log("threshold", '[OK] Janela de realtime fechada')
                except Exception as e:
                    fc_log("threshold", f'[AVISO] Erro ao fechar janela de realtime: {e}')
                    self.realtime_window = None
            else:
                fc_log("threshold", 'ℹ️ Janela de realtime não está aberta ou não existe')      
            # 6. LIMPA DADOS DE TEMPO REAL
            if hasattr(self, 'realtime_points'):
                self.realtime_points = {'freqs': [], 'powers': [], 'rssis': [], 'timestamps': []}
                fc_log("threshold", '🧹 Dados de tempo real limpos')          
            # 7. LIMPA GRÁFICOS
            self._clear_realtime_graphs()
            
            # 8. ATUALIZA INTERFACE
            self.update_graphs_from_history()
            
            # 9. RESTAURA BOTÃO ÚNICO (Iniciar Teste)
            fc_log("threshold", f"🛑 VERIFICANDO botão de iniciar: hasattr={hasattr(self, 'start_button')}, valor={getattr(self, 'start_button', None)}")
            if hasattr(self, 'start_button'):
                try:
                    self._set_test_button_idle()
                    fc_log("threshold", '[OK] Botão de iniciar teste habilitado')
                except Exception as e:
                    fc_log("threshold", f'[ERRO] ERRO ao habilitar botão de iniciar: {e}')
            else:
                fc_log("threshold", '[AVISO] Botão de iniciar teste não encontrado')           
            # 11. LIMPA REFERÊNCIAS
            self.realtime_auto_refresh_var = None
            
            fc_log("threshold", '[OK] CANCELAMENTO COMPLETO AUTOMÁTICO EXECUTADO COM SUCESSO!')            
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro durante cancelamento automático: {e}')
            import traceback
            traceback.print_exc()
            
            # MEDIDA DE SEGURANÇA: Força cancelamento mesmo com erro
            fc_log("threshold", '🛑 FORÇANDO CANCELAMENTO DE SEGURANÇA...')
            try:
                self.global_test_cancelled = True
                self.test_in_progress = False
                self.is_test_running = False
                
                # Força fechamento da janela de realtime
                if hasattr(self, 'realtime_window') and self.realtime_window is not None:
                    try:
                        self.realtime_window.destroy()
                        self.realtime_window = None
                        fc_log("threshold", '[OK] Janela de realtime fechada (modo segurança)')
                    except Exception:
                        pass
                
                # Força habilitação do botão de iniciar
                if hasattr(self, 'start_button'):
                    try:
                        self._set_test_button_idle()
                        fc_log("threshold", '[OK] Botão de iniciar habilitado (modo segurança)')
                    except Exception:
                        pass
                        
            except Exception as e2:
                fc_log("threshold", f'[ERRO] ERRO CRÍTICO no modo segurança: {e2}')

    def _force_ui_update(self):
        """
        Força atualização da interface após cancelamento
        """
        try:
            fc_log("threshold", '[INFO] EXECUTANDO ATUALIZAÇÃO FORÇADA DA INTERFACE...')          
            # 1. ATUALIZA O FRAME PRINCIPAL
            self.update()
            
            # 2. VERIFICA SE A JANELA DE REALTIME AINDA EXISTE
            if hasattr(self, 'realtime_window') and self.realtime_window is not None:
                try:
                    if self.realtime_window.winfo_exists():
                        fc_log("threshold", '🛑 FORÇANDO FECHAMENTO da janela de realtime...')
                        self.realtime_window.destroy()
                        self.realtime_window = None
                        fc_log("threshold", '[OK] Janela de realtime fechada forçadamente')
                    else:
                        fc_log("threshold", 'ℹ️ Janela de realtime já foi fechada')
                        self.realtime_window = None
                except Exception as e:
                    fc_log("threshold", f'[ERRO] Erro ao fechar janela de realtime: {e}')
                    self.realtime_window = None
            
            # 3. VERIFICA SE O BOTÃO DE INICIAR ESTÁ HABILITADO
            if hasattr(self, 'start_button'):
                try:
                    current_state = self.start_button.cget('state')
                    if current_state != 'normal':
                        fc_log("threshold", '🛑 FORÇANDO habilitação do botão de iniciar...')
                        self._set_test_button_idle()
                        fc_log("threshold", '[OK] Botão de iniciar habilitado forçadamente')
                    else:
                        fc_log("threshold", '[OK] Botão de iniciar já está habilitado')
                except Exception as e:
                    fc_log("threshold", f'[ERRO] Erro ao verificar/habilitar botão de iniciar: {e}')            
            # 4. ATUALIZA NOVAMENTE
            self.update()
            fc_log("threshold", '[OK] ATUALIZAÇÃO FORÇADA DA INTERFACE CONCLUÍDA!')         
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro durante atualização forçada da interface: {e}')

    def _extreme_window_kill(self):
        """
        FECHAMENTO EXTREMO - MATA todas as janelas de forma DEFINITIVA
        """
        try:
            fc_log("threshold", '💀 EXECUTANDO FECHAMENTO EXTREMO...')          
            # 1. MATA JANELA DE REALTIME DE FORMA EXTREMA
            if hasattr(self, 'realtime_window') and self.realtime_window is not None:
                try:
                    fc_log("threshold", f'💀 MATANDO janela de realtime de forma EXTREMA: {self.realtime_window}')                  
                    # MÉTODO EXTREMO 1: Múltiplas tentativas simultâneas
                    try:
                        self.realtime_window.grab_release()
                        fc_log("threshold", '[OK] Grab release extremo executado')
                    except:
                        fc_log("threshold", '[AVISO] Grab release extremo falhou')
                    
                    try:
                        self.realtime_window.quit()
                        fc_log("threshold", '[OK] Quit extremo executado')
                    except:
                        fc_log("threshold", '[AVISO] Quit extremo falhou')
                    
                    try:
                        self.realtime_window.destroy()
                        fc_log("threshold", '[OK] Destroy extremo executado')
                    except:
                        fc_log("threshold", '[AVISO] Destroy extremo falhou')
                    
                    # MÉTODO EXTREMO 2: Se ainda existir, MATA de forma BRUTAL
                    if self.realtime_window.winfo_exists():
                        fc_log("threshold", '💀 MATANDO JANELA DE FORMA BRUTAL...')                      
                        try:
                            self.realtime_window.attributes('-topmost', False)
                            fc_log("threshold", '[OK] Topmost removido brutalmente')
                        except:
                            pass
                        
                        try:
                            self.realtime_window.withdraw()
                            fc_log("threshold", '[OK] Janela escondida brutalmente')
                        except:
                            pass
                        
                        try:
                            self.realtime_window.destroy()
                            fc_log("threshold", '[OK] Destroy brutal executado')
                        except:
                            pass
                        
                        # MÉTODO EXTREMO 3: Se AINDA existir, MATA de forma DESESPERADA
                        if self.realtime_window.winfo_exists():
                            fc_log("threshold", '💀 MATANDO JANELA DE FORMA DESESPERADA...')                          
                            try:
                                # Força fechamento do sistema
                                self.realtime_window.after(0, self.realtime_window.destroy)
                                fc_log("threshold", '[OK] Destroy desesperado agendado')
                            except:
                                pass
                            
                            try:
                                # Força atualização e fechamento
                                self.realtime_window.update()
                                self.realtime_window.destroy()
                                fc_log("threshold", '[OK] Destroy desesperado executado')
                            except:
                                pass
                        
                        # MÉTODO EXTREMO 4: Se AINDA existir, MATA de forma FINAL
                        if self.realtime_window.winfo_exists():
                            fc_log("threshold", '💀 MATANDO JANELA DE FORMA FINAL - ÚLTIMO RECURSO...')                         
                            try:
                                # Força fechamento em loop
                                for _ in range(10):
                                    try:
                                        self.realtime_window.destroy()
                                        break
                                    except:
                                        pass
                                fc_log("threshold", '[OK] Destroy final executado')
                            except:
                                pass
                    
                    # LIMPA REFERÊNCIA DEFINITIVAMENTE
                    self.realtime_window = None
                    fc_log("threshold", '[OK] Referência da janela limpa definitivamente')                    
                except Exception as e:
                    fc_log("threshold", f'[ERRO] Erro no fechamento extremo da janela de realtime: {e}')
                    self.realtime_window = None
            
            # 2. MATA TODAS AS OUTRAS JANELAS DE FORMA EXTREMA
            try:
                fc_log("threshold", '💀 MATANDO TODAS AS OUTRAS JANELAS DE FORMA EXTREMA...')
                windows_killed = 0
                
                # Lista todas as janelas
                all_windows = []
                for child in self.root.winfo_children():
                    if isinstance(child, tk.Toplevel):
                        all_windows.append(child)
                
                fc_log("threshold", f'💀 Encontradas {len(all_windows)} janelas para MATAR')              
                # MATA cada janela de forma EXTREMA
                for window in all_windows:
                    try:
                        fc_log("threshold", f'💀 MATANDO janela: {window}')                      
                        # MÉTODO EXTREMO para cada janela
                        try:
                            window.grab_release()
                        except:
                            pass
                        
                        try:
                            window.quit()
                        except:
                            pass
                        
                        try:
                            window.destroy()
                        except:
                            pass
                        
                        # Se ainda existir, MATA de forma BRUTAL
                        if window.winfo_exists():
                            try:
                                window.attributes('-topmost', False)
                                window.withdraw()
                                window.destroy()
                            except:
                                pass
                        
                        # Se AINDA existir, MATA de forma FINAL
                        if window.winfo_exists():
                            try:
                                for _ in range(5):
                                    try:
                                        window.destroy()
                                        break
                                    except:
                                        pass
                            except:
                                pass
                        
                        windows_killed += 1
                        fc_log("threshold", f'[OK] Janela MATADA: {window}')
                        
                    except Exception as e:
                        fc_log("threshold", f'[AVISO] Erro ao MATAR janela {window}: {e}')
                
                fc_log("threshold", f'[OK] Total de janelas MATADAS: {windows_killed}')
                
            except Exception as e:
                fc_log("threshold", f'[ERRO] Erro no fechamento extremo de outras janelas: {e}')
            
            # 3. FORÇA ATUALIZAÇÃO EXTREMA
            try:
                fc_log("threshold", '💀 FORÇANDO ATUALIZAÇÃO EXTREMA...')
                self.root.update()
                self.update()
                fc_log("threshold", '[OK] Atualização extrema executada')
            except Exception as e:
                fc_log("threshold", f'[AVISO] Erro na atualização extrema: {e}')           
            fc_log("threshold", '💀 FECHAMENTO EXTREMO CONCLUÍDO!')         
        except Exception as e:
            fc_log("threshold", f'[ERRO] ERRO CRÍTICO no fechamento extremo: {e}')
            import traceback
            traceback.print_exc()


    def _setup_zoom_controls_graph1(self, parent):
        """Configura os controles de zoom na parte superior esquerda do gráfico 1"""
        # Frame para os controles de zoom
        zoom_frame = tk.Frame(parent, bg='white')
        zoom_frame.place(x=5, y=5, anchor='nw')
        
        # Botão Zoom In
        zoom_in_btn = tk.Button(zoom_frame, text="🔍+", width=3, height=1, 
                               command=self._zoom_in_graph1, font=("Arial", 10))
        zoom_in_btn.grid(row=0, column=0, padx=2)
        
        # Botão Zoom Out  
        zoom_out_btn = tk.Button(zoom_frame, text="🔍-", width=3, height=1,
                                command=self._zoom_out_graph1, font=("Arial", 10))
        zoom_out_btn.grid(row=0, column=1, padx=2)
        
        # Botão Reset Zoom
        reset_btn = tk.Button(zoom_frame, text="↻", width=3, height=1,
                             command=self._reset_zoom_graph1, font=("Arial", 10))
        reset_btn.grid(row=0, column=2, padx=2)
        fc_log("threshold", '[INFO] DEBUG: Botão reset zoom graph1 configurado')        
        # Botão Fit to Data
        fit_btn = tk.Button(zoom_frame, text="📐", width=3, height=1,
                           command=self._fit_to_data_graph1, font=("Arial", 10))
        fit_btn.grid(row=0, column=3, padx=2)
        
        # Botão Pan (arrastar) - Gráfico 1
        self.pan_active_graph1 = False
        self.pan_btn_graph1 = tk.Button(zoom_frame, text="✋", width=3, height=1,
                                         command=self._toggle_pan_graph1, font=("Arial", 10),
                                         relief=tk.RAISED)
        self.pan_btn_graph1.grid(row=0, column=4, padx=2)
        
        # Label de zoom
        zoom_label = tk.Label(zoom_frame, text="Zoom: 1.0x", font=("Arial", 8), bg='white')
        zoom_label.grid(row=0, column=5, padx=2)
        self.zoom_label_graph1 = zoom_label

    def _setup_zoom_controls_graph2(self, parent):
        """Configura os controles de zoom na parte superior esquerda do gráfico 2"""
        # Frame para os controles de zoom
        zoom_frame = tk.Frame(parent, bg='white')
        zoom_frame.place(x=5, y=5, anchor='nw')
        
        # Botão Zoom In
        zoom_in_btn = tk.Button(zoom_frame, text="🔍+", width=3, height=1, 
                               command=self._zoom_in_graph2, font=("Arial", 10))
        zoom_in_btn.grid(row=0, column=0, padx=2)
        
        # Botão Zoom Out  
        zoom_out_btn = tk.Button(zoom_frame, text="🔍-", width=3, height=1,
                                command=self._zoom_out_graph2, font=("Arial", 10))
        zoom_out_btn.grid(row=0, column=1, padx=2)
        
        # Botão Reset Zoom
        reset_btn = tk.Button(zoom_frame, text="↻", width=3, height=1,
                             command=self._reset_zoom_graph2, font=("Arial", 10))
        reset_btn.grid(row=0, column=2, padx=2)
        fc_log("threshold", '[INFO] DEBUG: Botão reset zoom graph2 configurado')        
        # Botão Fit to Data
        fit_btn = tk.Button(zoom_frame, text="📐", width=3, height=1,
                           command=self._fit_to_data_graph2, font=("Arial", 10))
        fit_btn.grid(row=0, column=3, padx=2)
        
        # Botão Pan (arrastar) - Gráfico 2
        self.pan_active_graph2 = False
        self.pan_btn_graph2 = tk.Button(zoom_frame, text="✋", width=3, height=1,
                                         command=self._toggle_pan_graph2, font=("Arial", 10),
                                         relief=tk.RAISED)
        self.pan_btn_graph2.grid(row=0, column=4, padx=2)
        
        # Label de zoom
        zoom_label = tk.Label(zoom_frame, text="Zoom: 1.0x", font=("Arial", 8), bg='white')
        zoom_label.grid(row=0, column=5, padx=2)
        self.zoom_label_graph2 = zoom_label

    def _zoom_in_graph1(self):
        """Aplica zoom in no gráfico 1"""
        try:
            if not hasattr(self, 'ax1') or not self.ax1:
                return
                
            # Salva limites originais se ainda não foram salvos
            if self.original_xlim_graph1 is None:
                self.original_xlim_graph1 = self.ax1.get_xlim()
                self.original_ylim_graph1 = self.ax1.get_ylim()
            
            # Aplica zoom
            self.zoom_factor_graph1 *= 1.2
            xlim = self.ax1.get_xlim()
            ylim = self.ax1.get_ylim()
            
            x_center = (xlim[0] + xlim[1]) / 2
            y_center = (ylim[0] + ylim[1]) / 2
            
            x_range = (xlim[1] - xlim[0]) / self.zoom_factor_graph1
            y_range = (ylim[1] - ylim[0]) / self.zoom_factor_graph1
            
            self.ax1.set_xlim(x_center - x_range/2, x_center + x_range/2)
            self.ax1.set_ylim(y_center - y_range/2, y_center + y_range/2)
            
            self.canvas1.draw()
            # Atualiza limites salvos para preservar zoom entre atualizações
            try:
                self._last_xlim_graph1 = self.ax1.get_xlim()
                self._last_ylim_graph1 = self.ax1.get_ylim()
            except Exception:
                pass
            self.zoom_label_graph1.config(text=f"Zoom: {self.zoom_factor_graph1:.1f}x")
            fc_log("threshold", f'[OK] Zoom in aplicado no Threshold Graph 1: {self.zoom_factor_graph1:.1f}x')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao aplicar zoom in no gráfico 1: {e}')
    def _zoom_out_graph1(self):
        """Aplica zoom out no gráfico 1"""
        try:
            if not hasattr(self, 'ax1') or not self.ax1:
                return
                
            # Salva limites originais se ainda não foram salvos
            if self.original_xlim_graph1 is None:
                self.original_xlim_graph1 = self.ax1.get_xlim()
                self.original_ylim_graph1 = self.ax1.get_ylim()
            
            # Aplica zoom
            self.zoom_factor_graph1 /= 1.2
            xlim = self.ax1.get_xlim()
            ylim = self.ax1.get_ylim()
            
            x_center = (xlim[0] + xlim[1]) / 2
            y_center = (ylim[0] + ylim[1]) / 2
            
            x_range = (xlim[1] - xlim[0]) / self.zoom_factor_graph1
            y_range = (ylim[1] - ylim[0]) / self.zoom_factor_graph1
            
            self.ax1.set_xlim(x_center - x_range/2, x_center + x_range/2)
            self.ax1.set_ylim(y_center - y_range/2, y_center + y_range/2)
            
            self.canvas1.draw()
            # Atualiza limites salvos para preservar zoom entre atualizações
            try:
                self._last_xlim_graph1 = self.ax1.get_xlim()
                self._last_ylim_graph1 = self.ax1.get_ylim()
            except Exception:
                pass
            self.zoom_label_graph1.config(text=f"Zoom: {self.zoom_factor_graph1:.1f}x")
            fc_log("threshold", f'[OK] Zoom out aplicado no Threshold Graph 1: {self.zoom_factor_graph1:.1f}x')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao aplicar zoom out no gráfico 1: {e}')
    def _reset_zoom_graph1(self):
        """Reseta o zoom do gráfico 1"""
        fc_log("threshold", '[INFO] DEBUG: Função _reset_zoom_graph1 chamada')
        fc_log("threshold", '[INFO] DEBUG: Botão reset gráfico 1 foi clicado!')
        try:
            if not hasattr(self, 'ax1') or not self.ax1:
                fc_log("threshold", '[ERRO] DEBUG: ax1 não existe')
                return

            freq_min, freq_max = self._threshold_graph_freq_limits()
            self.zoom_factor_graph1 = 1.0

            if self.ax1.get_lines():
                self._apply_threshold_autoscale_y(self.ax1)
                fc_log(
                    "threshold",
                    f'[INFO] DEBUG: Reset gráfico 1 — autoscale Y, X=({freq_min}-{freq_max}MHz)',
                )
            else:
                graph_type = self.graph1_var.get()
                if "RSSI" in graph_type:
                    y_min, y_max = -80, 20
                elif "Module Power" in graph_type:
                    y_min, y_max = 0, 30
                elif "Irradiated Power" in graph_type:
                    y_min, y_max = 0, 30
                elif "Backscatter" in graph_type:
                    y_min, y_max = -80, -20
                elif "Power on Tag Forward" in graph_type:
                    y_min, y_max = -50, 30
                elif "Power on Tag Reversed" in graph_type:
                    y_min, y_max = -80, 20
                elif "Conversion Loss" in graph_type:
                    y_min, y_max = -40, 0
                elif "Max FCC Link Forward" in graph_type:
                    y_min, y_max = 0, 30
                elif "Max FCC Link Reversed" in graph_type:
                    y_min, y_max = 0, 50
                elif "RCS" in graph_type:
                    y_min, y_max = -20, 20
                else:
                    y_min, y_max = 0, 30
                self.ax1.set_xlim(freq_min, freq_max)
                self.ax1.set_ylim(y_min, y_max)
                fc_log(
                    "threshold",
                    f'[INFO] DEBUG: Reset gráfico 1 vazio: X=({freq_min}-{freq_max}MHz), Y=({y_min}-{y_max})',
                )
            
            self.canvas1.draw()
            # Atualiza limites salvos após reset
            try:
                self._last_xlim_graph1 = self.ax1.get_xlim()
                self._last_ylim_graph1 = self.ax1.get_ylim()
            except Exception:
                pass
            self.zoom_label_graph1.config(text="Zoom: 1.0x")
            fc_log("threshold", '[OK] Zoom resetado no Threshold Graph 1')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao resetar zoom do gráfico 1: {e}')
            import traceback
            traceback.print_exc()

    def _fit_to_data_graph1(self):
        """Ajusta a visualização automaticamente aos dados do gráfico 1"""
        try:
            if not hasattr(self, 'ax1') or not self.ax1:
                return
                
            # Obtém todos os dados plotados
            lines = self.ax1.get_lines()
            if not lines:
                return
                
            # Encontra limites dos dados
            x_data = []
            y_data = []
            
            for line in lines:
                x_line = line.get_xdata()
                y_line = line.get_ydata()
                
                # Filtra apenas linhas com dados de threshold (mais de 2 pontos e range X > 1 MHz)
                if len(x_line) > 2 and (max(x_line) - min(x_line)) > 1:
                    x_data.extend(x_line)
                    y_data.extend(y_line)
            
            if not x_data or not y_data:
                return
                
            # Calcula limites com margem
            x_min, x_max = min(x_data), max(x_data)
            y_min, y_max = min(y_data), max(y_data)
            
            # Margem inteligente baseada no range
            x_range = x_max - x_min
            y_range = y_max - y_min
            
            # Se o range for muito pequeno, usa margem absoluta menor
            if x_range < 10:  # Menos de 10 MHz de range
                x_margin = 1  # 1 MHz de margem para ranges pequenos
            else:
                x_margin = x_range * 0.05  # 5% de margem para ranges maiores
                
            if y_range < 5:  # Menos de 5 dBm de range
                y_margin = 1  # 1 dBm de margem
            else:
                y_margin = y_range * 0.05  # 5% de margem
            
            self.ax1.set_xlim(x_min - x_margin, x_max + x_margin)
            self.ax1.set_ylim(y_min - y_margin, y_max + y_margin)
            
            # Atualiza limites originais
            self.original_xlim_graph1 = self.ax1.get_xlim()
            self.original_ylim_graph1 = self.ax1.get_ylim()
            self.zoom_factor_graph1 = 1.0
            
            self.canvas1.draw()
            self.zoom_label_graph1.config(text="Zoom: 1.0x")
            fc_log("threshold", f'[OK] Visualização ajustada aos dados no Threshold Graph 1: X({x_min - x_margin:.1f}-{x_max + x_margin:.1f}MHz), Y({y_min - y_margin:.1f}-{y_max + y_margin:.1f}dBm)')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao ajustar visualização do gráfico 1: {e}')

    def _zoom_in_graph2(self):
        """Aplica zoom in no gráfico 2"""
        try:
            if not hasattr(self, 'ax2') or not self.ax2:
                return
                
            # Salva limites originais se ainda não foram salvos
            if self.original_xlim_graph2 is None:
                self.original_xlim_graph2 = self.ax2.get_xlim()
                self.original_ylim_graph2 = self.ax2.get_ylim()
            
            # Aplica zoom
            self.zoom_factor_graph2 *= 1.2
            xlim = self.ax2.get_xlim()
            ylim = self.ax2.get_ylim()
            
            x_center = (xlim[0] + xlim[1]) / 2
            y_center = (ylim[0] + ylim[1]) / 2
            
            x_range = (xlim[1] - xlim[0]) / self.zoom_factor_graph2
            y_range = (ylim[1] - ylim[0]) / self.zoom_factor_graph2
            
            self.ax2.set_xlim(x_center - x_range/2, x_center + x_range/2)
            self.ax2.set_ylim(y_center - y_range/2, y_center + y_range/2)
            
            self.canvas2.draw()
            # Atualiza limites salvos para preservar zoom entre atualizações
            try:
                self._last_xlim_graph2 = self.ax2.get_xlim()
                self._last_ylim_graph2 = self.ax2.get_ylim()
            except Exception:
                pass
            self.zoom_label_graph2.config(text=f"Zoom: {self.zoom_factor_graph2:.1f}x")
            fc_log("threshold", f'[OK] Zoom in aplicado no Threshold Graph 2: {self.zoom_factor_graph2:.1f}x')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao aplicar zoom in no gráfico 2: {e}')
    def _zoom_out_graph2(self):
        """Aplica zoom out no gráfico 2"""
        try:
            if not hasattr(self, 'ax2') or not self.ax2:
                return
                
            # Salva limites originais se ainda não foram salvos
            if self.original_xlim_graph2 is None:
                self.original_xlim_graph2 = self.ax2.get_xlim()
                self.original_ylim_graph2 = self.ax2.get_ylim()
            
            # Aplica zoom
            self.zoom_factor_graph2 /= 1.2
            xlim = self.ax2.get_xlim()
            ylim = self.ax2.get_ylim()
            
            x_center = (xlim[0] + xlim[1]) / 2
            y_center = (ylim[0] + ylim[1]) / 2
            
            x_range = (xlim[1] - xlim[0]) / self.zoom_factor_graph2
            y_range = (ylim[1] - ylim[0]) / self.zoom_factor_graph2
            
            self.ax2.set_xlim(x_center - x_range/2, x_center + x_range/2)
            self.ax2.set_ylim(y_center - y_range/2, y_center + y_range/2)
            
            self.canvas2.draw()
            # Atualiza limites salvos para preservar zoom entre atualizações
            try:
                self._last_xlim_graph2 = self.ax2.get_xlim()
                self._last_ylim_graph2 = self.ax2.get_ylim()
            except Exception:
                pass
            self.zoom_label_graph2.config(text=f"Zoom: {self.zoom_factor_graph2:.1f}x")
            fc_log("threshold", f'[OK] Zoom out aplicado no Threshold Graph 2: {self.zoom_factor_graph2:.1f}x')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao aplicar zoom out no gráfico 2: {e}')
    def _reset_zoom_graph2(self):
        """Reseta o zoom do gráfico 2"""
        fc_log("threshold", '[INFO] DEBUG: Função _reset_zoom_graph2 chamada')
        fc_log("threshold", '[INFO] DEBUG: Botão reset gráfico 2 foi clicado!')
        try:
            if not hasattr(self, 'ax2') or not self.ax2:
                fc_log("threshold", '[ERRO] DEBUG: ax2 não existe')
                return

            freq_min, freq_max = self._threshold_graph_freq_limits()
            self.zoom_factor_graph2 = 1.0

            if self.ax2.get_lines():
                self._apply_threshold_autoscale_y(self.ax2)
                fc_log(
                    "threshold",
                    f'[INFO] DEBUG: Reset gráfico 2 — autoscale Y, X=({freq_min}-{freq_max}MHz)',
                )
            else:
                graph_type = self.graph2_var.get()
                if "RSSI" in graph_type:
                    y_min, y_max = -80, 20
                elif "Module Power" in graph_type:
                    y_min, y_max = 0, 30
                elif "Irradiated Power" in graph_type:
                    y_min, y_max = 0, 30
                elif "Backscatter" in graph_type:
                    y_min, y_max = -80, -20
                elif "Power on Tag Forward" in graph_type:
                    y_min, y_max = -50, 30
                elif "Power on Tag Reversed" in graph_type:
                    y_min, y_max = -80, 20
                elif "Conversion Loss" in graph_type:
                    y_min, y_max = -40, 0
                elif "Max FCC Link Forward" in graph_type:
                    y_min, y_max = 0, 30
                elif "Max FCC Link Reversed" in graph_type:
                    y_min, y_max = 0, 50
                elif "RCS" in graph_type:
                    y_min, y_max = -20, 20
                else:
                    y_min, y_max = 0, 30
                self.ax2.set_xlim(freq_min, freq_max)
                self.ax2.set_ylim(y_min, y_max)
                fc_log(
                    "threshold",
                    f'[INFO] DEBUG: Reset gráfico 2 vazio: X=({freq_min}-{freq_max}MHz), Y=({y_min}-{y_max})',
                )
            
            self.canvas2.draw()
            # Atualiza limites salvos após reset
            try:
                self._last_xlim_graph2 = self.ax2.get_xlim()
                self._last_ylim_graph2 = self.ax2.get_ylim()
            except Exception:
                pass
            self.zoom_label_graph2.config(text="Zoom: 1.0x")
            fc_log("threshold", '[OK] Zoom resetado no Threshold Graph 2')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao resetar zoom do gráfico 2: {e}')
            import traceback
            traceback.print_exc()

    def _fit_to_data_graph2(self):
        """Ajusta a visualização automaticamente aos dados do gráfico 2"""
        try:
            if not hasattr(self, 'ax2') or not self.ax2:
                return
                
            # Obtém todos os dados plotados
            lines = self.ax2.get_lines()
            if not lines:
                return
                
            # Encontra limites dos dados
            x_data = []
            y_data = []
            
            for line in lines:
                x_line = line.get_xdata()
                y_line = line.get_ydata()
                
                # Filtra apenas linhas com dados de threshold (mais de 2 pontos e range X > 1 MHz)
                if len(x_line) > 2 and (max(x_line) - min(x_line)) > 1:
                    x_data.extend(x_line)
                    y_data.extend(y_line)
            
            if not x_data or not y_data:
                return
                
            # Calcula limites com margem
            x_min, x_max = min(x_data), max(x_data)
            y_min, y_max = min(y_data), max(y_data)
            
            # Margem inteligente baseada no range
            x_range = x_max - x_min
            y_range = y_max - y_min
            
            # Se o range for muito pequeno, usa margem absoluta menor
            if x_range < 10:  # Menos de 10 MHz de range
                x_margin = 1  # 1 MHz de margem para ranges pequenos
            else:
                x_margin = x_range * 0.05  # 5% de margem para ranges maiores
                
            if y_range < 5:  # Menos de 5 dBm de range
                y_margin = 1  # 1 dBm de margem
            else:
                y_margin = y_range * 0.05  # 5% de margem
            
            self.ax2.set_xlim(x_min - x_margin, x_max + x_margin)
            self.ax2.set_ylim(y_min - y_margin, y_max + y_margin)
            
            # Atualiza limites originais
            self.original_xlim_graph2 = self.ax2.get_xlim()
            self.original_ylim_graph2 = self.ax2.get_ylim()
            self.zoom_factor_graph2 = 1.0
            
            self.canvas2.draw()
            self.zoom_label_graph2.config(text="Zoom: 1.0x")
            fc_log("threshold", f'[OK] Visualização ajustada aos dados no Threshold Graph 2: X({x_min - x_margin:.1f}-{x_max + x_margin:.1f}MHz), Y({y_min - y_margin:.1f}-{y_max + y_margin:.1f}dBm)')
        except Exception as e:
            fc_log("threshold", f'[ERRO] Erro ao ajustar visualização do gráfico 2: {e}')

    def _toggle_pan_graph1(self):
        """Ativa/desativa o modo pan (arrastar gráfico 1)"""
        self.pan_active_graph1 = not self.pan_active_graph1
        
        if self.pan_active_graph1:
            self.pan_btn_graph1.config(relief=tk.SUNKEN, bg='#d0d0d0')
            self.canvas1.mpl_connect('button_press_event', self._on_pan_press_graph1)
            self.canvas1.mpl_connect('button_release_event', self._on_pan_release_graph1)
            self.canvas1.mpl_connect('motion_notify_event', self._on_pan_motion_graph1)
            self.pan_start_graph1 = None
            fc_log("threshold", '[OK] Modo Pan ativado no Gráfico 1')
        else:
            self.pan_btn_graph1.config(relief=tk.RAISED, bg='SystemButtonFace')
            fc_log("threshold", '[OK] Modo Pan desativado no Gráfico 1')
    def _on_pan_press_graph1(self, event):
        if self.pan_active_graph1 and event.inaxes == self.ax1:
            self.pan_start_graph1 = (event.xdata, event.ydata)
            self.pan_xlim_graph1 = self.ax1.get_xlim()
            self.pan_ylim_graph1 = self.ax1.get_ylim()

    def _on_pan_release_graph1(self, event):
        if self.pan_active_graph1:
            # OTIMIZAÇÃO HÍBRIDA: Aplica posição final exata
            if hasattr(self, '_pan_smooth_buffer_graph1'):
                # Aplica a posição final exata (sem suavização)
                self.ax1.set_xlim(self._pan_smooth_buffer_graph1['xlim'])
                self.ax1.set_ylim(self._pan_smooth_buffer_graph1['ylim'])
                self.canvas1.draw_idle()
                # Limpa o buffer
                delattr(self, '_pan_smooth_buffer_graph1')
            self.pan_start_graph1 = None

    def _on_pan_motion_graph1(self, event):
        if self.pan_active_graph1 and self.pan_start_graph1 and event.inaxes == self.ax1:
            dx = self.pan_start_graph1[0] - event.xdata
            dy = self.pan_start_graph1[1] - event.ydata
            new_xlim = (self.pan_xlim_graph1[0] + dx, self.pan_xlim_graph1[1] + dx)
            new_ylim = (self.pan_ylim_graph1[0] + dy, self.pan_ylim_graph1[1] + dy)
            
            # OTIMIZAÇÃO HÍBRIDA: Throttling agressivo + suavização para eliminar tremulação
            import time
            current_time = time.time()
            if not hasattr(self, '_last_pan_update_graph1'):
                self._last_pan_update_graph1 = 0
                self._pan_smooth_buffer_graph1 = {'xlim': new_xlim, 'ylim': new_ylim}
            
            # Atualiza apenas a cada 50ms (20 FPS) para reduzir tremulação drasticamente
            if current_time - self._last_pan_update_graph1 >= 0.05:
                # Suavização: interpola entre posição atual e nova posição
                if hasattr(self, '_pan_smooth_buffer_graph1'):
                    # Interpolação suave (70% da nova posição + 30% da anterior)
                    smooth_xlim = (
                        self._pan_smooth_buffer_graph1['xlim'][0] * 0.3 + new_xlim[0] * 0.7,
                        self._pan_smooth_buffer_graph1['xlim'][1] * 0.3 + new_xlim[1] * 0.7
                    )
                    smooth_ylim = (
                        self._pan_smooth_buffer_graph1['ylim'][0] * 0.3 + new_ylim[0] * 0.7,
                        self._pan_smooth_buffer_graph1['ylim'][1] * 0.3 + new_ylim[1] * 0.7
                    )
                else:
                    smooth_xlim = new_xlim
                    smooth_ylim = new_ylim
                
                self.ax1.set_xlim(smooth_xlim)
                self.ax1.set_ylim(smooth_ylim)
                self.canvas1.draw_idle()
                self._last_pan_update_graph1 = current_time
                self._pan_smooth_buffer_graph1 = {'xlim': smooth_xlim, 'ylim': smooth_ylim}

    def _toggle_pan_graph2(self):
        """Ativa/desativa o modo pan (arrastar gráfico 2)"""
        self.pan_active_graph2 = not self.pan_active_graph2
        
        if self.pan_active_graph2:
            self.pan_btn_graph2.config(relief=tk.SUNKEN, bg='#d0d0d0')
            self.canvas2.mpl_connect('button_press_event', self._on_pan_press_graph2)
            self.canvas2.mpl_connect('button_release_event', self._on_pan_release_graph2)
            self.canvas2.mpl_connect('motion_notify_event', self._on_pan_motion_graph2)
            self.pan_start_graph2 = None
            fc_log("threshold", '[OK] Modo Pan ativado no Gráfico 2')
        else:
            self.pan_btn_graph2.config(relief=tk.RAISED, bg='SystemButtonFace')
            fc_log("threshold", '[OK] Modo Pan desativado no Gráfico 2')
    def _on_pan_press_graph2(self, event):
        if self.pan_active_graph2 and event.inaxes == self.ax2:
            self.pan_start_graph2 = (event.xdata, event.ydata)
            self.pan_xlim_graph2 = self.ax2.get_xlim()
            self.pan_ylim_graph2 = self.ax2.get_ylim()

    def _on_pan_release_graph2(self, event):
        if self.pan_active_graph2:
            # OTIMIZAÇÃO HÍBRIDA: Aplica posição final exata
            if hasattr(self, '_pan_smooth_buffer_graph2'):
                # Aplica a posição final exata (sem suavização)
                self.ax2.set_xlim(self._pan_smooth_buffer_graph2['xlim'])
                self.ax2.set_ylim(self._pan_smooth_buffer_graph2['ylim'])
                self.canvas2.draw_idle()
                # Limpa o buffer
                delattr(self, '_pan_smooth_buffer_graph2')
            self.pan_start_graph2 = None

    def _on_pan_motion_graph2(self, event):
        if self.pan_active_graph2 and self.pan_start_graph2 and event.inaxes == self.ax2:
            dx = self.pan_start_graph2[0] - event.xdata
            dy = self.pan_start_graph2[1] - event.ydata
            new_xlim = (self.pan_xlim_graph2[0] + dx, self.pan_xlim_graph2[1] + dx)
            new_ylim = (self.pan_ylim_graph2[0] + dy, self.pan_ylim_graph2[1] + dy)
            
            # OTIMIZAÇÃO HÍBRIDA: Throttling agressivo + suavização para eliminar tremulação
            import time
            current_time = time.time()
            if not hasattr(self, '_last_pan_update_graph2'):
                self._last_pan_update_graph2 = 0
                self._pan_smooth_buffer_graph2 = {'xlim': new_xlim, 'ylim': new_ylim}
            
            # Atualiza apenas a cada 50ms (20 FPS) para reduzir tremulação drasticamente
            if current_time - self._last_pan_update_graph2 >= 0.05:
                # Suavização: interpola entre posição atual e nova posição
                if hasattr(self, '_pan_smooth_buffer_graph2'):
                    # Interpolação suave (70% da nova posição + 30% da anterior)
                    smooth_xlim = (
                        self._pan_smooth_buffer_graph2['xlim'][0] * 0.3 + new_xlim[0] * 0.7,
                        self._pan_smooth_buffer_graph2['xlim'][1] * 0.3 + new_xlim[1] * 0.7
                    )
                    smooth_ylim = (
                        self._pan_smooth_buffer_graph2['ylim'][0] * 0.3 + new_ylim[0] * 0.7,
                        self._pan_smooth_buffer_graph2['ylim'][1] * 0.3 + new_ylim[1] * 0.7
                    )
                else:
                    smooth_xlim = new_xlim
                    smooth_ylim = new_ylim
                
                self.ax2.set_xlim(smooth_xlim)
                self.ax2.set_ylim(smooth_ylim)
                self.canvas2.draw_idle()
                self._last_pan_update_graph2 = current_time
                self._pan_smooth_buffer_graph2 = {'xlim': smooth_xlim, 'ylim': smooth_ylim}

if __name__ == '__main__':
    root = tk.Tk()
    root.title("Módulo Threshold (Desenvolvimento)")
    root.geometry("1100x750")
    app = ThresholdModule(root)
    app.pack(fill="both", expand=True)
    
    root.mainloop()

