# antenna_module.py
# Versão: 6.2 (Build com Edição In-line)
# Data: 01/09/2025
# Descrição: Módulo de Antena com sistema de histórico persistente, plotagem comparativa,
#            e interface de usuário alinhada com os outros módulos de análise.
#            - NOVO: Edição in-line do nome do teste no histórico com duplo-clique.

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import ctypes
import os
import sys
import struct
import threading
import time
from decimal import Decimal, getcontext
import json
from datetime import datetime

try:
    import matplotlib
    matplotlib.use("TkAgg")
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import numpy as np
    try:
        from scipy.interpolate import UnivariateSpline
        SCIPY_AVAILABLE = True
    except ImportError:
        SCIPY_AVAILABLE = False
    LIBS_DISPONIVEIS = True
except ImportError:
    LIBS_DISPONIVEIS = False
    SCIPY_AVAILABLE = False

from .app_console import fc_log
from .shell_open import open_path_with_default_app

try:
    from .antenna_database import AntennaDatabase
    DATABASE_AVAILABLE = True
except ImportError:
    try:
        from antenna_database import AntennaDatabase
        DATABASE_AVAILABLE = True
    except ImportError:
        DATABASE_AVAILABLE = False
        fc_log("antenna", "⚠️ Módulo de banco de dados (antenna_database.py) não encontrado.")

try:
    from .i18n import get_translator
    TRANSLATOR_AVAILABLE = True
except ImportError:
    try:
        from i18n import get_translator  # type: ignore
        TRANSLATOR_AVAILABLE = True
    except ImportError:
        TRANSLATOR_AVAILABLE = False
        get_translator = None  # type: ignore

# --- CONFIGURAÇÕES E CONSTANTES DO MÓDULO ---
getcontext().prec = 10
DLL_NAME = "UHFRFID.dll"
# COM_PORT = 4 ### ALTERADO: Removido
BAUD_RATE = 115200
RFID_CMD_SET_TXPOWER = 0x10
RFID_CMD_GET_PORT_LOSS = 0x32
RFID_CMD_SET_FREQ_TABLE = 0x14

try:
    if hasattr(sys, '_MEIPASS'):
        dll_path = os.path.join(sys._MEIPASS, DLL_NAME)
    else:
        dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), DLL_NAME)
    rfid_sdk = ctypes.CDLL(dll_path)
    rfid_sdk.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]
    rfid_sdk.UHF_RFID_Open.restype = ctypes.c_int
    rfid_sdk.UHF_RFID_Close.argtypes = [ctypes.c_ubyte]
    rfid_sdk.UHF_RFID_Close.restype = ctypes.c_int
    rfid_sdk.UHF_RFID_Set.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint)]
    rfid_sdk.UHF_RFID_Set.restype = ctypes.c_int
except OSError:
    rfid_sdk = None

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

class AntennaModule(ttk.Frame):
    ### ALTERADO: Adicionado com_port=4 ###
    def __init__(self, master=None, is_licensed=False, license_limits=None, app_shell=None, demo_mode=False, com_port=4):
        super().__init__(master)
        self.master = master
        self.app_shell = app_shell
        self.demo_mode = demo_mode
        self.com_port = com_port ### NOVO: Armazena a porta COM ###

        # CORREÇÃO: Padroniza com outros módulos - usa license_limits como dicionário
        self.license_limits = license_limits if license_limits else {'min_freq': 800, 'max_freq': 1000, 'min_power': 5, 'max_power': 25, 'is_licensed': False}
        
        # Atualiza limites da licença se app_shell estiver disponível
        if app_shell and hasattr(app_shell, 'license_limits'):
            self.license_limits = app_shell.license_limits
        elif app_shell and hasattr(app_shell, '_calculate_license_limits'):
            # Tenta obter limites da licença ativa
            try:
                valid_lics = ["AntennaCheck", "FastChecker"]
                license_limits = app_shell._calculate_license_limits(valid_lics)
                if license_limits.get('is_licensed', False):
                    self.license_limits = license_limits
                    fc_log("antenna", f"✅ AntennaCheck: Limites da licença aplicados - Freq: {license_limits.get('min_freq')}-{license_limits.get('max_freq')}MHz, Power: {license_limits.get('min_power')}-{license_limits.get('max_power')}dBm")
                    if license_limits.get('excluded_ranges'):
                        fc_log("antenna", f"✅ AntennaCheck: Faixas excluídas: {license_limits.get('excluded_ranges')}")
                else:
                    fc_log("antenna", "⚠️ AntennaCheck: Usando limites padrão (sem licença ativa)")
            except Exception as e:
                fc_log("antenna", f"⚠️ AntennaCheck: Erro ao obter limites da licença: {e}")
                fc_log("antenna", "⚠️ AntennaCheck: Usando limites padrão")

        # CORREÇÃO: Usa license_limits padronizado
        self.min_freq_lic = self.license_limits.get('min_freq', 800)
        self.max_freq_lic = self.license_limits.get('max_freq', 1000)
        self.min_power_lic = self.license_limits.get('min_power', 5)
        self.max_power_lic = self.license_limits.get('max_power', 25)

        self.translator = get_translator() if TRANSLATOR_AVAILABLE and callable(get_translator) else None
        self._translation_cache = {}
        self.worker_thread = None
        self.test_is_running = False
        self.current_test_run = None
        self.historical_plots = {}
        
        # Variáveis de zoom
        self.zoom_factor = 1.0
        self.original_xlim = None
        self.original_ylim = None
        self.annot = None
        self.protection_enabled = True
        # Popup de proteção: poller na thread principal mostra o aviso (como "Nome Duplicado")
        self._protection_popup = None
        self._stopped_by_protection = False
        self._protection_power_dbm = 10.0
        self._protection_requested = False  # worker levanta; poller ou _on_scan_finished mostram o popup
        self._protection_popup_shown = False  # evita mostrar duas vezes
        self._protection_message = None  # (titulo, msg) para popup de bloqueio durante leitura; None = mensagem padrão
        self._protection_poll_id = None
        self._open_failed_reason = None  # "dll" | "hardware" quando falha na abertura
        # NOVO: Variáveis para controle de ordenação
        self.current_sort_column = None
        self.current_sort_reverse = False
        
        # NOVO: Mensagem informativa sobre faixas excluídas
        self.license_info_message = None
        
        # Verifica disponibilidade do scipy para suavização
        if not SCIPY_AVAILABLE:
            fc_log("antenna", "⚠️ AntennaCheck: Biblioteca scipy não encontrada. Funcionalidade de curva suave não estará disponível.")
            fc_log("antenna", "💡 Para habilitar: pip install scipy")

        # CORREÇÃO: Status inicial contextual baseado na licença
        # Removido status_var - agora usamos browser_frame
        self.power_var = tk.IntVar(value=self.max_power_lic)
        self.freq_step_var = tk.DoubleVar(value=5.0)
        self.description_var = tk.StringVar(value="Antena_Teste")
        self.freq_min_var = tk.StringVar(value=str(self.min_freq_lic))
        self.freq_max_var = tk.StringVar(value=str(self.max_freq_lic))
        default_plot_label = self._get_plot_type_options()[0][1]
        self.plot_type_var = tk.StringVar(value=default_plot_label)
        default_curve_label = self._get_curve_type_options()[0][1]
        self.curve_type_var = tk.StringVar(value=default_curve_label)

        # Todos os widgets agora usam Spinbox com bordas nativas

        self.database = None
        if DATABASE_AVAILABLE:
            try:
                self.database = AntennaDatabase()
            except Exception as e:
                fc_log("antenna", f"⚠️ Erro ao inicializar banco de dados de Antena: {e}")

        self.create_widgets()
        
        # CORREÇÃO: Aplica o estado inicial da UI baseado na licença
        self.update_ui_state(False)
        
        self.bind("<Destroy>", self.on_destroy)

        if not rfid_sdk or not LIBS_DISPONIVEIS:
            self.show_dependency_error()
        
        if DATABASE_AVAILABLE:
            self.after(200, self.update_plot_from_history)

    def _t(self, key, default=None, **kwargs):
        translator = self.translator
        if translator:
            try:
                current_lang = getattr(translator, 'get_language', lambda: None)()
                cache_key = (current_lang, key)
                if cache_key in self._translation_cache:
                    text = self._translation_cache[cache_key]
                else:
                    text = translator.translate(key, default if default is not None else key)
                    self._translation_cache[cache_key] = text
            except Exception:
                text = default if default is not None else key
        else:
            text = default if default is not None else key

        if kwargs:
            try:
                return text.format(**kwargs)
            except Exception:
                return text
        return text

    def refresh_language(self):
        """Contrato com app_shell: reaplica traduções quando o idioma muda."""
        self._translation_cache.clear()
        self.after_idle(self._apply_translations)

    def _get_plot_type_options(self):
        return [
            ("return_loss", self._t('antenna.plot_option_return_loss', 'Proteção do Modulo')),
            ("vswr", self._t('antenna.plot_option_vswr', 'VSWR')),
        ]

    def _get_plot_type_code(self):
        current_label = self.plot_type_var.get()
        for code, label in self._get_plot_type_options():
            if current_label == label:
                return code
        return "vswr" if "VSWR" in current_label.upper() else "return_loss"

    def _is_plot_vswr(self):
        return self._get_plot_type_code() == "vswr"

    def _refresh_plot_type_options(self):
        options = self._get_plot_type_options()
        labels = [label for _, label in options]
        current_code = self._get_plot_type_code()
        self.plot_type_menu.configure(values=labels)
        for code, label in options:
            if code == current_code:
                self.plot_type_var.set(label)
                break

    def _get_curve_type_options(self):
        options = [("point", self._t('antenna.curve_point', 'Curva Pontual'))]
        if SCIPY_AVAILABLE:
            options.append(("smooth", self._t('antenna.curve_smooth', 'Curva Suave')))
        return options

    def _get_curve_type_code(self):
        current_label = self.curve_type_var.get()
        for code, label in self._get_curve_type_options():
            if current_label == label:
                return code
        return "smooth" if "suave" in current_label.lower() or "smooth" in current_label.lower() else "point"

    def _refresh_curve_type_options(self):
        options = self._get_curve_type_options()
        labels = [label for _, label in options]
        current_code = self._get_curve_type_code()
        self.curve_type_menu.configure(values=labels)
        for code, label in options:
            if code == current_code:
                self.curve_type_var.set(label)
                break

    def _apply_translations(self):
        """Aplica traduções aos elementos da interface."""
        try:
            if hasattr(self, 'config_frame'):
                self.config_frame.configure(text=self._t('antenna.test_config', 'Configuração do Teste'))
            if hasattr(self, 'controls_frame'):
                self.controls_frame.configure(text=self._t('antenna.controls', 'Controles'))
            if hasattr(self, 'history_frame'):
                self.history_frame.configure(text=self._t('antenna.test_history', 'Histórico de Testes'))

            if hasattr(self, 'test_name_label'):
                self.test_name_label.configure(text=self._t('antenna.name', 'Nome:'))
            if hasattr(self, 'power_label'):
                self.power_label.configure(text=self._t('antenna.power_dbm', 'Potência (dBm):'))
            if hasattr(self, 'plot_type_label'):
                self.plot_type_label.configure(text=self._t('antenna.graph', 'Gráfico:'))
            if hasattr(self, 'curve_type_label'):
                self.curve_type_label.configure(text=self._t('antenna.curve', 'Curva:'))
            if hasattr(self, 'frequency_label'):
                self.frequency_label.configure(text=self._t('antenna.frequency_mhz', 'Frequência (MHz):'))
            if hasattr(self, 'freq_min_label'):
                self.freq_min_label.configure(text=self._t('antenna.min', 'Min:'))
            if hasattr(self, 'freq_max_label'):
                self.freq_max_label.configure(text=self._t('antenna.max', 'Max:'))
            if hasattr(self, 'freq_step_label'):
                self.freq_step_label.configure(text=self._t('antenna.step_mhz', 'Passo (MHz):'))

            self._refresh_plot_type_options()
            self._refresh_curve_type_options()

            if hasattr(self, 'start_button'):
                if getattr(self, 'test_is_running', False):
                    self.start_button.configure(text=self._t('antenna.stop', 'Parar'), command=self.interrupt_scan)
                else:
                    self.start_button.configure(text=self._t('antenna.test', 'Testar'), command=self.start_scan)
            if hasattr(self, 'clear_button'):
                self.clear_button.configure(text=self._t('antenna.clear_plot', 'Limpar Gráfico'))
            if hasattr(self, 'save_selected_button'):
                self.save_selected_button.configure(text=self._t('antenna.save_selected', 'Salvar Selecionados'))
            if hasattr(self, 'import_tests_button'):
                self.import_tests_button.configure(text=self._t('antenna.import_tests', 'Importar Testes'))
            if hasattr(self, 'generate_pdf_button'):
                self.generate_pdf_button.configure(text=self._t('antenna.report_pdf', 'Relatório Selecionados (PDF)'))

            if hasattr(self, 'history_stats_label'):
                self.update_history_stats()
            self._update_history_headings()

            if hasattr(self, 'select_all_button'):
                self.select_all_button.configure(text=self._t('antenna.select_all', 'Selecionar Todos'))
            if hasattr(self, 'deselect_all_button'):
                self.deselect_all_button.configure(text=self._t('antenna.deselect_all', 'Deselecionar Todos'))
            if hasattr(self, 'delete_selected_button'):
                self.delete_selected_button.configure(text=self._t('antenna.delete_selected', 'Excluir Selecionados...'))

            if hasattr(self, 'browser_mode_label'):
                self.browser_mode_label.configure(text=f" {self._t('antenna.browser_mode_label', 'modo browser')}")

            if hasattr(self, 'license_info_label'):
                self._update_license_info_message()

            if hasattr(self, 'ax'):
                axis_label = self._t('antenna.y_axis_return_loss', 'Proteção do Modulo')
                if self._get_plot_type_code() == 'vswr':
                    axis_label = self._t('antenna.y_axis_vswr', 'VSWR')
                self.ax.set_xlabel(self._t('antenna.x_axis_label', 'Frequência (MHz)'))
                self.ax.set_ylabel(axis_label)
                self.canvas.draw_idle()

            if hasattr(self, 'history_tree'):
                self._update_history_headings()

        except Exception as e:
            fc_log("antenna", f"⚠️ AntennaModule: Erro ao aplicar traduções: {e}")

    def _update_history_headings(self):
        if not hasattr(self, 'history_tree'):
            return
        headings = {
            "Plot": self._t('antenna.plot', 'Plot'),
            "ID": "ID",
            "Nome": self._t('antenna.name_col', 'Nome'),
            "Potência": self._t('antenna.power', 'Pot (dBm)'),
            "Range Freq": self._t('antenna.freq_range', 'Range Freq (MHz)'),
            "Min Return Loss": self._t('antenna.min_return_loss', 'Min Return Loss (dBm)'),
            "VSWR": self._t('antenna.vswr', 'VSWR'),
            "Freq. Melhor Valor": self._t('antenna.best_freq', 'Na Freq (MHz)'),
            "Data/Hora": self._t('antenna.date_time', 'Data/Hora')
        }
        for column, label in headings.items():
            if column == "ID":
                heading_text = f"{label}"
            else:
                heading_text = f"{label} ↕"
            try:
                self.history_tree.heading(column, text=heading_text, command=lambda col=column: self.sort_treeview(col))
            except Exception as e:
                fc_log("antenna", f"⚠️ AntennaModule: Erro ao atualizar heading '{column}': {e}")
    def on_destroy(self, event=None):
        self.test_is_running = False
        if getattr(self, 'worker_thread', None) and self.worker_thread.is_alive():
            self.worker_thread.join(timeout=0.5)
        if getattr(self, '_protection_popup', None):
            try:
                self._protection_popup.destroy()
            except Exception:
                pass
            self._protection_popup = None
        if getattr(self, '_protection_poll_id', None):
            try:
                root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
                if root and root.winfo_exists():
                    root.after_cancel(self._protection_poll_id)
            except Exception:
                pass
            self._protection_poll_id = None
        if rfid_sdk:
            port = getattr(self, 'com_port', None)
            if port is not None:
                def _close():
                    try:
                        rfid_sdk.UHF_RFID_Close(port)
                    except Exception:
                        pass
                threading.Thread(target=_close, daemon=True).start()

    def show_dependency_error(self):
        for widget in self.winfo_children(): widget.destroy()
        error_msg = ""
        if not LIBS_DISPONIVEIS:
            error_msg += "Bibliotecas 'matplotlib' e 'numpy' não encontradas.\nInstale com: pip install matplotlib numpy\n\n"
        if not rfid_sdk:
            error_msg += f"Não foi possível carregar a DLL '{DLL_NAME}'.\nEste módulo não funcionará."
        ttk.Label(self, text=error_msg, justify="center", font=("Segoe UI", 12, "bold"), foreground="red").pack(expand=True, padx=20, pady=20)

    def create_widgets(self):
        # Layout principal: barra lateral + área principal
        main_container = ttk.Frame(self)
        main_container.pack(fill="both", expand=True, padx=5, pady=5)
        
        # Configuração do grid
        main_container.columnconfigure(1, weight=1)  # Área principal ocupa espaço restante
        main_container.rowconfigure(0, weight=1)
        
        # BARRA LATERAL (esquerda)
        self.sidebar = ttk.Frame(main_container, width=300)
        self.sidebar.grid(row=0, column=0, sticky="nsew", padx=(0, 5))
        self.sidebar.grid_propagate(False)  # Mantém largura fixa
        
        # ÁREA PRINCIPAL (direita)
        self.main_area = ttk.Frame(main_container)
        self.main_area.grid(row=0, column=1, sticky="nsew")
        self.main_area.columnconfigure(0, weight=1)
        self.main_area.rowconfigure(0, weight=1)
        
        # Construir componentes da barra lateral
        self._build_sidebar()
        
        # Construir área principal
        self._build_main_area()
        
        # Mensagem de modo browser (se necessário)
        self._setup_browser_mode_message()
        
        # NOVO: Mensagem informativa sobre faixas excluídas
        self._setup_license_info_message()

    def _build_sidebar(self):
        """Constrói a barra lateral com todas as seções"""
        # 1. Configuração do Teste
        self._build_test_config_section()
        
        # 2. Controles
        self._build_controls_section()

    def _build_test_config_section(self):
        """Seção de configuração do teste"""
        self.config_frame = ttk.LabelFrame(self.sidebar, text=self._t('antenna.test_config', 'Configuração do Teste'), padding=10)
        self.config_frame.pack(fill="x", pady=(0, 10))
        
        # Nome do teste
        self.test_name_label = ttk.Label(self.config_frame, text=self._t('antenna.name', 'Nome:'))
        self.test_name_label.pack(anchor='w')
        self.desc_entry = ttk.Entry(self.config_frame, textvariable=self.description_var)
        self.desc_entry.pack(fill='x', pady=(0, 5))
        
        # Potência
        self.power_label = ttk.Label(self.config_frame, text=self._t('antenna.power_dbm', 'Potência (dBm):'))
        self.power_label.pack(anchor='w')
        self.power_entry = ttk.Spinbox(self.config_frame, from_=self.min_power_lic, to=self.max_power_lic, increment=5, textvariable=self.power_var, wrap=True)
        self.power_entry.pack(fill='x', pady=(0, 5))
        
        # Tipo de gráfico
        self.plot_type_label = ttk.Label(self.config_frame, text=self._t('antenna.graph', 'Gráfico:'))
        self.plot_type_label.pack(anchor='w')
        plot_labels = [label for _, label in self._get_plot_type_options()]
        self.plot_type_menu = ttk.Spinbox(self.config_frame, values=plot_labels, textvariable=self.plot_type_var, wrap=True)
        self.plot_type_menu.pack(fill='x', pady=(0, 5))
        self.plot_type_menu.bind('<FocusOut>', self.on_plot_type_change)
        self.plot_type_menu.bind('<Return>', self.on_plot_type_change)
        self.plot_type_menu.bind('<KeyRelease>', self.on_plot_type_change)
        self.plot_type_menu.bind('<Button-1>', self.on_plot_type_change)
        self.plot_type_var.trace('w', self.on_plot_type_change)
        
        # Tipo de curva
        self.curve_type_label = ttk.Label(self.config_frame, text=self._t('antenna.curve', 'Curva:'))
        self.curve_type_label.pack(anchor='w')
        curve_labels = [label for _, label in self._get_curve_type_options()]
        self.curve_type_menu = ttk.Spinbox(self.config_frame, values=curve_labels, textvariable=self.curve_type_var, wrap=True)
        self.curve_type_menu.pack(fill='x', pady=(0, 5))
        self.curve_type_menu.bind('<FocusOut>', self.on_curve_type_change)
        self.curve_type_menu.bind('<Return>', self.on_curve_type_change)
        self.curve_type_menu.bind('<KeyRelease>', self.on_curve_type_change)
        self.curve_type_menu.bind('<Button-1>', self.on_curve_type_change)
        self.curve_type_var.trace('w', self.on_curve_type_change)
        
        if not SCIPY_AVAILABLE:
            self.curve_type_menu.configure(state="disabled")
        
        # Frequências
        self.frequency_label = ttk.Label(self.config_frame, text=self._t('antenna.frequency_mhz', 'Frequência (MHz):'))
        self.frequency_label.pack(anchor='w')
        
        freq_range_frame = ttk.Frame(self.config_frame)
        freq_range_frame.pack(fill='x', pady=(0, 5))
        
        self.freq_min_label = ttk.Label(freq_range_frame, text=self._t('antenna.min', 'Min:'))
        self.freq_min_label.pack(side="left")
        self.freq_min_entry = ttk.Entry(freq_range_frame, textvariable=self.freq_min_var, width=8)
        self.freq_min_entry.pack(side="left", padx=(2, 5))
        
        self.freq_max_label = ttk.Label(freq_range_frame, text=self._t('antenna.max', 'Max:'))
        self.freq_max_label.pack(side="left")
        self.freq_max_entry = ttk.Entry(freq_range_frame, textvariable=self.freq_max_var, width=8)
        self.freq_max_entry.pack(side="left", padx=(2, 0))
        
        # Passo de frequência
        self.freq_step_label = ttk.Label(self.config_frame, text=self._t('antenna.step_mhz', 'Passo (MHz):'))
        self.freq_step_label.pack(anchor='w')
        self.freq_step_menu = ttk.Spinbox(self.config_frame, from_=0.5, to=10.0, increment=0.5, textvariable=self.freq_step_var, wrap=True)
        self.freq_step_menu.pack(fill='x')


    def _build_controls_section(self):
        """Seção de controles"""
        self.controls_frame = ttk.LabelFrame(self.sidebar, text=self._t('antenna.controls', 'Controles'), padding=10)
        self.controls_frame.pack(fill="x", padx=5, pady=5)
        
        # Um único botão: Testar (quando parado) / Parar (quando a correr)
        self.start_button = ttk.Button(self.controls_frame, text=self._t('antenna.test', 'Testar'), command=self.start_scan, state="disabled")
        self.start_button.pack(fill="x", pady=2)
        
        # Botão de limpar
        self.clear_button = ttk.Button(self.controls_frame, text=self._t('antenna.clear_plot', 'Limpar Gráfico'), command=self.clear_plot_and_selection)
        self.clear_button.pack(fill="x", pady=2)
        
        # Separador
        separator = ttk.Separator(self.controls_frame, orient='horizontal')
        separator.pack(fill="x", pady=5)
        
        # Botão de salvar selecionados
        self.save_selected_button = ttk.Button(self.controls_frame, text=self._t('antenna.save_selected', 'Salvar Selecionados'), command=self.save_selected_tests, state="disabled")
        self.save_selected_button.pack(fill="x", pady=2)
        
        # Botão de importar testes
        self.import_tests_button = ttk.Button(self.controls_frame, text=self._t('antenna.import_tests', 'Importar Testes'), command=self.import_tests)
        self.import_tests_button.pack(fill="x", pady=2)
        
        # Botão de gerar relatório PDF dos selecionados
        self.generate_pdf_button = ttk.Button(
            self.controls_frame,
            text=self._t('antenna.report_pdf', "Relatório Selecionados (PDF)"),
            command=self.generate_pdf_report
        )
        self.generate_pdf_button.pack(fill="x", pady=2)


    def _build_main_area(self):
        """Constrói a área principal com gráfico e histórico"""
        # Área do gráfico
        self._build_plot_area()
        
        # Área do histórico (se disponível)
        if DATABASE_AVAILABLE:
            self._build_history_area()

    def _build_plot_area(self):
        """Constrói a área do gráfico"""
        plot_frame = ttk.Frame(self.main_area)
        plot_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
        plot_frame.columnconfigure(0, weight=1)
        plot_frame.rowconfigure(0, weight=1)

        self.fig = Figure(figsize=(6, 4), dpi=100)
        self.ax = self.fig.add_subplot(111)
        self.fig.subplots_adjust(right=0.75, bottom=0.15)
        self.setup_plot()
        
        self.canvas = FigureCanvasTkAgg(self.fig, master=plot_frame)
        self.canvas.draw()
        self.canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew")
        
        # Controles de zoom na parte superior esquerda
        self._setup_zoom_controls(plot_frame)
        
        self.setup_interactivity()

    def _build_history_area(self):
        """Constrói a área do histórico"""
        self.history_frame = ttk.LabelFrame(self.main_area, text=self._t('antenna.test_history', 'Histórico de Testes'), padding=10)
        self.history_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5)
        
        # Estatísticas
        stats_frame = ttk.Frame(self.history_frame)
        stats_frame.pack(fill="x", pady=(0, 5))
        self.history_stats_label = ttk.Label(stats_frame, text=self._t('antenna.stats_loading', 'Estatísticas: Carregando...'))
        self.history_stats_label.pack(side="left")
        
        # Treeview do histórico
        tree_frame = ttk.Frame(self.history_frame)
        tree_frame.pack(fill="both", expand=True)
        
        columns = ("Plot", "ID", "Nome", "Potência", "Range Freq", "Min Return Loss", "VSWR", "Freq. Melhor Valor", "Data/Hora")
        display_columns = ("Plot", "Nome", "Potência", "Range Freq", "Min Return Loss", "VSWR", "Freq. Melhor Valor", "Data/Hora")

        self.history_tree = ttk.Treeview(tree_frame, columns=columns, displaycolumns=display_columns, show="headings", height=8)
        
        # Configurar colunas
        self.history_tree.heading("Plot", text=self._t('antenna.plot', 'Plot') + " ↕", command=lambda: self.sort_treeview("Plot"))
        self.history_tree.column("Plot", width=40, anchor='center')
        self.history_tree.column("Nome", width=150)
        self.history_tree.column("Potência", width=80, anchor='center')
        self.history_tree.column("Range Freq", width=120, anchor='center')
        self.history_tree.column("Min Return Loss", width=120, anchor='center')
        self.history_tree.column("VSWR", width=80, anchor='center')
        self.history_tree.column("Freq. Melhor Valor", width=120, anchor='center')
        self.history_tree.column("Data/Hora", width=150, anchor='center')

        scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.history_tree.yview)
        self.history_tree.configure(yscrollcommand=scrollbar.set)
        
        self.history_tree.pack(side='left', fill='both', expand=True)
        scrollbar.pack(side='right', fill='y')

        # Botões de ação
        actions_frame = ttk.Frame(self.history_frame)
        actions_frame.pack(fill="x", pady=(5, 0))
        self.select_all_button = ttk.Button(actions_frame, text=self._t('antenna.select_all', 'Selecionar Todos'), command=self.select_all_tests)
        self.select_all_button.pack(side='left', padx=(0, 5))
        self.deselect_all_button = ttk.Button(actions_frame, text=self._t('antenna.deselect_all', 'Deselecionar Todos'), command=self.deselect_all_tests)
        self.deselect_all_button.pack(side='left', padx=(0, 5))
        self.delete_selected_button = ttk.Button(actions_frame, text=self._t('antenna.delete_selected', 'Excluir Selecionados...'), command=self.delete_selected_tests)
        self.delete_selected_button.pack(side='left', padx=(0, 5))

        self.history_tree.bind('<Button-1>', self.on_history_tree_click)
        self.history_tree.bind('<Double-1>', self.on_history_tree_double_click)
        
        self.load_history_to_tree()
        self._update_history_headings()

    def _setup_browser_mode_message(self):
        """Configura mensagem de modo browser"""
        # --- Mensagem de Modo Browser ---
        # Posiciona a mensagem DENTRO da janela "Configuração do Teste"
        target_parent = getattr(self, 'config_frame', self)
        self.browser_frame = tk.Frame(target_parent, bg='#f0f8ff')
        
        # 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=" modo browser", fg="blue", font=("Helvetica", 10), bg='#f0f8ff')
        self.browser_mode_label.pack(side='left', anchor='w')
        
        # CORREÇÃO: Controle inicial da visibilidade baseado na licença
        is_licensed = self.license_limits.get('is_licensed', False)
        fc_log("antenna", f"🔍 AntennaModule: Status da licença: {is_licensed}")
        fc_log("antenna", f"🔍 AntennaModule: license_limits: {self.license_limits}")
        
        if is_licensed:
            self.browser_frame.pack_forget()  # Esconde quando há licença
            fc_log("antenna", "✅ AntennaModule: Mensagem de modo browser ocultada (licença ativa)")
        else:
            # Dentro da seção de configuração
            self.browser_frame.pack(fill="x", pady=(5, 5), padx=5)
            fc_log("antenna", "⚠️ AntennaModule: Mensagem de modo browser visível (sem licença)")

    def _setup_license_info_message(self):
        """NOVO: Configura mensagem informativa sobre faixas excluídas"""
        # --- Mensagem Informativa sobre Faixas Excluídas ---
        self.license_info_frame = tk.Frame(self, bg='#fff3cd')
        
        # Ícone informativo
        info_label = tk.Label(self.license_info_frame, text="ℹ️", fg="orange", font=("Helvetica", 10), bg='#fff3cd')
        info_label.pack(side='left', anchor='w')
        
        # Texto informativo
        self.license_info_label = tk.Label(self.license_info_frame, text="", fg="orange", font=("Helvetica", 9), bg='#fff3cd')
        self.license_info_label.pack(side='left', anchor='w')
        
        # Controle inicial da visibilidade
        self._update_license_info_message()

    def _update_license_info_message(self):
        """NOVO: Atualiza a mensagem informativa sobre faixas excluídas"""
        try:
            excluded_ranges = self.license_limits.get('excluded_ranges', [])
            is_licensed = self.license_limits.get('is_licensed', False)
            
            if is_licensed and excluded_ranges:
                excluded_str = " e ".join([f"{r[0]}-{r[1]} MHz" for r in excluded_ranges])
                message = f" Durante o teste, frequências nas faixas excluídas ({excluded_str}) serão puladas automaticamente"
                self.license_info_label.config(text=message)
                self.license_info_frame.pack(side="left", fill="x", pady=(5, 5), padx=5)
                fc_log("antenna", f"ℹ️ AntennaModule: Mensagem informativa exibida - faixas excluídas: {excluded_str}")
            else:
                self.license_info_frame.pack_forget()
                fc_log("antenna", "ℹ️ AntennaModule: Mensagem informativa ocultada - sem faixas excluídas")
        except Exception as e:
            fc_log("antenna", f"⚠️ Erro ao atualizar mensagem informativa: {e}")
            self.license_info_frame.pack_forget()

    def _update_ui_for_license_status(self):
        """CORREÇÃO: Atualiza a UI baseado no status da licença"""
        is_licensed = self.license_limits.get('is_licensed', False)
        
        if is_licensed:
            self.browser_frame.pack_forget()  # Esconde quando há licença
            fc_log("antenna", "✅ AntennaModule: Mensagem de modo browser ocultada (licença ativa)")
        else:
            self.browser_frame.pack(fill="x", pady=(5, 5), padx=5)  # Mostra quando não há licença
            fc_log("antenna", "⚠️ AntennaModule: Mensagem de modo browser visível (sem licença)")

    def _sync_power_spinbox_to_license(self):
        """Atualiza o limite visual do Spinbox de potência conforme a licença ativa."""
        try:
            if not hasattr(self, "power_entry"):
                return

            self.power_entry.config(from_=self.min_power_lic, to=self.max_power_lic)

            # Mantém o valor atual dentro do range da licença.
            min_p = int(self.min_power_lic)
            max_p = int(self.max_power_lic)
            current = int(self.power_var.get())
            current = max(min_p, min(current, max_p))

            # Alinha ao step usado no Spinbox (increment=5 no construtor).
            step = 5
            if step > 0:
                steps = round((current - min_p) / step)
                current = min_p + steps * step
                current = max(min_p, min(current, max_p))

            self.power_var.set(current)
        except Exception as e:
            fc_log("antenna", f"⚠️ AntennaModule: Falha ao sincronizar Spinbox de potência: {e}")

    def update_license_status(self, new_license_status):
        """CORREÇÃO: Atualiza o status da licença e os limites de frequência"""
        try:
            fc_log("antenna", f"🔔 AntennaModule: Atualizando status da licença de {self.license_limits.get('is_licensed', False)} para {new_license_status}")
            
            # Atualiza os limites da licença se app_shell estiver disponível
            if new_license_status and self.app_shell:
                try:
                    # Verifica se há licença específica para Antenna Check
                    valid_lics = ["AntennaCheck", "FastChecker"]
                    license_limits = self.app_shell._calculate_license_limits(valid_lics)
                    if license_limits.get('is_licensed', False):
                        self.license_limits = license_limits
                        fc_log("antenna", f"✅ AntennaModule: Licença válida confirmada: {license_limits.get('license_name', 'N/A')}")
                    else:
                        fc_log("antenna", "⚠️ AntennaModule: AppShell diz que há licença, mas não é válida para Antenna Check")
                        new_license_status = False
                except Exception as e:
                    fc_log("antenna", f"⚠️ AntennaModule: Erro ao verificar licença específica: {e}")
                    new_license_status = False
            
            # Atualiza o status da licença
            self.license_limits['is_licensed'] = new_license_status
            
            # CORREÇÃO CRÍTICA: Atualiza as variáveis de limite de frequência
            self.min_freq_lic = self.license_limits.get('min_freq', 800)
            self.max_freq_lic = self.license_limits.get('max_freq', 1000)
            self.min_power_lic = self.license_limits.get('min_power', 5)
            self.max_power_lic = self.license_limits.get('max_power', 25)
            
            fc_log("antenna", f"✅ AntennaModule: Limites atualizados - Freq: {self.min_freq_lic}-{self.max_freq_lic}MHz, Power: {self.min_power_lic}-{self.max_power_lic}dBm")
            if self.license_limits.get('excluded_ranges'):
                fc_log("antenna", f"✅ AntennaModule: Faixas excluídas: {self.license_limits.get('excluded_ranges')}")
            
            # CORREÇÃO CRÍTICA: sincroniza o limite visual do Spinbox de potência.
            self._sync_power_spinbox_to_license()
            
            # Atualiza a interface
            self._update_ui_for_license_status()
            
            # NOVO: Atualiza a mensagem informativa sobre faixas excluídas
            self._update_license_info_message()
            
            fc_log("antenna", f"✅ AntennaModule: Status da licença atualizado para: {new_license_status}")
            
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao atualizar status da licença no AntennaModule: {e}")

    def update_ui_state(self, is_running):
        normal_state = 'normal'; disabled_state = 'disabled'
        current_state = disabled_state if is_running else normal_state
        
        # CORREÇÃO: Usa license_limits padronizado
        is_licensed = self.license_limits.get('is_licensed', False)
        
        # Um único botão: Testar ↔ Parar
        if is_running:
            self.start_button.config(text=self._t('antenna.stop', 'Parar'), command=self.interrupt_scan, state='normal' if is_licensed else 'disabled')
        else:
            self.start_button.config(text=self._t('antenna.test', 'Testar'), command=self.start_scan, state='normal' if is_licensed else 'disabled')
        
        # Botão de limpar sempre habilitado
        self.clear_button.config(state='normal')
        
        # Controles do histórico (se existirem)
        if hasattr(self, 'delete_selected_button'):
            # Botão de histórico é ação de browser: sempre habilitado (exceto se preferir travar durante execução)
            self.delete_selected_button.config(state='normal')
        
        # Botões de salvar e importar (se existirem)
        if hasattr(self, 'save_selected_button'):
            # Ação de browser: sempre habilitado quando não está rodando (independente de seleção/licença)
            self.save_selected_button.config(state='normal' if not is_running else 'disabled')
                
        if hasattr(self, 'import_tests_button'):
            # Ação de browser: importar sempre permitido quando não está rodando
            self.import_tests_button.config(state='normal' if not is_running else 'disabled')
        
        # Botão de gerar relatório PDF (se existir)
        if hasattr(self, 'generate_pdf_button'):
            # Ação de browser: sempre habilitado quando não está rodando
            self.generate_pdf_button.config(state='normal' if not is_running else 'disabled')
        
        # CORREÇÃO: Controle baseado na licença E no estado do teste
        if is_licensed:
            # Com licença: campos habilitados baseado no estado do teste
            for widget in [self.desc_entry, self.power_entry, self.freq_step_menu, self.freq_min_entry, self.freq_max_entry, self.plot_type_menu]:
                widget.config(state=current_state)
            # Esconde mensagem de modo browser quando há licença
            if hasattr(self, 'browser_frame'):
                self.browser_frame.pack_forget()
        else:
            # Sem licença: permitir tarefas de browser (edição de campos), bloquear apenas ações de reader já controladas acima
            for widget in [self.desc_entry, self.power_entry, self.freq_step_menu, self.freq_min_entry, self.freq_max_entry, self.plot_type_menu]:
                widget.config(state=normal_state if not is_running else disabled_state)
            # Mostra mensagem de modo browser quando não há licença
            if hasattr(self, 'browser_frame'):
                self.browser_frame.pack(fill="x", pady=(5, 5), padx=5)
        

    
    def start_scan(self):
        if self.demo_mode:
            messagebox.showinfo(
                self._t('antenna.demo_mode'),
                self._t('antenna.demo_mode_msg'))
            return
        
        # CORREÇÃO CRÍTICA: Força atualização dos limites da licença antes de validar
        if self.app_shell and hasattr(self.app_shell, '_calculate_license_limits'):
            try:
                valid_lics = ["AntennaCheck", "FastChecker"]
                license_limits = self.app_shell._calculate_license_limits(valid_lics)
                if license_limits.get('is_licensed', False):
                    self.license_limits = license_limits
                    # ATUALIZA as variáveis de limite de frequência
                    self.min_freq_lic = license_limits.get('min_freq', 800)
                    self.max_freq_lic = license_limits.get('max_freq', 1000)
                    self.min_power_lic = license_limits.get('min_power', 5)
                    self.max_power_lic = license_limits.get('max_power', 25)
                    fc_log("antenna", f"🔧 AntennaCheck: Limites atualizados em tempo real - Freq: {self.min_freq_lic}-{self.max_freq_lic}MHz, Power: {self.min_power_lic}-{self.max_power_lic}dBm")
                    if license_limits.get('excluded_ranges'):
                        fc_log("antenna", f"🔧 AntennaCheck: Faixas excluídas atualizadas: {license_limits.get('excluded_ranges')}")
                    
                    # Garante que o Spinbox reflita imediatamente os limites recalculados.
                    self._sync_power_spinbox_to_license()
            except Exception as e:
                fc_log("antenna", f"⚠️ AntennaCheck: Erro ao atualizar limites em tempo real: {e}")
        
        # CORREÇÃO: Usa license_limits padronizado
        is_licensed = self.license_limits.get('is_licensed', False)
        if not is_licensed:
            messagebox.showwarning(
                self._t('antenna.invalid_license'),
                self._t('antenna.test_disabled'),
                parent=self)
            return
        
        # Validação de nome duplicado
        test_name = self.description_var.get().strip()
        if not test_name:
            messagebox.showerror(
                self._t('antenna.name_empty'),
                self._t('antenna.name_empty_msg'),
                parent=self)
            return
        
        # Verifica se o nome já existe no histórico
        if self.check_duplicate_test_name(test_name):
            messagebox.showerror(
                self._t('antenna.name_duplicate'),
                self._t('antenna.name_duplicate_msg', name=test_name),
                parent=self)
            return
        
        try:
            freq_min = float(self.freq_min_var.get()); freq_max = float(self.freq_max_var.get())
            fc_log("antenna", f"🔍 AntennaCheck: Validando frequência {freq_min}-{freq_max} MHz")
            
            # CORREÇÃO: Validação contra múltiplas faixas de frequência
            freq_ranges = self.license_limits.get('freq_ranges', [])
            excluded_ranges = self.license_limits.get('excluded_ranges', [])
            
            if freq_ranges:
                # CORREÇÃO: Permite configuração da faixa completa, mesmo que sobreponha faixas excluídas
                # O sistema irá pular automaticamente as faixas excluídas durante o teste
                freq_valid = False
                valid_range = None
                
                for range_min, range_max in freq_ranges:
                    if range_min <= freq_min < freq_max <= range_max:
                        freq_valid = True
                        valid_range = (range_min, range_max)
                        fc_log("antenna", f"✅ AntennaCheck: Frequência {freq_min}-{freq_max} MHz ACEITA na faixa {valid_range[0]}-{valid_range[1]} MHz")
                        if excluded_ranges:
                            excluded_str = " e ".join([f"{r[0]}-{r[1]} MHz" for r in excluded_ranges])
                            fc_log("antenna", f"ℹ️ AntennaCheck: Durante o teste, frequências nas faixas excluídas ({excluded_str}) serão puladas automaticamente")
                        break
                
                if not freq_valid:
                    ranges_str = " ou ".join([f"{r[0]}-{r[1]} MHz" for r in freq_ranges])
                    fc_log("antenna", f"❌ AntennaCheck: Frequência {freq_min}-{freq_max} MHz REJEITADA - fora das faixas permitidas")
                    messagebox.showerror(
                        self._t('antenna.freq_outside_ranges'),
                        self._t(
                            'antenna.freq_outside_ranges_msg',
                            freq_min=freq_min,
                            freq_max=freq_max,
                            ranges=ranges_str),
                        parent=self)
                    return
            else:
                # Fallback para validação simples
                if not (self.min_freq_lic <= freq_min < freq_max <= self.max_freq_lic):
                    fc_log("antenna", f"❌ AntennaCheck: Frequência {freq_min}-{freq_max} MHz REJEITADA - fora dos limites {self.min_freq_lic}-{self.max_freq_lic} MHz")
                    messagebox.showerror(
                        self._t('antenna.freq_outside_range'),
                        self._t(
                            'antenna.freq_outside_range_msg',
                            freq_min=freq_min,
                            freq_max=freq_max,
                            min_freq=self.min_freq_lic,
                            max_freq=self.max_freq_lic),
                        parent=self)
                    return
                else:
                    fc_log("antenna", f"✅ AntennaCheck: Frequência {freq_min}-{freq_max} MHz ACEITA - dentro dos limites {self.min_freq_lic}-{self.max_freq_lic} MHz")
        except ValueError: 
            fc_log("antenna", f"❌ AntennaCheck: Erro na conversão de frequência: {self.freq_min_var.get()}, {self.freq_max_var.get()}")
            messagebox.showerror(
                self._t('antenna.validation_error'),
                self._t('antenna.invalid_freq_values'),
                parent=self)
            return
        
        # CORREÇÃO CRÍTICA: Validação da potência contra limites da licença
        try:
            power = float(self.power_var.get())
            fc_log("antenna", f"🔍 AntennaCheck: Validando potência {power} dBm contra limites {self.min_power_lic}-{self.max_power_lic} dBm")
            
            if not (self.min_power_lic <= power <= self.max_power_lic):
                fc_log("antenna", f"❌ AntennaCheck: Potência {power} dBm REJEITADA - fora dos limites {self.min_power_lic}-{self.max_power_lic} dBm")
                messagebox.showerror(
                    self._t('antenna.power_outside_range'),
                    self._t(
                        'antenna.power_outside_range_msg',
                        power=power,
                        min_power=self.min_power_lic,
                        max_power=self.max_power_lic),
                    parent=self)
                return
            else:
                fc_log("antenna", f"✅ AntennaCheck: Potência {power} dBm ACEITA - dentro dos limites {self.min_power_lic}-{self.max_power_lic} dBm")
        except ValueError: 
            fc_log("antenna", f"❌ AntennaCheck: Erro na conversão de potência: {self.power_var.get()}")
            messagebox.showerror(
                self._t('antenna.validation_error'),
                self._t('antenna.invalid_power_value'),
                parent=self)
            return
        
        self.ax.set_xlim(freq_min, freq_max)
        self.test_is_running = True
        if self.app_shell: self.app_shell.set_test_running(True, "Antenna Check")
        self.update_ui_state(True)
        # Importante: NÃO sobrescrever o atributo interno do Tkinter `_root`.
        # O Tkinter usa um método interno `_root()`; ao sobrescrever com um objeto Tk
        # a segunda execução quebra com `TypeError: 'Tk' object is not callable`.
        self._antenna_root = self.winfo_toplevel()
        # Reset completo das flags de proteção para cada nova tentativa (popup pode aparecer de novo)
        self._stopped_by_protection = False
        self._protection_requested = False
        self._protection_popup_shown = False
        self._protection_message = None
        self._open_failed_reason = None
        self.worker_thread = threading.Thread(target=self.worker_scan, args=(freq_min, freq_max), daemon=True)
        self.worker_thread.start()
        self._protection_poll()

    def _protection_poll(self):
        """Poller agendado na janela principal (root): se o worker pediu popup, mostra messagebox (igual a Nome Duplicado)."""
        if getattr(self, '_protection_requested', False) and not getattr(self, '_protection_popup_shown', True):
            self._protection_popup_shown = True
            self._protection_requested = False
            root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
            if getattr(self, '_protection_poll_id', None) and root:
                try:
                    root.after_cancel(self._protection_poll_id)
                except Exception:
                    pass
                self._protection_poll_id = None
            # Mensagem específica de bloqueio durante leitura ou padrão
            custom = getattr(self, '_protection_message', None)
            if custom and len(custom) >= 2:
                title, msg = custom[0], custom[1]
                messagebox.showwarning(title, msg, parent=self)
                self._protection_message = None
            else:
                title = self._t('antenna.protection_activated', 'Proteção de segurança do reader')
                msg = self._t('antenna.protection_popup_ok_msg', 'Proteção de segurança do reader acionada. Clique em OK para parar o teste e reestabelecer a conexão.')
                messagebox.showinfo(title, msg, parent=self)
            self._on_protection_popup_ok()
            return
        if getattr(self, 'test_is_running', False):
            root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
            if root:
                self._protection_poll_id = root.after(200, self._protection_poll)

    def interrupt_scan(self):
        # Se há popup de proteção visível, fechar e reestabelecer conexão com o reader
        if getattr(self, '_protection_popup', None):
            try:
                self._protection_popup.destroy()
            except Exception:
                pass
            self._protection_popup = None
            self._stopped_by_protection = False
            # Close em background: NUNCA chamar DLL no thread principal (trava se reader em proteção)
            if rfid_sdk:
                port = self.com_port
                def _close():
                    try:
                        rfid_sdk.UHF_RFID_Close(port)
                    except Exception:
                        pass
                threading.Thread(target=_close, daemon=True).start()
            if self.app_shell:
                self.app_shell.set_test_running(False, "Antenna Check")
                if hasattr(self.app_shell, 'perform_strong_reader_reset_async'):
                    self.after(300, lambda: self.app_shell.perform_strong_reader_reset_async() if self.app_shell else None)
            self.test_is_running = False
            self.update_ui_state(False)
            return
        if self.test_is_running:
            self.test_is_running = False
            root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
            if getattr(self, '_protection_poll_id', None) and root:
                try:
                    root.after_cancel(self._protection_poll_id)
                except Exception:
                    pass
                self._protection_poll_id = None
            if self.app_shell:
                self.app_shell.set_test_running(False, "Antenna Check")
            self.update_ui_state(False)

    def _on_protection_popup_ok(self):
        """Chamado ao clicar OK no popup de proteção: para o teste, reestabelece a conexão e reseta flags para a próxima tentativa (loop: testar → proteger → liberar → testar)."""
        # Manter _protection_popup_shown = True para que _on_scan_finished (se rodar depois) não mostre o popup de novo; só resetamos em start_scan.
        self._protection_requested = False
        self._protection_message = None
        self._stopped_by_protection = False
        root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
        if getattr(self, '_protection_poll_id', None) and root:
            try:
                root.after_cancel(self._protection_poll_id)
            except Exception:
                pass
            self._protection_poll_id = None
        if getattr(self, '_protection_popup', None):
            try:
                self._protection_popup.destroy()
            except Exception:
                pass
            self._protection_popup = None
        if rfid_sdk:
            port = self.com_port
            def _close():
                try:
                    rfid_sdk.UHF_RFID_Close(port)
                except Exception:
                    pass
            threading.Thread(target=_close, daemon=True).start()
        if self.app_shell:
            self.app_shell.set_test_running(False, "Antenna Check")
            if hasattr(self.app_shell, 'perform_strong_reader_reset_async'):
                self.after(300, lambda: self.app_shell.perform_strong_reader_reset_async() if self.app_shell else None)
        self.test_is_running = False
        self.update_ui_state(False)
        # Voltar o gráfico à condição inicial (sem curva parcial do teste interrompido); permite novo teste no loop.
        try:
            self.after(150, self.update_plot)
        except Exception:
            pass

    def _on_scan_error(self, error_msg):
        """Reset de estado e UI após exceção durante a varredura; libera botão Testar."""
        self.test_is_running = False
        self._protection_popup_shown = False
        self._protection_requested = False
        root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
        if getattr(self, '_protection_poll_id', None) and root:
            try:
                root.after_cancel(self._protection_poll_id)
            except Exception:
                pass
            self._protection_poll_id = None
        if self.app_shell:
            self.app_shell.set_test_running(False, "Antenna Check")
        self.update_ui_state(False)
        messagebox.showerror(
            self._t('antenna.scan_error'),
            self._t('antenna.scan_error_msg', error=error_msg),
            parent=self)

    def _on_open_failed(self):
        """Reset de estado e UI após falha na abertura do reader; libera botão Testar para nova tentativa."""
        self.test_is_running = False
        self._protection_popup_shown = False
        self._protection_requested = False
        self._protection_message = None
        root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
        if getattr(self, '_protection_poll_id', None) and root:
            try:
                root.after_cancel(self._protection_poll_id)
            except Exception:
                pass
            self._protection_poll_id = None
        if self.app_shell:
            self.app_shell.set_test_running(False, "Antenna Check")
        self.update_ui_state(False)
        reason = getattr(self, '_open_failed_reason', "hardware")
        if reason == "dll":
            messagebox.showerror(
                self._t('antenna.error'),
                self._t('antenna.dll_not_loaded'),
                parent=self)
        else:
            messagebox.showerror(
                self._t('antenna.hardware_error_title'),
                self._t('antenna.hardware_error_reader_msg'),
                parent=self)
        self._open_failed_reason = None

    def _show_protection_popup(self, power_dbm):
        """Popup modal: usa mensagem de bloqueio durante leitura se definida, senão mensagem padrão."""
        custom = getattr(self, '_protection_message', None)
        if custom and len(custom) >= 2:
            title, msg = custom[0], custom[1]
            messagebox.showwarning(title, msg, parent=self)
            self._protection_message = None
        else:
            title = self._t('antenna.protection_activated', 'Proteção de segurança do reader')
            msg = self._t('antenna.protection_popup_ok_msg', 'Proteção de segurança do reader acionada. Clique em OK para parar o teste e reestabelecer a conexão.')
            messagebox.showinfo(title, msg, parent=self)
        self._on_protection_popup_ok()

    def worker_scan(self, freq_min, freq_max):
        # Cor do teste ao vivo: próxima da paleta (histórico usa ID do banco)
        _palette = ["#1f77b4", "#d62728", "#2ca02c", "#ff7f0e", "#9467bd", "#8c564b", "#e377c2"]
        _idx = len(getattr(self, "historical_plots", {}) or {})
        live_color = _palette[_idx % len(_palette)]
        self.current_test_run = {
            "id": f"live_{time.time()}", "description": self.description_var.get(), "power": self.power_var.get(),
            "data_points": {}, "color": live_color
        }
        try:
            # Passo B: Verificação de retorno na abertura do Reader (DLL retorna != 0 se bloqueado/desconectado)
            if rfid_sdk is None:
                self.test_is_running = False
                self._open_failed_reason = "dll"
                self.after(0, self._on_open_failed)
                return
            result = rfid_sdk.UHF_RFID_Open(ctypes.c_ubyte(self.com_port), BAUD_RATE)
            if result != 0:
                self.test_is_running = False
                self._open_failed_reason = "hardware"
                port = self.com_port
                def _force_close():
                    try:
                        if rfid_sdk:
                            rfid_sdk.UHF_RFID_Close(ctypes.c_ubyte(port))
                    except Exception:
                        pass
                threading.Thread(target=_force_close, daemon=True).start()
                self.after(0, self._on_open_failed)
                return
            dummy_buffer, dummy_len = ctypes.create_string_buffer(256), ctypes.c_uint(0)
            power_data = bytes([0,0]) + int(self.current_test_run['power'] * 100).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))
            time.sleep(0.1)

            current_freq = Decimal(str(freq_min))
            while current_freq <= Decimal(str(freq_max)) and self.test_is_running:
                # CORREÇÃO CRÍTICA: Verifica se a frequência atual está em uma faixa permitida pela licença
                freq_valid = False
                freq_ranges = self.license_limits.get('freq_ranges', [])
                excluded_ranges = self.license_limits.get('excluded_ranges', [])
                fc_log("antenna", f"🔍 AntennaCheck: Verificando frequência {current_freq} MHz contra freq_ranges: {freq_ranges}, excluded_ranges: {excluded_ranges}")
                
                if freq_ranges:
                    # Verifica se a frequência atual está em alguma faixa permitida
                    for range_min, range_max in freq_ranges:
                        if range_min <= float(current_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 <= float(current_freq) <= excl_max:
                                    in_excluded = True
                                    fc_log("antenna", f"⏭️ AntennaCheck: Frequência {current_freq} MHz está na faixa excluída {excl_min}-{excl_max} MHz")
                                    break
                            
                            if not in_excluded:
                                freq_valid = True
                                fc_log("antenna", f"✅ AntennaCheck: Frequência {current_freq} MHz ACEITA - faixa permitida {range_min}-{range_max} MHz")
                                break
                            else:
                                fc_log("antenna", f"❌ AntennaCheck: Frequência {current_freq} MHz REJEITADA - faixa excluída")
                else:
                    # Fallback para validação simples
                    min_freq_lic = self.license_limits.get('min_freq', 800)
                    max_freq_lic = self.license_limits.get('max_freq', 1000)
                    freq_valid = (min_freq_lic <= float(current_freq) <= max_freq_lic)
                
                if freq_valid:
                    # Frequência permitida - faz a medição normalmente (GET_PORT_LOSS com timeout para não travar se reader em proteção)
                    readings = []
                    _get_port_loss_timeout_s = 5.0
                    for _ in range(3):
                        if not self.test_is_running: break
                        freq_data = (1).to_bytes(1, 'big') + int(current_freq * 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))
                        time.sleep(0.05)
                        result_holder = []
                        def _do_get_port_loss():
                            ob = ctypes.create_string_buffer(64)
                            ol = ctypes.c_uint(0)
                            s = rfid_sdk.UHF_RFID_Set(RFID_CMD_GET_PORT_LOSS, b'', 0, ob, ctypes.byref(ol))
                            result_holder.append((s, bytes(ob.raw[:ol.value]) if ol.value else b'', ol.value))
                        thr = threading.Thread(target=_do_get_port_loss, daemon=True)
                        thr.start()
                        thr.join(timeout=_get_port_loss_timeout_s)
                        if thr.is_alive():
                            self._stopped_by_protection = True
                            self._protection_power_dbm = 10.0
                            self._protection_requested = True
                            self._protection_message = (
                                "Bloqueio de Segurança",
                                "A leitura foi interrompida: O reader acionou um bloqueio de segurança (Reflexão Alta/Superaquecimento)."
                            )
                            self.test_is_running = False
                            break
                        if not result_holder:
                            continue
                        status, raw_buf, out_len = result_holder[0]
                        # Passo C: retorno da DLL != 0 durante leitura = bloqueio de segurança
                        if status != 0:
                            self._stopped_by_protection = True
                            self._protection_power_dbm = 10.0
                            self._protection_requested = True
                            self._protection_message = (
                                "Bloqueio de Segurança",
                                "A leitura foi interrompida: O reader acionou um bloqueio de segurança (Reflexão Alta/Superaquecimento)."
                            )
                            self.test_is_running = False
                            break
                        if status == 0 and out_len >= 3:
                            adc_value = struct.unpack('>H', raw_buf[1:3])[0]
                            reflected_power_dbm = -25 + next((i for i, val in enumerate(RxAdcTable) if adc_value <= val), len(RxAdcTable) - 1)
                            if reflected_power_dbm > 10 and self.protection_enabled:
                                self._stopped_by_protection = True
                                self._protection_power_dbm = reflected_power_dbm
                                self._protection_requested = True
                                self._protection_message = (
                                    "Bloqueio de Segurança",
                                    "A leitura foi interrompida: O reader acionou um bloqueio de segurança (Reflexão Alta/Superaquecimento)."
                                )
                                self.test_is_running = False
                                break
                            readings.append(reflected_power_dbm)
                    if not self.test_is_running: break
                    if readings:
                        self.current_test_run['data_points'][current_freq] = sum(readings) / len(readings)
                        self.after(0, self.update_plot)
                        fc_log("antenna", f"✅ AntennaCheck: Frequência {current_freq} MHz testada (faixa permitida)")
                else:
                    # Frequência na faixa excluída - pula sem fazer medição
                    fc_log("antenna", f"⏭️ AntennaCheck: Frequência {current_freq} MHz pulada (faixa excluída pela licença)")
                    # NÃO adiciona ponto excluído aos data_points - simplesmente pula
                    # Isso evita plotar pontos na faixa excluída
                
                current_freq += Decimal(str(self.freq_step_var.get()))
        except Exception as e:
            self.test_is_running = False
            err_msg = str(e)
            self.after(0, lambda m=err_msg: self._on_scan_error(m))
        finally:
            self.test_is_running = False
            stopped_by_protection = getattr(self, '_stopped_by_protection', False)
            run_to_save = self.current_test_run
            self.current_test_run = None
            port_to_close = self.com_port
            def _close_port_background():
                try:
                    if rfid_sdk:
                        rfid_sdk.UHF_RFID_Close(port_to_close)
                except Exception:
                    pass
            threading.Thread(target=_close_port_background, daemon=True).start()
            self.after(0, lambda sp=stopped_by_protection, run=run_to_save: self._on_scan_finished(sp, run))
    
    def _on_scan_finished(self, stopped_by_protection, run_to_save):
        """Chamado na thread principal quando o worker termina. Se proteção: mostra o popup uma vez; senão já foi mostrado (poller/OK), só atualiza estado."""
        if stopped_by_protection:
            if not getattr(self, '_protection_popup_shown', True):
                self._protection_popup_shown = True
                self._show_protection_popup(getattr(self, '_protection_power_dbm', 10.0))
            else:
                # Popup já foi mostrado e OK dado (ex.: pelo poller); garantir estado e parar o poller.
                root = getattr(self, '_antenna_root', None) or self.winfo_toplevel()
                if getattr(self, '_protection_poll_id', None) and root:
                    try:
                        root.after_cancel(self._protection_poll_id)
                    except Exception:
                        pass
                    self._protection_poll_id = None
                if self.app_shell:
                    self.app_shell.set_test_running(False, "Antenna Check")
                self.update_ui_state(False)
            return
        if self.app_shell:
            self.app_shell.set_test_running(False, "Antenna Check")
        self.update_ui_state(False)
        if run_to_save and run_to_save.get('data_points'):
            self._save_test_to_history_async(run_to_save)

    def setup_plot(self):
        self.ax.clear()
        self.ax.set_xlabel(self._t('antenna.x_axis_label', 'Frequência (MHz)'))
        self.ax.grid(True, which='both', linestyle='--', linewidth=0.5)
        self.ax.set_xlim(float(self.freq_min_var.get()), float(self.freq_max_var.get()))
        
        # Ajusta configurações baseado no tipo de plot e curva
        if self._is_plot_vswr():
            self.ax.set_ylabel(self._t('antenna.y_axis_vswr', 'VSWR'))
            self.ax.set_ylim(1.0, 3.5)
        else:
            self.ax.set_ylabel(self._t('antenna.y_axis_return_loss', 'Proteção do Modulo'))
            self.ax.set_ylim(-30, 15)
            self.ax.axhline(y=10, color='red', linestyle='--', label='Limite dBm')
        
        self.annot = self.ax.annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points",
                                       bbox=dict(boxstyle="round,pad=0.5", 
                                                facecolor="lightblue", 
                                                alpha=0.9,
                                                edgecolor="navy",
                                                linewidth=1),
                                       arrowprops=dict(arrowstyle="->", 
                                                      connectionstyle="arc3,rad=0",
                                                      color="navy",
                                                      lw=1),
                                       fontsize=9, 
                                       ha='left',
                                       va='bottom',
                                       wrap=True)
        self.annot.set_visible(False)
        self.ax.axvspan(865, 868, color='lightblue', alpha=0.3)
        self.ax.axvspan(902, 907, color='lightblue', alpha=0.3)
        self.ax.axvspan(915.5, 928, color='lightblue', alpha=0.3)

    def setup_interactivity(self):
        self.canvas.mpl_connect('motion_notify_event', self._on_hover)

    def _on_hover(self, event):
        if event.inaxes != self.ax:
            if self.annot and self.annot.get_visible(): self.annot.set_visible(False); self.canvas.draw_idle()
            return
        
        # Verifica se está sobre um marcador (ponto original) ou linha suavizada
        for line in self.ax.lines:
            cont, ind = line.contains(event)
            if cont:
                pos = line.get_xydata()[ind["ind"][0]]
                x, y = pos[0], pos[1]
                
                # Unidade só no modo VSWR (Proteção do Módulo: valores sem sufixo de unidade)
                unit = "VSWR" if self._is_plot_vswr() else ""
                
                # Adiciona informação sobre o tipo de curva
                curve_type = self.curve_type_var.get()
                suffix_smooth = " (Suavizado)" if curve_type == "Curva Suave" else ""
                if unit:
                    tooltip_text = f"Freq {x:.1f} -> {y:.2f} {unit}{suffix_smooth}"
                else:
                    tooltip_text = f"Freq {x:.1f} -> {y:.2f}{suffix_smooth}"
                
                self._position_tooltip_smartly(event, x, y, tooltip_text)
                return
        
        if self.annot and self.annot.get_visible(): 
            self.annot.set_visible(False)
            self.canvas.draw_idle()

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

    def update_plot(self):
        if getattr(self, '_stopped_by_protection', False):
            return
        self.setup_plot()
        # CORREÇÃO: Cores com alto contraste - sem cores claras
        colors = ["#1f77b4", "#d62728", "#2ca02c", "#ff7f0e", "#9467bd", "#8c564b", "#e377c2"]
        plot_vswr = self._is_plot_vswr()
        curve_type = self.curve_type_var.get()
        fc_log("antenna", f"🎯 Modo de curva selecionado: {curve_type}")
        
        plot_count = 0
        # Ordena por ID de teste para ordem estável; rótulo continua sendo o nome
        def _hist_sort_key(item):
            k = item[0]
            try:
                return (0, int(k))
            except (TypeError, ValueError):
                return (1, str(k))

        for plot_key, test_data in sorted(self.historical_plots.items(), key=_hist_sort_key):
            disp_name = test_data.get("name", str(plot_key))
            try:
                tid_int = int(plot_key)
                color = colors[tid_int % len(colors)]
            except (TypeError, ValueError):
                color = colors[plot_count % len(colors)]
            p_inc_dbm = test_data['power']
            sorted_points = sorted(test_data["data_points"].items(), key=lambda item: item[0])
            if sorted_points:
                # CORREÇÃO: Separa pontos válidos e pontos excluídos (None)
                x_valid = []
                y_raw_valid = []
                x_excluded = []
                
                for freq, value in sorted_points:
                    if value is not None:
                        # Ponto válido - faixa permitida
                        x_valid.append(float(freq))
                        y_raw_valid.append(float(value))
                    else:
                        # Ponto excluído - faixa proibida
                        x_excluded.append(float(freq))
                
                # Plota pontos válidos normalmente
                if x_valid:
                    y = [self.calculate_vswr_from_powers(p_inc_dbm, val) for val in y_raw_valid] if plot_vswr else y_raw_valid
                    
                    # NOVO: Separa pontos em segmentos contínuos (evita ligação através de faixas excluídas)
                    segments = self._create_continuous_segments(x_valid, y)
                    
                    if curve_type == "Curva Suave" and SCIPY_AVAILABLE and len(x_valid) >= 3:
                        # Aplica suavização para cada segmento separadamente
                        try:
                            fc_log("antenna", f"🔧 Aplicando suavização para {disp_name} com {len(x_valid)} pontos em {len(segments)} segmentos")
                            for i, (x_seg, y_seg) in enumerate(segments):
                                if len(x_seg) >= 3:  # Só suaviza se tiver pelo menos 3 pontos
                                    spline = UnivariateSpline(x_seg, y_seg, s=0, k=min(3, len(x_seg)-1))
                                    x_smooth = np.linspace(min(x_seg), max(x_seg), max(50, len(x_seg)*10))
                                    y_smooth = spline(x_smooth)
                                    # Linha suavizada para este segmento
                                    label = f"{disp_name} (Suave)" if i == 0 else None  # Só coloca label no primeiro segmento
                                    self.ax.plot(x_smooth, y_smooth, '-', c=color, label=label, linewidth=2.5, alpha=0.9)
                                else:
                                    # Poucos pontos - plota como linha reta
                                    label = f"{disp_name} (Suave)" if i == 0 else None
                                    self.ax.plot(x_seg, y_seg, '-', c=color, label=label, linewidth=2.5, alpha=0.9)
                                # Marcadores nos pontos originais deste segmento
                                self.ax.plot(x_seg, y_seg, 'o', c=color, markersize=2, alpha=0.6, picker=5)
                        except Exception as e:
                            fc_log("antenna", f"⚠️ Erro na suavização: {e}")
                            # Fallback para plotagem normal por segmentos
                            for i, (x_seg, y_seg) in enumerate(segments):
                                label = disp_name if i == 0 else None
                                self.ax.plot(x_seg, y_seg, 'o-', c=color, label=label, markersize=2, linewidth=0.8, alpha=0.8)
                    else:
                        # Plotagem normal com marcadores por segmentos
                        fc_log("antenna", f"🔧 Plotagem pontual para {disp_name} com {len(x_valid)} pontos em {len(segments)} segmentos")
                        for i, (x_seg, y_seg) in enumerate(segments):
                            label = disp_name if i == 0 else None
                            self.ax.plot(x_seg, y_seg, 'o-', c=color, label=label, markersize=3, linewidth=1.2, alpha=0.8)
                
                plot_count += 1
        
        if self.current_test_run and self.current_test_run['data_points']:
            p_inc_dbm = self.current_test_run['power']
            sorted_points = sorted(self.current_test_run["data_points"].items(), key=lambda item: item[0])
            if sorted_points:
                # CORREÇÃO: Separa pontos válidos e pontos excluídos (None) para teste atual
                x_valid = []
                y_raw_valid = []
                x_excluded = []
                
                for freq, value in sorted_points:
                    if value is not None:
                        # Ponto válido - faixa permitida
                        x_valid.append(float(freq))
                        y_raw_valid.append(float(value))
                    else:
                        # Ponto excluído - faixa proibida
                        x_excluded.append(float(freq))
                
                # Plota pontos válidos normalmente
                if x_valid:
                    y = [self.calculate_vswr_from_powers(p_inc_dbm, val) for val in y_raw_valid] if plot_vswr else y_raw_valid
                    label = f"{self.current_test_run['description']} (Ao Vivo)"
                    
                    # NOVO: Separa pontos em segmentos contínuos (evita ligação através de faixas excluídas)
                    segments = self._create_continuous_segments(x_valid, y)
                    
                    if curve_type == "Curva Suave" and SCIPY_AVAILABLE and len(x_valid) >= 3:
                        # Aplica suavização ao teste atual para cada segmento separadamente
                        try:
                            fc_log("antenna", f"🔧 Aplicando suavização para teste atual com {len(x_valid)} pontos em {len(segments)} segmentos")
                            for i, (x_seg, y_seg) in enumerate(segments):
                                if len(x_seg) >= 3:  # Só suaviza se tiver pelo menos 3 pontos
                                    x_smooth = np.linspace(min(x_seg), max(x_seg), max(50, len(x_seg)*10))
                                    spline = UnivariateSpline(x_seg, y_seg, s=0, k=min(3, len(x_seg)-1))
                                    y_smooth = spline(x_smooth)
                                    # Linha suavizada para este segmento
                                    seg_label = f"{label} (Suave)" if i == 0 else None
                                    self.ax.plot(x_smooth, y_smooth, '-', c=self.current_test_run['color'], label=seg_label, linewidth=3, alpha=0.9)
                                else:
                                    # Poucos pontos - plota como linha reta
                                    seg_label = f"{label} (Suave)" if i == 0 else None
                                    self.ax.plot(x_seg, y_seg, '-', c=self.current_test_run['color'], label=seg_label, linewidth=3, alpha=0.9)
                                # Marcadores nos pontos originais deste segmento
                                self.ax.plot(x_seg, y_seg, 'o', c=self.current_test_run['color'], markersize=3, alpha=0.7, picker=5)
                        except Exception as e:
                            fc_log("antenna", f"⚠️ Erro na suavização do teste atual: {e}")
                            # Fallback para plotagem normal por segmentos
                            for i, (x_seg, y_seg) in enumerate(segments):
                                seg_label = label if i == 0 else None
                                self.ax.plot(x_seg, y_seg, 'o-', c=self.current_test_run['color'], label=seg_label, markersize=2, linewidth=0.8)
                    else:
                        # Plotagem normal com marcadores por segmentos
                        fc_log("antenna", f"🔧 Plotagem pontual para teste atual com {len(x_valid)} pontos em {len(segments)} segmentos")
                        for i, (x_seg, y_seg) in enumerate(segments):
                            seg_label = label if i == 0 else None
                            self.ax.plot(x_seg, y_seg, 'o-', c=self.current_test_run['color'], label=seg_label, markersize=3, linewidth=1.2)

        # CORREÇÃO: Adiciona indicação visual das faixas excluídas
        self._plot_excluded_frequency_ranges()

        self.ax.legend(bbox_to_anchor=(1.02, 1), loc='upper left', 
                      bbox_transform=self.ax.transAxes, frameon=True, 
                      borderpad=1.0, columnspacing=1.0, fontsize=9)
        self.canvas.draw_idle()

    def _create_continuous_segments(self, x_values, y_values):
        """
        NOVO: Cria segmentos contínuos de pontos, separando onde há gaps de frequência
        
        Args:
            x_values: Lista de frequências
            y_values: Lista de valores correspondentes
            
        Returns:
            Lista de tuplas (x_segment, y_segment) para cada segmento contínuo
        """
        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 = self.license_limits.get('excluded_ranges', [])
        # NOVO: Respeita mudanças entre faixas permitidas (freq_ranges) para nunca interligar segmentos
        freq_ranges = self.license_limits.get('freq_ranges', [])
        
        def _in_same_allowed_range(fa, fb):
            """Retorna True se as duas frequências estiverem na MESMA faixa permitida."""
            if not freq_ranges:
                return True
            same_bucket = None
            for (rmin, rmax) in freq_ranges:
                in_a = (rmin <= fa <= rmax)
                in_b = (rmin <= fb <= rmax)
                if in_a and in_b:
                    same_bucket = True
                    break
            return bool(same_bucket)
        
        for i in range(1, len(x_sorted)):
            prev_freq = x_sorted[i-1]
            curr_freq = x_sorted[i]
            
            # Verifica se há um gap significativo (≈ passo de frequência ou maior)
            freq_step = getattr(self, 'freq_step_var', None)
            if freq_step:
                step = freq_step.get()
            else:
                step = 1.0  # Fallback
            # Tolerância para erros de arredondamento em steps maiores (ex.: 5 MHz)
            gap_threshold = max(step + 1e-6, step * 1.05)
            
            # 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
                    break
            
            # Se há gap significativo, frequência excluída OU mudou de faixa permitida, inicia novo segmento
            crossed_allowed_bucket = not _in_same_allowed_range(prev_freq, curr_freq)
            if (curr_freq - prev_freq) >= gap_threshold or in_excluded or crossed_allowed_bucket:
                # Salva o segmento atual
                if len(current_segment_x) > 0:
                    segments.append((current_segment_x.copy(), current_segment_y.copy()))
                
                # Inicia novo segmento
                current_segment_x = [curr_freq]
                current_segment_y = [y_sorted[i]]
            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("antenna", f"🔍 AntennaCheck: Criados {len(segments)} segmentos contínuos")
        for i, (x_seg, y_seg) in enumerate(segments):
            fc_log("antenna", f"   Segmento {i+1}: {len(x_seg)} pontos, faixa {min(x_seg):.1f}-{max(x_seg):.1f} MHz")
        
        return segments

    def _plot_excluded_frequency_ranges(self):
        """Removido: não exibe mais faixa vermelha de restrição no gráfico."""
        return

    def calculate_vswr_from_powers(self, p_inc_dbm, p_ref_dbm):
        """Calcula VSWR a partir das potências incidente e refletida"""
        try:
            # Converte para float para evitar problemas de tipo
            p_inc = float(p_inc_dbm)
            p_ref = float(p_ref_dbm)
            
            # Calcula o Return Loss em dB (deve ser negativo)
            # Return Loss = -20 × log₁₀(|Γ|) onde |Γ| é o coeficiente de reflexão
            if p_ref >= p_inc:
                return 3.5  # VSWR alto quando potência refletida >= incidente
            
            # Calcula o coeficiente de reflexão a partir das potências
            # |Γ|² = P_ref / P_inc
            # Para potências em dBm, converte para mW primeiro
            p_inc_mw = 10**(p_inc / 10)
            p_ref_mw = 10**(p_ref / 10)
            
            reflection_coeff_squared = p_ref_mw / p_inc_mw
            reflection_coeff = reflection_coeff_squared ** 0.5
            
            # Calcula VSWR: VSWR = (1 + |Γ|) / (1 - |Γ|)
            if abs(reflection_coeff - 1.0) < 1e-10:
                return 3.5  # VSWR alto quando |Γ| ≈ 1
            else:
                vswr = (1 + reflection_coeff) / (1 - reflection_coeff)
                # Limita VSWR a um valor máximo plotável
                return min(vswr, 3.5)
                
        except Exception as e:
            fc_log("antenna", f"⚠️ Erro no cálculo de VSWR: {e}")
            return 3.5

    def on_plot_type_change(self, *args):
        self.update_plot_from_history()
    
    def on_curve_type_change(self, *args):
        # Atualiza o plot considerando dados históricos se disponíveis
        if hasattr(self, 'database') and self.database:
            self.update_plot_from_history()
        else:
            self.update_plot()
    
    def _refresh_history_ui(self):
        """Atualiza árvore e gráfico do histórico (só na thread principal)."""
        if not self.database:
            return
        try:
            self.load_history_to_tree()
            self.update_plot_from_history()
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao atualizar UI do histórico: {e}")

    def _save_test_to_history_async(self, test_data):
        """Salva o teste em background para não bloquear a UI (evita travamento pós-proteção)."""
        if not self.database:
            return
        # Cópia para o thread não alterar o dict original
        data = dict(test_data)
        if not data.get('data_points'):
            return

        def _worker():
            try:
                data['data_points'] = {str(k): v for k, v in data['data_points'].items()}
                data['show_in_graph'] = True
                if data.get('id', '').startswith('live_'):
                    del data['id']
                if self.database.add_test_to_history(data):
                    self.after(0, self._refresh_history_ui)
            except Exception as e:
                fc_log("antenna", f"❌ Erro ao salvar teste de antena no histórico: {e}")

        threading.Thread(target=_worker, daemon=True).start()

    def save_test_to_history(self, test_data):
        """Salva síncrono (para chamadas que já estão em contexto não bloqueante)."""
        if not self.database:
            return
        try:
            test_data['data_points'] = {str(k): v for k, v in test_data['data_points'].items()}
            test_data['show_in_graph'] = True
            if 'id' in test_data and 'live_' in test_data['id']:
                del test_data['id']
            if self.database.add_test_to_history(test_data):
                self.load_history_to_tree()
                self.update_plot_from_history()
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao salvar teste de antena no histórico: {e}")

    def load_history_to_tree(self):
        if not self.database: return
        for item in self.history_tree.get_children(): self.history_tree.delete(item)
        history = self.database.get_test_history()
        for test in reversed(history):
            data_points = test.get('data_points', {})
            freqs = [float(f) for f in data_points.keys()]
            vals = list(data_points.values())
            
            freq_range = f"{min(freqs):.1f} a {max(freqs):.1f}" if freqs else "N/A"
            best_val, best_freq, vswr_val = ("N/A", "N/A", "N/A")
            if vals:
                min_return_loss = min(vals)
                best_val = f"{min_return_loss:.2f}"
                best_freq = f"{freqs[vals.index(min_return_loss)]:.1f}"
                # Calcula VSWR usando a mesma função do gráfico
                if min_return_loss != float('inf'):
                    # Usa a mesma função calculate_vswr_from_powers do gráfico
                    # O valor armazenado é o Return Loss, e a potência incidente é a potência do teste
                    test_power = test.get('power', 25)  # Potência do teste (padrão 25 dBm)
                    vswr_calculated = self.calculate_vswr_from_powers(test_power, min_return_loss)
                    vswr_val = f"{vswr_calculated:.2f}"
                else:
                    vswr_val = "∞"

            self.history_tree.insert("", "end", values=(
                "☑" if test.get('show_in_graph') else "☐",
                test.get('id'),
                test.get('description'),
                test.get('power'),
                freq_range,
                best_val,
                vswr_val,
                best_freq,
                test.get('timestamp')
            ))
        self.update_history_stats()

    def update_history_stats(self):
        if not self.database: return
        stats = self.database.get_statistics()
        stats_text = f"Estatísticas: {stats['total_tests']} testes no histórico"
        
        # Atualizar apenas o label de estatísticas do histórico
        if hasattr(self, 'history_stats_label'):
            self.history_stats_label.config(text=stats_text)

    def on_history_tree_click(self, event):
        region = self.history_tree.identify("region", event.x, event.y)
        # Só aceita cliques na coluna do checkbox (coluna 1)
        if region == "cell":
            column_id = self.history_tree.identify_column(event.x)
            # Coluna 1 é o checkbox (Plot)
            if column_id == '#1':
                item = self.history_tree.identify_row(event.y)
                if not item: return
                test_id = int(self.history_tree.item(item)['values'][1])
                test_data = self.database.get_test_by_id(test_id)
                if test_data:
                    test_data['show_in_graph'] = not test_data.get('show_in_graph', False)
                    self.database.update_test_in_history(test_id, test_data)
                self.load_history_to_tree()
                self.update_plot_from_history()
                # Atualizar estado dos botões após mudança na seleção
                self.update_button_states()

    def update_button_states(self):
        """Atualiza o estado dos botões baseado na seleção atual"""
        if not hasattr(self, 'save_selected_button'):
            return
            
        # Verificar se está rodando teste
        is_running = getattr(self, 'test_is_running', False)
        
        # Atualizar botão de salvar: sempre habilitado quando não está rodando
        self.save_selected_button.config(state='normal' if not is_running else 'disabled')
            
        # Atualizar botão de importar
        if hasattr(self, 'import_tests_button'):
            if not is_running:
                self.import_tests_button.config(state='normal')
            else:
                self.import_tests_button.config(state='disabled')

    def check_duplicate_test_name(self, test_name):
        """
        Verifica se o nome do teste já existe no histórico
        
        Args:
            test_name: Nome do teste a verificar
            
        Returns:
            bool: True se o nome já existe, False caso contrário
        """
        if not DATABASE_AVAILABLE or not self.database:
            return False
        
        try:
            history = self.database.get_test_history()
            # Compara nomes normalizados (sem espaços extras, case-insensitive)
            test_name_normalized = test_name.strip().lower()
            existing_names = [test.get('description', '').strip().lower() for test in history]
            return test_name_normalized in existing_names
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao verificar nomes duplicados: {e}")
            return False

    def check_duplicate_test_name_excluding_current(self, test_name, current_test_id):
        """
        Verifica se o nome do teste já existe no histórico, excluindo o teste atual
        
        Args:
            test_name: Nome do teste a verificar
            current_test_id: ID do teste atual que está sendo editado
            
        Returns:
            bool: True se o nome já existe em outro teste, False caso contrário
        """
        if not DATABASE_AVAILABLE or not self.database:
            return False
        
        try:
            history = self.database.get_test_history()
            # NOVO: Compara nomes normalizados (sem espaços extras, case-insensitive)
            test_name_normalized = test_name.strip().lower()
            
            for test in history:
                if test.get('id') != current_test_id:  # Exclui o teste atual
                    existing_name = test.get('description', '').strip().lower()
                    if existing_name == test_name_normalized:
                        return True
            return False
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao verificar nomes duplicados: {e}")
            return False

    # --- NOVOS MÉTODOS PARA EDIÇÃO IN-LINE ---

    def on_history_tree_double_click(self, event):
        """Manipula o duplo-clique para edição in-line do nome 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.history_tree.identify("region", event.x, event.y)
        if region != "cell":
            return

        column_id = self.history_tree.identify_column(event.x)
        # As colunas visíveis são ("Plot", "Nome", ...), então "Nome" é a coluna #2
        if column_id != '#2':
            return
            
        item_id = self.history_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.history_tree.bbox(item_id, column_id)

        # Cria e posiciona o widget de edição
        current_name = self.history_tree.set(item_id, 'Nome')
        self._edit_var = tk.StringVar(value=current_name)
        self._edit_entry = ttk.Entry(self.history_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_name(item_id))
        self._edit_entry.bind("<FocusOut>", lambda e: self.save_edited_name(item_id))
        self._edit_entry.bind("<Escape>", lambda e: self.cancel_edit_name())

    def save_edited_name(self, item_id):
        """Salva o nome do teste que foi editado."""
        if not (hasattr(self, '_edit_entry') and self._edit_entry.winfo_exists()):
            return
            
        try:
            new_name = self._edit_var.get().strip()
            self.cancel_edit_name() # Destrói o widget de edição

            if not new_name:
                # O nome não pode ser vazio, idealmente, mostrar um aviso
                return

            # Obtém o ID do teste a partir dos valores da linha
            test_id = int(self.history_tree.item(item_id)['values'][1])

            if self.database:
                # NOVO: Verifica se o novo nome já existe (excluindo o teste atual)
                if self.check_duplicate_test_name_excluding_current(new_name, test_id):
                    messagebox.showerror(
                        self._t('antenna.name_duplicate'),
                        self._t('antenna.name_duplicate_msg', name=new_name),
                        parent=self)
                    return
                
                test_data = self.database.get_test_by_id(test_id)
                if test_data:
                    # O campo do nome neste módulo é 'description'
                    test_data['description'] = new_name
                    if self.database.update_test_in_history(test_id, test_data):
                        self.history_tree.set(item_id, 'Nome', new_name)
                        fc_log("antenna", f"✅ Nome do teste ID {test_id} atualizado para '{new_name}'")
                        # Atualiza o gráfico caso o teste esteja plotado
                        self.update_plot_from_history()
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao salvar nome editado: {e}")

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

    # --- FIM DOS MÉTODOS DE EDIÇÃO ---

    def update_plot_from_history(self):
        if not self.database: self.update_plot(); return
        self.historical_plots.clear()
        history = self.database.get_test_history()
        for test in history:
            if test.get('show_in_graph'):
                test_id = test.get('id')
                if test_id is None:
                    continue
                name = test.get('description', f'Teste_{test_id}')
                data_points = {Decimal(k): v for k, v in test.get('data_points', {}).items()}
                # Chave = ID do banco: cor e série estáveis ao renomear descrição
                self.historical_plots[test_id] = {
                    'name': name,
                    'power': test.get('power'),
                    'data_points': data_points,
                }
        self.update_plot()
        
    def clear_plot_and_selection(self):
        self.current_test_run = None
        self.historical_plots.clear()
        self.deselect_all_tests()

    def get_selected_test_ids_from_tree(self):
        if not hasattr(self, 'history_tree'): return []
        return [int(self.history_tree.item(item)['values'][1]) for item in self.history_tree.get_children() if self.history_tree.set(item, 'Plot') == "☑"]
    
    def select_all_tests(self):
        if not self.database: return
        for item in self.history_tree.get_children():
            test_id = int(self.history_tree.item(item)['values'][1])
            test_data = self.database.get_test_by_id(test_id)
            if test_data and not test_data.get('show_in_graph'):
                test_data['show_in_graph'] = True
                self.database.update_test_in_history(test_id, test_data)
        self.load_history_to_tree(); self.update_plot_from_history()
        self.update_button_states()

    def deselect_all_tests(self):
        if not self.database: return
        for item in self.history_tree.get_children():
            test_id = int(self.history_tree.item(item)['values'][1])
            test_data = self.database.get_test_by_id(test_id)
            if test_data and test_data.get('show_in_graph'):
                test_data['show_in_graph'] = False
                self.database.update_test_in_history(test_id, test_data)
        self.load_history_to_tree(); self.update_plot_from_history()
        self.update_button_states()


    def import_tests(self):
        if not self.database: return
        filepath = filedialog.askopenfilename(
            filetypes=[(self._t('export_popup.filetype_json', 'JSON files'), "*.json")],
            title=self._t('antenna.import_tests_dialog'),
            parent=self)
        if not filepath: return
        try:
            with open(filepath, "r", encoding='utf-8') as f: data = json.load(f)
            tests_to_import = data.get("test_data", [])
            if not tests_to_import:
                messagebox.showwarning(
                    self._t('antenna.no_tests'),
                    self._t('antenna.no_tests_msg'),
                    parent=self)
                return
            for test in tests_to_import:
                if 'id' in test: del test['id']
                test['show_in_graph'] = True
                self.database.add_test_to_history(test)
            self.load_history_to_tree(); self.update_plot_from_history()
            messagebox.showinfo(
                self._t('antenna.success'),
                self._t('antenna.tests_imported', count=len(tests_to_import)),
                parent=self)
        except Exception as e:
            messagebox.showerror(
                self._t('antenna.error'),
                self._t('antenna.import_failed', error=str(e)),
                parent=self)
    
    def save_selected_tests(self):
        """Salva os testes selecionados no histórico em um arquivo JSON"""
        if not self.database:
            messagebox.showwarning(
                self._t('antenna.error'),
                self._t('antenna.database_unavailable'),
                parent=self)
            return
            
        selected_ids = self.get_selected_test_ids_from_tree()
        if not selected_ids:
            messagebox.showwarning(
                self._t('antenna.nothing_selected'),
                self._t('antenna.select_to_save'),
                parent=self)
            return

        # Gerar nome padrão do arquivo com timestamp
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        default_filename = f"Ant_tests_{timestamp}.json"
        
        # Abrir diálogo para salvar arquivo
        filename = filedialog.asksaveasfilename(
            title=self._t('antenna.save_tests_dialog'),
            defaultextension=".json",
            filetypes=[
                (self._t('export_popup.filetype_json', 'JSON files'), "*.json"),
                (self._t('export_popup.filetype_all', 'All files'), "*.*"),
            ],
            initialfile=default_filename,
            parent=self
        )
        
        if not filename:
            return
            
        try:
            # Obter dados dos testes selecionados
            history = self.database.get_test_history()
            selected_tests = [test for test in history if test.get('id') in selected_ids]
            
            # Preparar dados para exportação
            export_data = {
                "export_info": {
                    "timestamp": datetime.now().isoformat(),
                    "module": "Antenna Check",
                    "version": "6.2",
                    "total_tests": len(selected_tests)
                },
                "tests": selected_tests
            }
            
            # Salvar arquivo
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(export_data, f, indent=2, ensure_ascii=False)
                
            messagebox.showinfo(
                self._t('antenna.success'),
                self._t('antenna.tests_saved', count=len(selected_tests), filename=filename),
                parent=self)
            
        except Exception as e:
            messagebox.showerror(
                self._t('antenna.error'),
                self._t('antenna.save_failed', error=str(e)),
                parent=self)

    def import_tests(self):
        """Importa testes de um arquivo JSON"""
        if not self.database:
            messagebox.showwarning(
                self._t('antenna.error'),
                self._t('antenna.database_unavailable'),
                parent=self)
            return
            
        # Abrir diálogo para selecionar arquivo
        filename = filedialog.askopenfilename(
            title=self._t('antenna.import_tests_title'),
            filetypes=[
                (self._t('export_popup.filetype_json', 'JSON files'), "*.json"),
                (self._t('export_popup.filetype_all', 'All files'), "*.*"),
            ],
            parent=self
        )
        
        if not filename:
            return
            
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                import_data = json.load(f)
            
            # Verificar estrutura do arquivo
            if 'tests' not in import_data:
                messagebox.showerror(
                    self._t('antenna.error'),
                    self._t('antenna.invalid_file_structure'),
                    parent=self)
                return
                
            tests_to_import = import_data['tests']
            if not tests_to_import:
                messagebox.showwarning(
                    self._t('antenna.empty_file'),
                    self._t('antenna.no_tests_in_file'),
                    parent=self)
                return
                
            # Confirmar importação
            result = messagebox.askyesno(
                self._t('antenna.confirm_import'),
                self._t(
                    'antenna.confirm_import_msg',
                    count=len(tests_to_import),
                    filename=os.path.basename(filename)),
                parent=self
            )
            
            if not result:
                return
                
            # Importar testes
            imported_count = 0
            for test_data in tests_to_import:
                # Garantir que o teste tenha um ID único
                if 'id' in test_data:
                    del test_data['id']  # Remove ID para gerar novo
                    
                # Adicionar ao histórico
                if self.database.add_test_to_history(test_data):
                    imported_count += 1
                    
            # Atualizar interface
            self.load_history_to_tree()
            self.update_plot_from_history()
            
            messagebox.showinfo(
                self._t('antenna.success'),
                self._t('antenna.tests_imported_success', count=imported_count),
                parent=self)
            
        except json.JSONDecodeError:
            messagebox.showerror(
                self._t('antenna.error'),
                self._t('antenna.invalid_json'),
                parent=self)
        except Exception as e:
            messagebox.showerror(
                self._t('antenna.error'),
                self._t('antenna.import_failed_error', error=str(e)),
                parent=self)

    def delete_selected_tests(self):
        if not self.database: return
        selected_ids = self.get_selected_test_ids_from_tree()
        if not selected_ids:
            messagebox.showwarning(
                self._t('antenna.nothing_selected'),
                self._t('antenna.select_to_delete'),
                parent=self)
            return

        for tid in selected_ids: self.database.delete_test_from_history(tid)
        self.load_history_to_tree(); self.update_plot_from_history()
        messagebox.showinfo(
            self._t('antenna.success'),
            self._t('antenna.tests_deleted', count=len(selected_ids)),
            parent=self)

    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 DATABASE_AVAILABLE or not self.database:
                messagebox.showwarning(
                    self._t('antenna.db_unavailable_report', "Aviso"),
                    self._t('antenna.db_unavailable_report_msg', "Banco de dados não disponível para gerar relatório."),
                    parent=self
                )
                return
            
            # Obtém apenas os testes selecionados
            selected_ids = self.get_selected_test_ids_from_tree()
            if not selected_ids:
                messagebox.showwarning(
                    self._t('antenna.no_test_selected_report', "Nenhum Teste Selecionado"),
                    self._t('antenna.no_test_selected_report_msg', "Por favor, selecione um ou mais testes no histórico para gerar o relatório."),
                    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(
                    self._t('antenna.no_valid_test', "Aviso"),
                    self._t('antenna.no_valid_test_msg', "Nenhum teste válido encontrado entre os selecionados."),
                    parent=self
                )
                return
            
            # Gera nome do arquivo com data e hora
            now = datetime.now()
            filename = f"Antenna_Check_Report_{now.strftime('%d.%m.%Y_%H.%M.%S')}.pdf"
            
            # Diálogo para salvar arquivo
            filepath = filedialog.asksaveasfilename(
                defaultextension=".pdf",
                filetypes=[(self._t('fastthreshold.pdf_files', 'PDF files'), "*.pdf")],
                initialfile=filename,
                title=self._t('antenna.save_pdf_title', "Salvar Relatório PDF"),
                parent=self
            )
            
            if not filepath:
                return  # Usuário cancelou
            
            # Mostra mensagem de progresso
            fc_log("antenna", "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']:
                fc_log("antenna", "Relatório PDF dos testes selecionados gerado com sucesso!")
                messagebox.showinfo(
                    self._t('antenna.success', "Sucesso"),
                    self._t(
                        'antenna.success_report_generated',
                        "Relatório PDF dos testes selecionados gerado com sucesso!\n\nArquivo: {filepath}\n\nTestes incluídos: {count}",
                        filepath=filepath,
                        count=len(history_data)
                    ),
                    parent=self
                )
                
                # Abre o PDF automaticamente (fallbacks se startfile falhar)
                open_path_with_default_app(filepath, log_tag="antenna")
            else:
                error_msg = result.get('error', 'Erro desconhecido')
                fc_log("antenna", f"Erro ao gerar relatório PDF: {error_msg}")
                messagebox.showerror(
                    self._t('antenna.error_generating_pdf', "Erro"),
                    self._t('antenna.error_generating_pdf_msg', "Erro ao gerar relatório PDF:\n{error}", error=error_msg),
                    parent=self
                )
                
        except Exception as e:
            fc_log("antenna", f"Erro ao gerar relatório PDF: {str(e)}")
            import traceback
            traceback.print_exc()
            messagebox.showerror(
                self._t('antenna.error_generating_pdf', "Erro"),
                self._t('antenna.error_unexpected_pdf', "Erro ao gerar relatório PDF:\n{error}", error=str(e)),
                parent=self
            )

    def _generate_pdf_with_selected_tests(self, filepath, selected_tests):
        """Gera PDF usando ReportLab com dados selecionados respeitando idioma e estilo padrão"""
        try:
            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_charts_title_block,
                cond_page_break_before_medium_figure,
                cond_page_break_before_footer_block,
            )
            from reportlab.lib import colors
            from reportlab.lib.enums import TA_CENTER
            from .antenna_chart_generator import generate_all_antenna_charts
            import tempfile
            import base64

            translator = self.translator
            if not translator and TRANSLATOR_AVAILABLE and callable(get_translator):
                translator = get_translator()
            current_lang = 'pt'
            if translator and hasattr(translator, 'get_language'):
                try:
                    current_lang = (translator.get_language() or 'pt').lower()
                except Exception:
                    current_lang = 'pt'
            lang_prefix = (current_lang or 'pt')[:2]

            def _format_datetime(value):
                if not value or not isinstance(value, str):
                    return '-'
                try:
                    timestamp_dt = None
                    if 'T' in value:
                        timestamp_dt = datetime.fromisoformat(value.replace('Z', '+00:00'))
                    else:
                        formats_to_try = [
                            '%d/%m/%Y %H:%M:%S',
                            '%d/%m/%Y %H:%M',
                            '%d-%m-%Y %H:%M:%S',
                            '%d-%m-%Y %H:%M',
                            '%Y-%m-%d %H:%M:%S',
                            '%Y-%m-%d %H:%M'
                        ]
                        for fmt in formats_to_try:
                            try:
                                timestamp_dt = datetime.strptime(value, fmt)
                                break
                            except ValueError:
                                continue
                    if timestamp_dt:
                        has_seconds = value.count(':') >= 2
                        if lang_prefix == 'en':
                            return timestamp_dt.strftime('%m/%d/%y %I:%M:%S %p' if has_seconds else '%m/%d/%y %I:%M %p')
                        return timestamp_dt.strftime('%d/%m/%Y %H:%M:%S' if has_seconds else '%d/%m/%Y %H:%M')
                except Exception:
                    pass
                return value

            def _get_system_info():
                """Obtém informações do sistema da licença ativa"""
                date_format = '%d/%m/%Y %H:%M:%S' if lang_prefix == 'pt' else '%m/%d/%Y %I:%M:%S %p'
                try:
                    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(self.com_port)
                        system_info = system_info or {}
                        system_info.setdefault('generated_at', datetime.now().strftime(date_format))
                        return system_info

                    try:
                        from .license_module import LicenseManager
                    except ImportError:
                        from license_module import LicenseManager
                    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(self.com_port)
                    system_info = system_info or {}
                    system_info.setdefault('generated_at', datetime.now().strftime(date_format))
                    return system_info
                except Exception as e:
                    fc_log("antenna", f"⚠️ Erro geral ao obter informações do sistema: {e}")
                    return {
                        'software': '4.0.0',
                        'hardware': 'N/A',
                        'firmware': 'N/A',
                        'serial_number': 'N/A',
                        'license': 'N/A',
                        'generated_at': datetime.now().strftime(date_format)
                    }

            def _display_name(test, fallback_index):
                name = test.get('description') or test.get('test_name')
                if name:
                    return name
                fallback_label = self._t('threshold.test', 'Teste')
                return f"{fallback_label} {test.get('id', fallback_index)}"

            doc = SimpleDocTemplate(
                filepath,
                pagesize=A4,
                rightMargin=72,
                leftMargin=72,
                topMargin=72,
                bottomMargin=18
            )

            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')
            )
            footer_style = ParagraphStyle(
                'FooterStyle',
                parent=styles['Normal'],
                fontSize=10,
                alignment=TA_CENTER,
                textColor=colors.grey
            )

            story = []

            try:
                root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
                logo_path = os.path.join(root, 'assets', 'images', 'fasttag_logo.png')
                if os.path.exists(logo_path):
                    logo_size = 1.5 * inch
                    logo_img = Image(logo_path, width=logo_size, height=logo_size)
                    story.append(logo_img)
                    story.append(Spacer(1, 10))
                else:
                    fc_log("antenna", "Logo Fasttag não encontrado")
            except Exception as e:
                fc_log("antenna", f"Erro ao adicionar logo: {e}")

            story.append(Paragraph(self._t('pdf.report_title_antenna', 'Relatório de Testes - Antenna Check'), title_style))
            story.append(Spacer(1, 20))

            sysinfo = _get_system_info()
            story.append(Paragraph(self._t('pdf.system_info', 'Informações do Sistema'), heading_style))
            info_text = (
                f"<b>{self._t('pdf.software', 'Software')}</b> {sysinfo.get('software', 'N/A')}<br/>"
                f"<b>{self._t('pdf.hardware', 'Hardware')}</b> {sysinfo.get('hardware', 'N/A')}<br/>"
                f"<b>{self._t('pdf.firmware', 'Firmware')}</b> {sysinfo.get('firmware', 'N/A')}<br/>"
                f"<b>{self._t('pdf.serial_number', 'Serial Number')}</b> {sysinfo.get('serial_number', 'N/A')}<br/>"
                f"<b>{self._t('pdf.license', 'Licença')}</b> {sysinfo.get('license', 'N/A')}<br/>"
                f"<b>{self._t('pdf.generated_at', 'Gerado em:')}</b> {sysinfo.get('generated_at', 'N/A')}"
            )
            story.append(Paragraph(info_text, styles['Normal']))
            story.append(Spacer(1, 20))

            story.append(Paragraph(self._t('pdf.test_description_title_antenna', 'Descrição do Teste'), heading_style))
            story.append(Paragraph(self._t(
                'pdf.test_description_text_antenna',
                'Este relatório contém os resultados dos testes de antena realizados pelo FastChecker II.'
            ), styles['Normal']))
            story.append(Spacer(1, 20))

            story.append(Paragraph(self._t('pdf.test_statistics', 'Estatísticas dos Testes'), heading_style))

            total_tests = len(selected_tests)
            total_data_points = 0
            freq_ranges = []
            power_values = []
            for test in selected_tests:
                data_points = test.get('data_points', {})
                if isinstance(data_points, dict):
                    total_data_points += len(data_points)
                    frequencies = []
                    for freq_str in data_points.keys():
                        try:
                            frequencies.append(float(freq_str))
                        except (ValueError, TypeError):
                            continue
                    if frequencies:
                        freq_ranges.append((min(frequencies), max(frequencies)))
                power = test.get('power')
                if isinstance(power, (int, float)):
                    power_values.append(float(power))

            all_freqs = [freq for freq_range in freq_ranges for freq in freq_range]
            freq_range_text = f"{min(all_freqs):.1f} - {max(all_freqs):.1f} MHz" if all_freqs else 'N/A'
            avg_power = sum(power_values) / len(power_values) if power_values else 0
            avg_power_text = f"{avg_power:.1f} dBm" if power_values else 'N/A'
            min_power_text = f"{min(power_values):.1f} dBm" if power_values else 'N/A'
            max_power_text = f"{max(power_values):.1f} dBm" if power_values else 'N/A'

            stats_text = (
                f"<b>{self._t('pdf.total_tests', 'Total de Testes:')}</b> {total_tests}<br/>"
                f"<b>{self._t('pdf.total_data_points', 'Total de Pontos de Dados:')}</b> {total_data_points}<br/>"
                f"<b>{self._t('pdf.freq_range_general', 'Faixa de Frequência Geral:')}</b> {freq_range_text}<br/>"
                f"<b>{self._t('pdf.avg_power', 'Potência Média:')}</b> {avg_power_text}<br/>"
                f"<b>{self._t('pdf.min_power', 'Potência Mínima:')}</b> {min_power_text}<br/>"
                f"<b>{self._t('pdf.max_power', 'Potência Máxima:')}</b> {max_power_text}"
            )
            story.append(Paragraph(stats_text, styles['Normal']))
            story.append(Spacer(1, 20))

            story.append(Paragraph(self._t('pdf.test_history_antenna', 'Histórico de Testes'), heading_style))

            header_cell_style = ParagraphStyle(
                'AntennaHeaderCell',
                parent=styles['Normal'],
                alignment=TA_CENTER,
                fontSize=8,
                leading=9,
                textColor=colors.white,
                spaceBefore=0,
                spaceAfter=0
            )
            body_cell_style = ParagraphStyle(
                'AntennaBodyCell',
                parent=styles['Normal'],
                alignment=TA_CENTER,
                fontSize=7,
                leading=8,
                wordWrap='CJK',
                textColor=colors.black
            )

            header_texts = [
                self._t('pdf.name_col_antenna', 'Nome'),
                self._t('pdf.power_db', 'Pot (dBm)'),
                self._t('pdf.freq_range', 'Range Freq (MHz)'),
                self._t('pdf.min_return_loss', 'Min Return Loss (dBm)'),
                self._t('pdf.vswr', 'VSWR'),
                self._t('pdf.freq_at_best', 'Na Freq (MHz)'),
                self._t('pdf.date_time_col', 'Data/Hora')
            ]
            header_paragraphs = []
            for text in header_texts:
                if ' (' in text:
                    text = text.replace(' (', '<br/>(')
                elif ' ' in text and len(text) > 12:
                    parts = text.split(' ')
                    midpoint = len(parts) // 2
                    text = ' '.join(parts[:midpoint]) + '<br/>' + ' '.join(parts[midpoint:])
                header_paragraphs.append(Paragraph(text, header_cell_style))

            table_data = [header_paragraphs]
            test_summaries = []

            for index, test in enumerate(selected_tests):
                display_name = _display_name(test, index + 1)
                power = float(test.get('power', 0) or 0)
                timestamp_formatted = _format_datetime(test.get('timestamp'))

                data_points = test.get('data_points', {})
                frequencies = []
                return_loss_values = []
                if isinstance(data_points, dict):
                    for freq_str, return_loss in data_points.items():
                        try:
                            freq = float(freq_str)
                            rl_value = float(return_loss)
                            frequencies.append(freq)
                            return_loss_values.append(rl_value)
                        except (ValueError, TypeError):
                            continue

                freq_min = min(frequencies) if frequencies else None
                freq_max = max(frequencies) if frequencies else None
                freq_range_cell = f"{freq_min:.1f} - {freq_max:.1f}" if freq_min is not None and freq_max is not None else 'N/A'
                min_rl = min(return_loss_values) if return_loss_values else None
                min_rl_cell = f"{min_rl:.2f}" if min_rl is not None else 'N/A'
                if min_rl is not None and return_loss_values and frequencies:
                    min_rl_index = return_loss_values.index(min_rl)
                    min_freq_value = frequencies[min_rl_index]
                    min_freq_cell = f"{min_freq_value:.1f}"
                else:
                    min_freq_value = None
                    min_freq_cell = 'N/A'
                vswr = self.calculate_vswr_from_powers(power, min_rl if min_rl is not None else power)
                vswr_cell = f"{vswr:.2f}" if min_rl is not None else 'N/A'

                row_cells = [
                    Paragraph(display_name, body_cell_style),
                    Paragraph(f"{power:.0f}", body_cell_style),
                    Paragraph(freq_range_cell, body_cell_style),
                    Paragraph(min_rl_cell, body_cell_style),
                    Paragraph(vswr_cell, body_cell_style),
                    Paragraph(min_freq_cell, body_cell_style),
                    Paragraph(timestamp_formatted, body_cell_style)
                ]
                table_data.append(row_cells)

                max_rl = max(return_loss_values) if return_loss_values else None
                avg_rl = (sum(return_loss_values) / len(return_loss_values)) if return_loss_values else None

                test_summaries.append({
                    'id': test.get('id', '-'),
                    'name': display_name,
                    'power': power,
                    'freq_min': freq_min,
                    'freq_max': freq_max,
                    'min_rl': min_rl,
                    'max_rl': max_rl,
                    'avg_rl': avg_rl,
                    'timestamp': timestamp_formatted,
                    'points': len(return_loss_values)
                })

            test_table = Table(
                table_data,
                colWidths=[1.2 * inch, 0.8 * inch, 1.1 * inch, 1.0 * inch, 0.8 * inch, 0.8 * inch, 1.1 * 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'),
                ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
                ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
                ('FONTSIZE', (0, 1), (-1, -1), 7),
                ('BOTTOMPADDING', (0, 0), (-1, -1), 6),
                ('TOPPADDING', (0, 0), (-1, -1), 6),
                ('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))

            temp_files = []
            chart_plot_arg = "VSWR" if self._get_plot_type_code() == "vswr" else "Return Loss"
            charts = generate_all_antenna_charts(selected_tests, chart_plot_arg)

            if charts:
                story.append(cond_page_break_before_charts_title_block())
                story.append(Paragraph(self._t('pdf.charts_title_antenna', 'Gráficos dos Testes'), heading_style))

                for i, (name, image_base64) in enumerate(charts):
                    story.append(cond_page_break_before_medium_figure(2.5))
                    summary = test_summaries[i] if i < len(test_summaries) else {}
                    with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
                        tmp_file.write(base64.b64decode(image_base64))
                        tmp_path = tmp_file.name
                        temp_files.append(tmp_path)

                    img = Image(tmp_path, width=7 * inch, height=2.5 * inch)

                    freq_min_val = summary.get('freq_min')
                    freq_max_val = summary.get('freq_max')
                    min_rl_val = summary.get('min_rl')
                    max_rl_val = summary.get('max_rl')
                    avg_rl_val = summary.get('avg_rl')
                    freq_min_text = f"{freq_min_val:.1f}" if freq_min_val is not None else 'N/A'
                    freq_max_text = f"{freq_max_val:.1f}" if freq_max_val is not None else 'N/A'
                    min_rl_text = f"{min_rl_val:.2f} dB" if min_rl_val is not None else 'N/A'
                    max_rl_text = f"{max_rl_val:.2f} dB" if max_rl_val is not None else 'N/A'
                    avg_rl_text = f"{avg_rl_val:.2f} dB" if avg_rl_val is not None else 'N/A'
                    points_text = summary.get('points', 0)

                    chart_info_text = (
                        f"{self._t('pdf.test_id', 'Teste ID:')} {summary.get('id', '-')}"
                        f" | {self._t('pdf.date_time', 'Data/Hora')}: {summary.get('timestamp', '-')}"
                        f" | {self._t('pdf.power_db', 'Pot (dBm)')}: {summary.get('power', 0):.1f} dBm<br/>"
                        f"{self._t('pdf.freq_range_detail', 'Faixa:')} {freq_min_text} - {freq_max_text} MHz"
                        f" | {self._t('pdf.points', 'Pontos:')} {points_text}"
                        f" | {self._t('pdf.rl_min', 'RL Min:')} {min_rl_text}"
                        f" | {self._t('pdf.rl_max', 'RL Max:')} {max_rl_text}"
                        f" | {self._t('pdf.rl_avg', 'RL Médio:')} {avg_rl_text}"
                    )

                    story.append(Paragraph(f"{self._t('pdf.antenna_chart_title', 'Antenna Check')} - {name}", heading_style))
                    story.append(Paragraph(chart_info_text, styles['Normal']))
                    story.append(img)
                    story.append(Spacer(1, 16))
            else:
                story.append(Paragraph(self._t('pdf.no_data_available', 'Nenhum dado disponível para este tipo de gráfico'), styles['Normal']))

            story.append(Spacer(1, 12))
            story.append(Paragraph(self._t('pdf.auto_report_footer', 'Relatório automático gerado pelo FastChecker'), footer_style))
            story.append(cond_page_break_before_footer_block())
            story.append(Spacer(1, 12))
            story.append(Paragraph(self._t('pdf.additional_info', 'Informações Adicionais'), heading_style))
            story.append(Paragraph(self._t('pdf.footer_text', 'Este relatório foi gerado automaticamente pelo FastChecker II.'), styles['Normal']))

            timestamp_format = "%d/%m/%Y às %H:%M:%S" if lang_prefix == 'pt' else "%m/%d/%Y at %I:%M:%S %p"
            story.append(Paragraph(
                f"<b>{self._t('pdf.document_generated_at', 'Documento gerado em:')}</b> {datetime.now().strftime(timestamp_format)}",
                styles['Normal']
            ))

            doc.build(story)

            for temp_file in temp_files:
                try:
                    if os.path.exists(temp_file):
                        os.unlink(temp_file)
                except Exception as e:
                    fc_log("antenna", f"⚠️ Erro ao limpar arquivo temporário {temp_file}: {e}")

            return {'success': True}

        except Exception as e:
            fc_log("antenna", f"❌ Erro ao gerar PDF: {e}")
            import traceback
            traceback.print_exc()
            return {'success': False, 'error': str(e)}

    def sort_treeview(self, column):
        """
        Ordena a tabela de histórico pela coluna especificada
        """
        try:
            # Obtém todos os itens da tabela com seus valores
            items = []
            for item in self.history_tree.get_children(''):
                values = self.history_tree.item(item, 'values')
                items.append((values, item))
            
            # 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 == "Potência":
                # Ordenação numérica
                items.sort(key=lambda x: int(x[0][3]) if x[0][3].isdigit() else float('inf'), reverse=self.current_sort_reverse)
            elif column == "Min Return Loss":
                # Ordenação numérica decimal
                items.sort(key=lambda x: float(x[0][5]) if x[0][5] != 'N/A' and x[0][5] != '∞' else float('inf'), reverse=self.current_sort_reverse)
            elif column == "VSWR":
                # Ordenação numérica decimal
                items.sort(key=lambda x: float(x[0][6]) if x[0][6] != 'N/A' and x[0][6] != '∞' else float('inf'), reverse=self.current_sort_reverse)
            elif column == "Freq. Melhor Valor":
                # Ordenação numérica decimal
                items.sort(key=lambda x: float(x[0][7]) if x[0][7] != 'N/A' else float('inf'), reverse=self.current_sort_reverse)
            elif column == "Data/Hora":
                # Ordenação cronológica (YYYY-MM-DD HH:MM:SS)
                items.sort(key=lambda x: datetime.strptime(x[0][8], "%Y-%m-%d %H:%M:%S") if x[0][8] != 'N/A' else datetime.min, reverse=self.current_sort_reverse)
            elif column == "Plot":
                # Ordenação por estado do checkbox (☐ primeiro, depois ☑)
                def plot_key(x):
                    return 0 if x[0][0] == "☐" else 1
                items.sort(key=plot_key, reverse=self.current_sort_reverse)
            else:
                # Ordenação alfabética para outras colunas
                items.sort(key=lambda x: x[0][self._get_column_index(column)], reverse=self.current_sort_reverse)
            
            # Reinsere os itens na ordem correta
            for item in self.history_tree.get_children(''):
                self.history_tree.delete(item)
            for values, _ in items:
                self.history_tree.insert('', 'end', values=values)
            
            self.update_sort_indicator(column)
        except Exception as e:
            fc_log("antenna", f"Erro ao ordenar Treeview: {e}")
            # Em caso de erro, recarrega a tabela
            self.load_history_to_tree()

    def update_sort_indicator(self, column):
        """
        Atualiza os indicadores de ordenação nas colunas
        """
        try:
            # Remove símbolos de todas as colunas
            for col in ["Plot", "Nome", "Potência", "Range Freq", "Min Return Loss", "VSWR", "Freq. Melhor Valor", "Data/Hora"]:
                current_text = self.history_tree.heading(col)['text']
                # Remove símbolos existentes
                clean_text = current_text.replace(" ↑", "").replace(" ↓", "").replace(" ↕", "")
                self.history_tree.heading(col, text=f"{clean_text} ↕") # Adiciona ↕ de volta para indicar que é ordenável
            
            # Adiciona o símbolo de ordenação para a coluna atual
            current_text = self.history_tree.heading(column)['text']
            clean_text = current_text.replace(" ↑", "").replace(" ↓", "").replace(" ↕", "")
            
            if self.current_sort_reverse:
                self.history_tree.heading(column, text=f"{clean_text} ↓")
            else:
                self.history_tree.heading(column, text=f"{clean_text} ↑")
        except Exception as e:
            fc_log("antenna", f"Erro ao atualizar indicador de ordenação: {e}")

    def _get_column_index(self, column):
        """
        Retorna o índice da coluna na lista de valores
        """
        columns = ["Plot", "ID", "Nome", "Potência", "Range Freq", "Min Return Loss", "VSWR", "Freq. Melhor Valor", "Data/Hora"]
        try:
            return columns.index(column)
        except ValueError:
            return 0


    def _setup_zoom_controls(self, parent):
        """Configura os controles de zoom na parte superior esquerda do gráfico"""
        # Frame para os controles de zoom
        zoom_frame = ttk.Frame(parent)
        zoom_frame.grid(row=0, column=0, sticky="nw", padx=5, pady=5)
        
        # Botão Zoom In
        zoom_in_btn = tk.Button(zoom_frame, text="🔍+", width=3, height=1, 
                               command=self._zoom_in, 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, 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, font=("Arial", 10))
        reset_btn.grid(row=0, column=2, padx=2)
        
        # Botão Fit to Data
        fit_btn = tk.Button(zoom_frame, text="📐", width=3, height=1,
                           command=self._fit_to_data, font=("Arial", 10))
        fit_btn.grid(row=0, column=3, padx=2)
        
        # Botão Pan (arrastar)
        self.pan_active = False
        self.pan_btn = tk.Button(zoom_frame, text="✋", width=3, height=1,
                                 command=self._toggle_pan, font=("Arial", 10),
                                 relief=tk.RAISED)
        self.pan_btn.grid(row=0, column=4, padx=2)
        
        # Label do fator de zoom
        self.zoom_label = tk.Label(zoom_frame, text="Zoom: 1.0x", 
                                  font=("Arial", 8), fg="gray")
        self.zoom_label.grid(row=0, column=5, padx=5)

    def _zoom_in(self):
        """Aplica zoom in no gráfico"""
        try:
            # Obtém limites atuais
            xlim = self.ax.get_xlim()
            ylim = self.ax.get_ylim()
            
            # Calcula centro dos limites atuais
            x_center = (xlim[0] + xlim[1]) / 2
            y_center = (ylim[0] + ylim[1]) / 2
            
            # Calcula nova amplitude (zoom in = amplitude menor)
            x_range = xlim[1] - xlim[0]
            y_range = ylim[1] - ylim[0]
            zoom_factor = 0.8  # Reduz amplitude em 20%
            
            new_x_range = x_range * zoom_factor
            new_y_range = y_range * zoom_factor
            
            # Define novos limites
            new_xlim = (x_center - new_x_range/2, x_center + new_x_range/2)
            new_ylim = (y_center - new_y_range/2, y_center + new_y_range/2)
            
            # Regra: Para VSWR, o eixo Y deve sempre iniciar em 1.0
            if self._is_plot_vswr():
                new_ylim = (1.0, max(1.0 + 0.05, new_ylim[1]))

            self.ax.set_xlim(new_xlim)
            self.ax.set_ylim(new_ylim)
            self.canvas.draw()
            
            # Atualiza fator de zoom
            self.zoom_factor *= 1.25
            self.zoom_label.config(text=f"Zoom: {self.zoom_factor:.1f}x")
            
            fc_log("antenna", "✅ Zoom in aplicado no Antenna Check")
            
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao aplicar zoom in: {e}")

    def _zoom_out(self):
        """Aplica zoom out no gráfico"""
        try:
            # Obtém limites atuais
            xlim = self.ax.get_xlim()
            ylim = self.ax.get_ylim()
            
            # Calcula centro dos limites atuais
            x_center = (xlim[0] + xlim[1]) / 2
            y_center = (ylim[0] + ylim[1]) / 2
            
            # Calcula nova amplitude (zoom out = amplitude maior)
            x_range = xlim[1] - xlim[0]
            y_range = ylim[1] - ylim[0]
            zoom_factor = 1.25  # Aumenta amplitude em 25%
            
            new_x_range = x_range * zoom_factor
            new_y_range = y_range * zoom_factor
            
            # Define novos limites
            new_xlim = (x_center - new_x_range/2, x_center + new_x_range/2)
            new_ylim = (y_center - new_y_range/2, y_center + new_y_range/2)
            
            # Regra: Para VSWR, o eixo Y deve sempre iniciar em 1.0
            if self._is_plot_vswr():
                new_ylim = (1.0, max(1.0 + 0.05, new_ylim[1]))

            self.ax.set_xlim(new_xlim)
            self.ax.set_ylim(new_ylim)
            self.canvas.draw()
            
            # Atualiza fator de zoom
            self.zoom_factor *= 0.8
            self.zoom_label.config(text=f"Zoom: {self.zoom_factor:.1f}x")
            
            fc_log("antenna", "✅ Zoom out aplicado no Antenna Check")
            
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao aplicar zoom out: {e}")

    def _reset_zoom(self):
        """Reseta o zoom para a visualização padrão"""
        try:
            # Restaura limites originais baseados no tipo de plot
            freq_min = float(self.freq_min_var.get())
            freq_max = float(self.freq_max_var.get())
            
            self.ax.set_xlim(freq_min, freq_max)
            
            if self._is_plot_vswr():
                self.ax.set_ylim(1.0, 3.5)
            else:
                self.ax.set_ylim(-30, 15)
            
            self.canvas.draw()
            
            # Reseta fator de zoom
            self.zoom_factor = 1.0
            self.zoom_label.config(text="Zoom: 1.0x")
            
            fc_log("antenna", "✅ Zoom resetado no Antenna Check")
            
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao resetar zoom: {e}")

    def _fit_to_data(self):
        """Ajusta a visualização automaticamente aos dados"""
        try:
            # Obtém todos os dados plotados
            lines = self.ax.get_lines()
            if not lines:
                return
                
            # Encontra limites dos dados (excluindo linhas de limite)
            x_data = []
            y_data = []
            
            for line in lines:
                # Pula linhas de limite (linhas horizontais ou com poucos pontos)
                x_line = line.get_xdata()
                y_line = line.get_ydata()
                label = (line.get_label() or "").lower()
                
                # Debug: mostra informações de cada linha
                fc_log("antenna", f"🔍 Linha encontrada: {len(x_line)} pontos, X: {min(x_line) if len(x_line) > 0 else 'N/A'}-{max(x_line) if len(x_line) > 0 else 'N/A'} MHz")
                
                # INCLUSÃO CORRIGIDA: ignorar linhas de referência/limite e artefatos (ex.: axhline com x=[0,1])
                if 'limite' in label or label == '_nolegend_':
                    fc_log("antenna", "↷ Linha ignorada (referência/limite/sem legenda)")
                    continue
                if len(x_line) >= 2:
                    x_min_line = min(x_line); x_max_line = max(x_line)
                    x_range_line = x_max_line - x_min_line
                    # exige pelo menos 1 MHz de faixa para considerar dado real
                    if x_range_line >= 1:
                        x_data.extend(x_line)
                        y_data.extend(y_line)
                        fc_log("antenna", f"✅ Linha de dados incluída: {len(x_line)} pontos, range X: {x_range_line:.1f} MHz")
                    else:
                        fc_log("antenna", "↷ Linha ignorada (faixa X < 1 MHz)")
            
            if not x_data or not y_data:
                fc_log("antenna", "❌ Nenhum dado de antena encontrado")
                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)
            
            fc_log("antenna", f"🔍 Dados filtrados: X({x_min:.1f}-{x_max:.1f} MHz), Y({y_min:.1f}-{y_max:.1f} dBm)")
            
            # Margem maior para melhor visualização
            x_range = x_max - x_min
            y_range = y_max - y_min
            
            # Ajuste de margem: se range for pequeno, ainda assim dar uma folga visual
            if x_range < 10:  # Menos de 10 MHz de range (faixas estreitas)
                x_margin = max(0.5, x_range * 0.1)  # pelo menos 0.5 MHz
            else:
                x_margin = x_range * 0.05  # 5% para ranges maiores
                
            if y_range < 5:  # Menos de 5 dBm de range
                y_margin = 0.5  # 0.5 dBm de margem
            else:
                y_margin = y_range * 0.05  # 5% de margem
            
            # Define novos limites: forçar cobertura completa das faixas ativas
            # Se a licença fornecer freq_ranges, use-os para "estender" o gráfico
            try:
                freq_ranges = self.license_limits.get('freq_ranges') if hasattr(self, 'license_limits') else None
            except Exception:
                freq_ranges = None
            if freq_ranges and isinstance(freq_ranges, (list, tuple)) and len(freq_ranges) > 0:
                lic_min = min(r[0] for r in freq_ranges)
                lic_max = max(r[1] for r in freq_ranges)
                new_x_min = min(lic_min, max(0, x_min - x_margin))
                new_x_max = max(lic_max, x_max + x_margin)
            else:
                new_x_min = max(0, x_min - x_margin)
                new_x_max = x_max + x_margin
            new_y_min = y_min - y_margin
            new_y_max = y_max + y_margin
            
            # Para VSWR, força início em 1.0
            if self._is_plot_vswr():
                new_y_min = 1.0
                # Garante um topo minimamente acima de 1.0 para visualização
                new_y_max = max(new_y_max, 1.1)

            self.ax.set_xlim(new_x_min, new_x_max)
            self.ax.set_ylim(new_y_min, new_y_max)
            self.canvas.draw()
            
            fc_log("antenna", f"✅ Visualização ajustada aos dados no Antenna Check: X({new_x_min:.1f}-{new_x_max:.1f}MHz), Y({new_y_min:.1f}-{new_y_max:.1f}dBm)")
            
        except Exception as e:
            fc_log("antenna", f"❌ Erro ao ajustar visualização: {e}")

    def _toggle_pan(self):
        """Ativa/desativa o modo pan (arrastar gráfico)"""
        self.pan_active = not self.pan_active
        
        if self.pan_active:
            # Ativa modo pan
            self.pan_btn.config(relief=tk.SUNKEN, bg='#d0d0d0')
            self.canvas.mpl_connect('button_press_event', self._on_pan_press)
            self.canvas.mpl_connect('button_release_event', self._on_pan_release)
            self.canvas.mpl_connect('motion_notify_event', self._on_pan_motion)
            self.pan_start = None
            fc_log("antenna", "✅ Modo Pan ativado - clique e arraste para mover o gráfico")
        else:
            # Desativa modo pan
            self.pan_btn.config(relief=tk.RAISED, bg='SystemButtonFace')
            fc_log("antenna", "✅ Modo Pan desativado")

    def _on_pan_press(self, event):
        """Callback quando pressiona o mouse no modo pan"""
        if self.pan_active and event.inaxes == self.ax:
            self.pan_start = (event.xdata, event.ydata)
            self.pan_xlim = self.ax.get_xlim()
            self.pan_ylim = self.ax.get_ylim()

    def _on_pan_release(self, event):
        """Callback quando solta o mouse no modo pan"""
        if self.pan_active:
            self.pan_start = None

    def _on_pan_motion(self, event):
        """Callback quando move o mouse no modo pan"""
        if self.pan_active and self.pan_start and event.inaxes == self.ax:
            # Calcula deslocamento
            dx = self.pan_start[0] - event.xdata
            dy = self.pan_start[1] - event.ydata
            
            # Aplica deslocamento aos limites
            new_xlim = (self.pan_xlim[0] + dx, self.pan_xlim[1] + dx)
            new_ylim = (self.pan_ylim[0] + dy, self.pan_ylim[1] + dy)
            
            self.ax.set_xlim(new_xlim)
            self.ax.set_ylim(new_ylim)
            self.canvas.draw_idle()

if __name__ == '__main__':
    root = tk.Tk()
    root.title("Teste Isolado - Módulo de Antena")
    root.geometry("1050x850")
    
    from port_manager import get_com_port_number
    com_port = get_com_port_number()
    if com_port is None:
        # CORREÇÃO: Não exibe erro de hardware quando sem licença (modo browser)
        fc_log("antenna", "ℹ️ Modo browser - hardware não encontrado")
        # Cria uma instância com porta padrão para modo browser
        fake_limits = {'min_freq': 860, 'max_freq': 960, 'min_power': 5, 'max_power': 25}
        app = AntennaModule(root, is_licensed=False, license_limits=fake_limits, com_port=4)  # COM4 como padrão
        app.pack(fill="both", expand=True)
        root.mainloop()
    else:
        fake_limits = {'min_freq': 860, 'max_freq': 960, 'min_power': 5, 'max_power': 25}
        app = AntennaModule(root, is_licensed=True, license_limits=fake_limits, com_port=com_port)
        app.pack(fill="both", expand=True)
        root.mainloop()