# 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 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

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
        print("⚠️ 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:
    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
                    print(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'):
                        print(f"✅ AntennaCheck: Faixas excluídas: {license_limits.get('excluded_ranges')}")
                else:
                    print("⚠️ AntennaCheck: Usando limites padrão (sem licença ativa)")
            except Exception as e:
                print(f"⚠️ AntennaCheck: Erro ao obter limites da licença: {e}")
                print("⚠️ 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._language_listener_registered = False
        if self.translator:
            try:
                self.translator.add_language_change_listener(self._on_language_changed)
                self._language_listener_registered = True
            except Exception as e:
                print(f"⚠️ AntennaModule: Falha ao registrar listener de idioma: {e}")

        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
        # 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:
            print("⚠️ AntennaCheck: Biblioteca scipy não encontrada. Funcionalidade de curva suave não estará disponível.")
            print("💡 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:
                print(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 _on_language_changed(self, *_):
        """Callback disparado quando o idioma é alterado."""
        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', 'Return Loss (dBm)')),
            ("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 _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'):
                self.start_button.configure(text=self._t('antenna.test', 'Testar'))
            if hasattr(self, 'stop_button'):
                self.stop_button.configure(text=self._t('antenna.stop', 'Parar'))
            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', 'Return Loss (dBm)')
                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:
            print(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:
                print(f"⚠️ AntennaModule: Erro ao atualizar heading '{column}': {e}")
    def on_destroy(self, event=None):
        if self.test_is_running:
            self.test_is_running = False
            if self.worker_thread and self.worker_thread.is_alive():
                self.worker_thread.join(timeout=0.5)
        if self.translator and self._language_listener_registered:
            try:
                self.translator.remove_language_change_listener(self._on_language_changed)
            except Exception as e:
                print(f"⚠️ AntennaModule: Falha ao remover listener de idioma: {e}")
        if rfid_sdk:
            ### ALTERADO: usa self.com_port ###
            try: rfid_sdk.UHF_RFID_Close(self.com_port)
            except Exception as e: print(f"Erro ao fechar COM na destruição: {e}")

    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)
        
        # Botão de teste
        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 parar
        self.stop_button = ttk.Button(self.controls_frame, text=self._t('antenna.stop', 'Parar'), command=self.interrupt_scan, state="disabled")
        self.stop_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)
        print(f"🔍 AntennaModule: Status da licença: {is_licensed}")
        print(f"🔍 AntennaModule: license_limits: {self.license_limits}")
        
        if is_licensed:
            self.browser_frame.pack_forget()  # Esconde quando há licença
            print("✅ 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)
            print("⚠️ 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)
                print(f"ℹ️ AntennaModule: Mensagem informativa exibida - faixas excluídas: {excluded_str}")
            else:
                self.license_info_frame.pack_forget()
                print("ℹ️ AntennaModule: Mensagem informativa ocultada - sem faixas excluídas")
        except Exception as e:
            print(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
            print("✅ 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
            print("⚠️ AntennaModule: Mensagem de modo browser visível (sem licença)")

    def update_license_status(self, new_license_status):
        """CORREÇÃO: Atualiza o status da licença e os limites de frequência"""
        try:
            print(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
                        print(f"✅ AntennaModule: Licença válida confirmada: {license_limits.get('license_name', 'N/A')}")
                    else:
                        print("⚠️ AntennaModule: AppShell diz que há licença, mas não é válida para Antenna Check")
                        new_license_status = False
                except Exception as e:
                    print(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)
            
            print(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'):
                print(f"✅ AntennaModule: Faixas excluídas: {self.license_limits.get('excluded_ranges')}")
            
            # Atualiza a interface
            self._update_ui_for_license_status()
            
            # NOVO: Atualiza a mensagem informativa sobre faixas excluídas
            self._update_license_info_message()
            
            print(f"✅ AntennaModule: Status da licença atualizado para: {new_license_status}")
            
        except Exception as e:
            print(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)
        
        # Atualizar botões da barra lateral
        if is_running:
            # Em execução: permitir parar apenas com licença; iniciar já está em execução
            self.start_button.config(text="Parar", command=self.interrupt_scan, state='normal' if is_licensed else 'disabled')
            self.stop_button.config(state='normal' if is_licensed else 'disabled')
        else:
            # Não licenciado: bloquear apenas ações que acionam o reader (Testar)
            self.start_button.config(text="Testar", command=self.start_scan, state='normal' if is_licensed else 'disabled')
            self.stop_button.config(state='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("Modo Demo", "Este módulo está funcionando em modo demo."); 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)
                    print(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'):
                        print(f"🔧 AntennaCheck: Faixas excluídas atualizadas: {license_limits.get('excluded_ranges')}")
            except Exception as e:
                print(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("Licença Inválida", "A funcionalidade de teste está desabilitada.", parent=self); return
        
        # Validação de nome duplicado
        test_name = self.description_var.get().strip()
        if not test_name:
            messagebox.showerror("Nome Vazio", "O campo 'Nome do Teste' não pode estar vazio.", parent=self); return
        
        # Verifica se o nome já existe no histórico
        if self.check_duplicate_test_name(test_name):
            messagebox.showerror("Nome Duplicado", 
                               f"Já existe um teste com o nome '{test_name}' no histórico.\n\n"
                               f"Por favor, escolha um nome diferente para este teste.", 
                               parent=self)
            return
        
        try:
            freq_min = float(self.freq_min_var.get()); freq_max = float(self.freq_max_var.get())
            print(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)
                        print(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])
                            print(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])
                    print(f"❌ AntennaCheck: Frequência {freq_min}-{freq_max} MHz REJEITADA - fora das faixas permitidas")
                    messagebox.showerror("Frequência fora das faixas da licença", 
                                       f"A frequência selecionada ({freq_min}-{freq_max} MHz) está fora das faixas permitidas pela licença.\n\n"
                                       f"Faixas permitidas: {ranges_str}\n"
                                       f"Frequência selecionada: {freq_min} - {freq_max} MHz\n\n"
                                       f"Ajuste a frequência para dentro de uma das faixas permitidas.", 
                                       parent=self); return
            else:
                # Fallback para validação simples
                if not (self.min_freq_lic <= freq_min < freq_max <= self.max_freq_lic):
                    print(f"❌ AntennaCheck: Frequência {freq_min}-{freq_max} MHz REJEITADA - fora dos limites {self.min_freq_lic}-{self.max_freq_lic} MHz")
                    messagebox.showerror("Frequência fora da faixa da licença", 
                                       f"A frequência selecionada ({freq_min}-{freq_max} MHz) está fora da faixa permitida pela licença.\n\n"
                                       f"Faixa permitida: {self.min_freq_lic} - {self.max_freq_lic} MHz\n"
                                       f"Frequência selecionada: {freq_min} - {freq_max} MHz\n\n"
                                       f"Ajuste a frequência para dentro da faixa permitida.", 
                                       parent=self)
                    return
                else:
                    print(f"✅ AntennaCheck: Frequência {freq_min}-{freq_max} MHz ACEITA - dentro dos limites {self.min_freq_lic}-{self.max_freq_lic} MHz")
        except ValueError: 
            print(f"❌ AntennaCheck: Erro na conversão de frequência: {self.freq_min_var.get()}, {self.freq_max_var.get()}")
            messagebox.showerror("Erro de Validação", "Valores de frequência inválidos.", parent=self); return
        
        # CORREÇÃO CRÍTICA: Validação da potência contra limites da licença
        try:
            power = float(self.power_var.get())
            print(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):
                print(f"❌ AntennaCheck: Potência {power} dBm REJEITADA - fora dos limites {self.min_power_lic}-{self.max_power_lic} dBm")
                messagebox.showerror("Potência fora da faixa da licença", 
                                   f"A potência selecionada ({power} dBm) está fora da faixa permitida pela licença.\n\n"
                                   f"Faixa permitida: {self.min_power_lic} - {self.max_power_lic} dBm\n"
                                   f"Potência selecionada: {power} dBm\n\n"
                                   f"Ajuste a potência para dentro da faixa permitida.", 
                                   parent=self); return
            else:
                print(f"✅ AntennaCheck: Potência {power} dBm ACEITA - dentro dos limites {self.min_power_lic}-{self.max_power_lic} dBm")
        except ValueError: 
            print(f"❌ AntennaCheck: Erro na conversão de potência: {self.power_var.get()}")
            messagebox.showerror("Erro de Validação", "Valor de potência inválido.", 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)
        self.worker_thread = threading.Thread(target=self.worker_scan, args=(freq_min, freq_max), daemon=True)
        self.worker_thread.start()

    def interrupt_scan(self):
        if self.test_is_running:
            self.test_is_running = False

    def worker_scan(self, freq_min, freq_max):
        self.current_test_run = {
            "id": f"live_{time.time()}", "description": self.description_var.get(), "power": self.power_var.get(),
            "data_points": {}, "color": "red"
        }
        try:
            ### ALTERADO: usa self.com_port ###
            if rfid_sdk.UHF_RFID_Open(self.com_port, BAUD_RATE) != 0:
                self.test_is_running = False
                self.after(0, lambda: messagebox.showwarning("Erro de Comunicação", f"Falha ao abrir a porta COM{self.com_port}. Verifique a conexão do hardware.", parent=self))
                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', [])
                print(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
                                    print(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
                                print(f"✅ AntennaCheck: Frequência {current_freq} MHz ACEITA - faixa permitida {range_min}-{range_max} MHz")
                                break
                            else:
                                print(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
                    readings = []
                    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)
                        output_buffer, output_len = ctypes.create_string_buffer(64), ctypes.c_uint(0)
                        status = rfid_sdk.UHF_RFID_Set(RFID_CMD_GET_PORT_LOSS, b'', 0, output_buffer, ctypes.byref(output_len))
                        if status == 0 and output_len.value >= 3:
                            adc_value = struct.unpack('>H', output_buffer.raw[1:3])[0]
                            reflected_power_dbm = -25 + next((i for i, val in enumerate(RxAdcTable) if adc_value <= val), len(RxAdcTable) - 1)
                            if reflected_power_dbm > 10 and self.protection_enabled:
                                self.test_is_running = False
                                self.after(0, lambda: messagebox.showwarning("Proteção Ativada", f"Teste interrompido. Return Loss muito alto: {reflected_power_dbm:.2f} dBm.", parent=self))
                                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)
                        print(f"✅ AntennaCheck: Frequência {current_freq} MHz testada (faixa permitida)")
                else:
                    # Frequência na faixa excluída - pula sem fazer medição
                    print(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; self.after(0, lambda: messagebox.showerror("Erro de Scan", f"Ocorreu um erro durante a varredura: {e}", parent=self))
        finally:
            ### ALTERADO: usa self.com_port ###
            if rfid_sdk: rfid_sdk.UHF_RFID_Close(self.com_port)
            final_status = "Varredura concluída!" if self.test_is_running else "Teste interrompido."
            self.test_is_running = False
            if self.app_shell: self.app_shell.set_test_running(False, "Antenna Check")
            
            if self.current_test_run and self.current_test_run['data_points']:
                self.after(0, self.save_test_to_history, self.current_test_run)
            self.current_test_run = None
            self.after(0, self.update_ui_state, False)
    
    def setup_plot(self):
        self.ax.clear()
        self.ax.set_xlabel("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()))
        plot_type = self.plot_type_var.get()
        curve_type = self.curve_type_var.get()
        
        # Ajusta configurações baseado no tipo de plot e curva
        if plot_type == "VSWR":
            self.ax.set_ylabel("VSWR")
            self.ax.set_ylim(1.0, 3.5)
        else:
            self.ax.set_ylabel("Return Loss (dBm)")
            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]
                
                # Determina a unidade baseada no tipo de plot
                unit = "VSWR" if self.plot_type_var.get() == "VSWR" else "dBm"
                
                # Adiciona informação sobre o tipo de curva
                curve_type = self.curve_type_var.get()
                if curve_type == "Curva Suave":
                    tooltip_text = f"Freq {x:.1f} -> {y:.2f} {unit} (Suavizado)"
                else:
                    tooltip_text = f"Freq {x:.1f} -> {y:.2f} {unit}"
                
                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:
            print(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:
                print(f"⚠️ Erro no fallback do tooltip: {fallback_error}")

    def update_plot(self):
        self.setup_plot()
        # CORREÇÃO: Cores com alto contraste - sem cores claras
        colors = ["#1f77b4", "#d62728", "#2ca02c", "#ff7f0e", "#9467bd", "#8c564b", "#e377c2"]
        plot_type = self.plot_type_var.get()
        curve_type = self.curve_type_var.get()
        print(f"🎯 Modo de curva selecionado: {curve_type}")
        
        plot_count = 0
        for test_name, test_data in self.historical_plots.items():
            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_type == "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:
                            print(f"🔧 Aplicando suavização para {test_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"{test_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"{test_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:
                            print(f"⚠️ Erro na suavização: {e}")
                            # Fallback para plotagem normal por segmentos
                            for i, (x_seg, y_seg) in enumerate(segments):
                                label = test_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
                        print(f"🔧 Plotagem pontual para {test_name} com {len(x_valid)} pontos em {len(segments)} segmentos")
                        for i, (x_seg, y_seg) in enumerate(segments):
                            label = test_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_type == "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:
                            print(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:
                            print(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
                        print(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))
        
        print(f"🔍 AntennaCheck: Criados {len(segments)} segmentos contínuos")
        for i, (x_seg, y_seg) in enumerate(segments):
            print(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:
            print(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 save_test_to_history(self, test_data):
        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:
            print(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:
            print(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:
            print(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("Nome Duplicado", 
                                       f"Já existe um teste com o nome '{new_name}' no histórico.\n\n"
                                       f"Por favor, escolha um nome diferente para este teste.", 
                                       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)
                        print(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:
            print(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'):
                # NOVO: Usa apenas o nome do teste para o rótulo do gráfico
                test_id = test.get('id', 'unknown')
                name = test.get('description', f'Teste_{test_id}')
                data_points = {Decimal(k): v for k, v in test.get('data_points', {}).items()}
                self.historical_plots[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=[("JSON Files", "*.json")], title="Importar Testes de Antena", 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("Nenhum Teste", "Arquivo não contém dados de teste válidos.", 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("Sucesso", f"{len(tests_to_import)} teste(s) importado(s).", parent=self)
        except Exception as e: messagebox.showerror("Erro", f"Falha ao importar.\n{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("Erro", "Banco de dados não disponível.", parent=self)
            return
            
        selected_ids = self.get_selected_test_ids_from_tree()
        if not selected_ids: 
            messagebox.showwarning("Nada Selecionado", "Selecione testes no histórico para salvar.", 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="Salvar Testes Selecionados",
            defaultextension=".json",
            filetypes=[("Arquivos JSON", "*.json"), ("Todos os arquivos", "*.*")],
            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("Sucesso", f"{len(selected_tests)} teste(s) salvos em:\n{filename}", parent=self)
            
        except Exception as e:
            messagebox.showerror("Erro", f"Falha ao salvar testes.\n{str(e)}", parent=self)

    def import_tests(self):
        """Importa testes de um arquivo JSON"""
        if not self.database:
            messagebox.showwarning("Erro", "Banco de dados não disponível.", parent=self)
            return
            
        # Abrir diálogo para selecionar arquivo
        filename = filedialog.askopenfilename(
            title="Importar Testes",
            filetypes=[("Arquivos JSON", "*.json"), ("Todos os arquivos", "*.*")],
            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("Erro", "Arquivo inválido: estrutura 'tests' não encontrada.", parent=self)
                return
                
            tests_to_import = import_data['tests']
            if not tests_to_import:
                messagebox.showwarning("Arquivo Vazio", "Nenhum teste encontrado no arquivo.", parent=self)
                return
                
            # Confirmar importação
            result = messagebox.askyesno(
                "Confirmar Importação", 
                f"Importar {len(tests_to_import)} teste(s) de antena?\n\n"
                f"Arquivo: {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("Sucesso", f"{imported_count} teste(s) importado(s) com sucesso.", parent=self)
            
        except json.JSONDecodeError:
            messagebox.showerror("Erro", "Arquivo JSON inválido.", parent=self)
        except Exception as e:
            messagebox.showerror("Erro", f"Falha ao importar testes.\n{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("Nada Selecionado", "Selecione testes no histórico para excluir.", 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("Sucesso", f"{len(selected_ids)} teste(s) excluídos.", 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=[("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
            print("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']:
                print("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
                try:
                    os.startfile(filepath)
                except Exception:
                    pass  # Ignora erro se não conseguir abrir
            else:
                error_msg = result.get('error', 'Erro desconhecido')
                print(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:
            print(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, PageBreak, KeepTogether
            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:
                    print(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:
                    print("Logo Fasttag não encontrado")
            except Exception as e:
                print(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]
            )
            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(KeepTogether(test_table))
            story.append(Spacer(1, 20))

            temp_files = []
            current_plot_type = self.plot_type_var.get()
            charts = generate_all_antenna_charts(selected_tests, current_plot_type)

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

                for i, (name, image_base64) in enumerate(charts):
                    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}"
                    )

                    chart_elements = [
                        Paragraph(f"{self._t('pdf.antenna_chart_title', 'Antenna Check')} - {name}", heading_style),
                        Paragraph(chart_info_text, styles['Normal']),
                        img,
                        Spacer(1, 20)
                    ]
                    story.append(KeepTogether(chart_elements))
            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, 20))
            story.append(Paragraph(self._t('pdf.auto_report_footer', 'Relatório automático gerado pelo FastChecker'), footer_style))
            story.append(PageBreak())
            story.append(Spacer(1, 20))
            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:
                    print(f"⚠️ Erro ao limpar arquivo temporário {temp_file}: {e}")

            return {'success': True}

        except Exception as e:
            print(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:
            print(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:
            print(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.plot_type_var.get() == "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")
            
            print("✅ Zoom in aplicado no Antenna Check")
            
        except Exception as e:
            print(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.plot_type_var.get() == "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")
            
            print("✅ Zoom out aplicado no Antenna Check")
            
        except Exception as e:
            print(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
            plot_type = self.plot_type_var.get()
            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 plot_type == "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")
            
            print("✅ Zoom resetado no Antenna Check")
            
        except Exception as e:
            print(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
                print(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_':
                    print("↷ 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)
                        print(f"✅ Linha de dados incluída: {len(x_line)} pontos, range X: {x_range_line:.1f} MHz")
                    else:
                        print("↷ Linha ignorada (faixa X < 1 MHz)")
            
            if not x_data or not y_data:
                print("❌ 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)
            
            print(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.plot_type_var.get() == "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()
            
            print(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:
            print(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
            print("✅ Modo Pan ativado - clique e arraste para mover o gráfico")
        else:
            # Desativa modo pan
            self.pan_btn.config(relief=tk.RAISED, bg='SystemButtonFace')
            print("✅ 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)
        print("ℹ️ 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()