# simulador_modulo.py
"""
Módulo Simulador de Performance de Tags RFID
Autor: Assistente de IA Gemini
Data: 01 de Setembro de 2025
Versão: 4.0

- v3.7: Versão estável com UI limpa e tooltip funcional.
- v3.8: Corrigido bug onde a interatividade do mouse (tooltip) era
        perdida ao alterar o valor da margem.
- v3.9: Adicionado painel de Risco com imagem na barra lateral.
- v4.0: Corrigido problema onde a imagem de Risco era cortada,
        adicionando redimensionamento automático da imagem.
"""

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import json
import traceback
import math
import time
from datetime import datetime
from .i18n import get_translator, t
from .app_console import fc_log
from .shell_open import open_path_with_default_app
from .embedded_images import get_risk_image
from .simulador_pdf_generator import SimuladorPDFGenerator
from .correction_table import compute_max_fcc_link_reversed_m

# As importações que podem falhar serão feitas depois de a janela principal ser criada
LIBRARIES_OK = False
try:
    import pandas as pd
    import numpy as np
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
    from PIL import Image, ImageTk # <-- Import do Pillow
    LIBRARIES_OK = True
except ImportError:
    pass

STATE_FILE = "simulador_estado.json"


def _format_threshold_test_calibration_display(test_dict):
    """Texto de calibração alinhado ao histórico do Threshold (ficheiro ou perfil)."""
    f = (test_dict.get("calibration_file") or "").strip()
    n = (test_dict.get("calibration") or "").strip()
    if f:
        base = os.path.basename(f.replace("\\", "/"))
        return base if base else f
    if n:
        return n
    _epc = (test_dict.get("epc") or "").strip()
    _epc_low = _epc.casefold()
    is_statistical_row = bool(test_dict.get("is_statistical")) or bool(test_dict.get("stat_type")) or (
        _epc_low.startswith("estatístico") or _epc_low.startswith("estatistico")
    )
    if is_statistical_row:
        return ""
    return t("threshold.calibration_history_none")


def _build_slot_sidebar_label_text(test_record):
    """Label do slot: nome/apelido (se houver), sempre EPC quando existir e não for redundante, descrição extra, calibração."""
    tag = (test_record.get("tag_name") or "").strip()
    desc = (test_record.get("description") or "").strip()
    epc = (test_record.get("epc") or "").strip()
    cal_text = _format_threshold_test_calibration_display(test_record)
    head = tag or desc or epc or "Teste Importado"
    lines = [f"📁 {head}"]
    if epc and epc != head:
        lines.append(f"   {epc}")
    if desc and desc != head and desc != epc:
        lines.append(f"   {desc}")
    if cal_text:
        lines.append(f"   {cal_text}")
    return "\n".join(lines)


def _slot_frame_title(slot_index, test_record):
    """Título curto do LabelFrame do slot."""
    tag = (test_record.get("tag_name") or "").strip()
    desc = (test_record.get("description") or "").strip() or "—"
    head = tag or desc
    cal_short = _format_threshold_test_calibration_display(test_record)
    if len(cal_short) > 36:
        cal_short = cal_short[:33] + "..."
    base = f"{t('simulator.slot')} #{slot_index + 1}: {head}"
    if cal_short:
        return f"{base} — {cal_short}"
    return base


class MissingParametersPopup(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.title(t('simulator.missing_params_title')); self.geometry("350x180"); self.transient(parent); self.grab_set()
        self.distance = None; self.attenuator = None; self.cancelled = True
        main_frame = ttk.Frame(self, padding=15); main_frame.pack(fill='both', expand=True)
        ttk.Label(main_frame, text=t('simulator.missing_params_msg1'), wraplength=320).pack(pady=(0,10))
        ttk.Label(main_frame, text=t('simulator.missing_params_msg2'), wraplength=320).pack(pady=(0,10))
        self.dist_var = tk.StringVar(value="0.25"); self.att_var = tk.StringVar(value="10.0")
        param_frame = ttk.Frame(main_frame); param_frame.pack(fill='x')
        ttk.Label(param_frame, text=t('simulator.distance')).grid(row=0, column=0, sticky='w', padx=5, pady=5)
        ttk.Entry(param_frame, textvariable=self.dist_var, width=10).grid(row=0, column=1, sticky='e', padx=5)
        ttk.Label(param_frame, text=t('simulator.attenuator')).grid(row=1, column=0, sticky='w', padx=5, pady=5)
        ttk.Entry(param_frame, textvariable=self.att_var, width=10).grid(row=1, column=1, sticky='e', padx=5)
        param_frame.columnconfigure(1, weight=1)
        button_frame = ttk.Frame(main_frame); button_frame.pack(side='bottom', fill='x', pady=(10,0))
        ttk.Button(button_frame, text=t('simulator.ok'), command=self.on_ok).pack(side='right')
        ttk.Button(button_frame, text=t('simulator.cancel'), command=self.on_cancel).pack(side='right', padx=5)
        self.protocol("WM_DELETE_WINDOW", self.on_cancel)
    def on_ok(self):
        try:
            self.distance = float(self.dist_var.get()); self.attenuator = float(self.att_var.get())
            self.cancelled = False; self.destroy()
        except ValueError: messagebox.showerror(t('simulator.validation_error'), t('simulator.validation_error_msg'), parent=self)
    def on_cancel(self):
        self.cancelled = True; self.destroy()

class TestSelectorPopup(tk.Toplevel):
    def __init__(self, parent, test_list):
        super().__init__(parent)
        self.title(t('simulator.select_test_title')); self.geometry("600x400"); self.transient(parent); self.grab_set()
        self.selected_test = None; self.test_list = test_list
        main_frame = ttk.Frame(self, padding=10); main_frame.pack(fill='both', expand=True)
        ttk.Label(main_frame, text=t('simulator.select_test_msg'), wraplength=580).pack(fill='x', pady=(0, 10))
        columns = ('id', 'description', 'epc', 'date')
        self.tree = ttk.Treeview(main_frame, columns=columns, show='headings', selectmode='browse')
        self.tree.heading('id', text=t('simulator.id')); self.tree.heading('description', text=t('simulator.description')); self.tree.heading('epc', text=t('simulator.tag_epc')); self.tree.heading('date', text=t('simulator.date'))
        self.tree.column('id', width=50, stretch=False, anchor='center'); self.tree.column('description', width=250); self.tree.column('epc', width=150); self.tree.column('date', width=120)
        for test in self.test_list: self.tree.insert('', 'end', values=(test.get('id', 'N/A'), test.get('description', 'Sem descrição'), test.get('epc', 'N/A'), test.get('date_time', 'N/A')))
        self.tree.pack(fill='both', expand=True)
        button_frame = ttk.Frame(main_frame, padding=(0, 10, 0, 0)); button_frame.pack(fill='x')
        self.ok_button = ttk.Button(button_frame, text=t('simulator.load_selected'), command=self.on_ok, state='disabled'); self.ok_button.pack(side='right')
        ttk.Button(button_frame, text=t('simulator.cancel'), command=self.on_cancel).pack(side='right', padx=5)
        self.tree.bind('<<TreeviewSelect>>', self.on_selection_change); self.tree.bind('<Double-1>', self.on_ok); self.protocol("WM_DELETE_WINDOW", self.on_cancel)
    def on_selection_change(self, event):
        if self.tree.selection(): self.ok_button.config(state='normal')
    def on_ok(self, event=None):
        if not self.tree.selection(): return
        selected_item = self.tree.selection()[0]; selected_index = self.tree.index(selected_item)
        test_to_check = self.test_list[selected_index]
        if test_to_check and isinstance(test_to_check.get('results'), list) and test_to_check.get('results'):
            self.selected_test = test_to_check; self.destroy()
        else:
            desc = test_to_check.get('description', 'N/A')
            messagebox.showwarning(t('simulator.invalid_test'), t('simulator.invalid_test_msg').format(desc=desc), parent=self)
            self.tree.selection_remove(selected_item); self.ok_button.config(state='disabled')
    def on_cancel(self):
        self.selected_test = None; self.destroy()

class TestSelectionPopup(tk.Toplevel):
    def __init__(self, parent, slot_index, projects):
        super().__init__(parent)
        self.title(f"{t('simulator.import_test_for_slot')} #{slot_index + 1}")
        self.geometry("920x600")
        self.transient(parent)
        self.grab_set()
        
        self.slot_index = slot_index
        self.projects = projects
        self.selected_test = None
        
        fc_log("simulator", f"🔄 TestSelectionPopup criado para Slot #{slot_index + 1}")
        fc_log("simulator", f"📊 Projetos recebidos no popup: {len(projects)}")
        for i, project in enumerate(projects):
            fc_log("simulator", f"  {i+1}. {project.get('name', 'Sem nome')} - {project.get('test_count', 0)} testes")
        self.selected_project = None
        self.current_tests = []
        
        self.setup_ui()
        
    def setup_ui(self):
        fc_log("simulator", "🔄 Configurando UI do TestSelectionPopup...")
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill='both', expand=True)
        
        # Título
        title_label = ttk.Label(main_frame, text=f"{t('simulator.import_test_for_slot')} #{self.slot_index + 1}", 
                               font=("Arial", 12, "bold"))
        title_label.pack(pady=(0, 10))
        
        # Frame para seleção de projeto
        project_frame = ttk.LabelFrame(main_frame, text=t('simulator.select_project'), padding=5)
        project_frame.pack(fill='x', pady=(0, 10))
        
        ttk.Label(project_frame, text=t('simulator.project')).pack(anchor='w')
        self.project_var = tk.StringVar()
        
        # Tenta usar tk.OptionMenu em vez de ttk.Combobox
        try:
            self.project_combobox = ttk.Combobox(project_frame, textvariable=self.project_var, 
                                               state="readonly", width=50)
            self.project_combobox.pack(fill='x', pady=(5, 0))
            self.project_combobox.bind('<<ComboboxSelected>>', self.on_project_selected)
            fc_log("simulator", "✅ Usando ttk.Combobox")
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro com ttk.Combobox: {e}")
            # Fallback para tk.OptionMenu
            self.project_combobox = tk.OptionMenu(project_frame, self.project_var, "")
            self.project_combobox.pack(fill='x', pady=(5, 0))
            self.project_var.trace('w', self.on_project_selected)
            fc_log("simulator", "✅ Usando tk.OptionMenu como fallback")
        
        # Popula o dropdown com projetos
        project_names = [f"{p['name']} ({p['test_count']} testes)" for p in self.projects]
        fc_log("simulator", f"📊 Populando dropdown com {len(project_names)} projetos:")
        for i, name in enumerate(project_names):
            fc_log("simulator", f"  {i+1}. {name}")
        
        # Popula dependendo do tipo de widget
        if isinstance(self.project_combobox, ttk.Combobox):
            self.project_combobox['values'] = project_names
            fc_log("simulator", f"✅ ttk.Combobox populado com {len(project_names)} itens")
            
            # Força seleção do primeiro item se houver projetos
            if project_names:
                self.project_var.set(project_names[0])
                fc_log("simulator", f"🔄 Primeiro projeto selecionado: {project_names[0]}")
                # NÃO chama on_project_selected aqui - será chamado após setup_ui completo
            
            self.project_combobox.update()
            fc_log("simulator", f"🔄 Combobox atualizado - valores: {self.project_combobox['values']}")
        else:
            # Para tk.OptionMenu, precisa recriar o menu
            menu = self.project_combobox['menu']
            menu.delete(0, 'end')
            for name in project_names:
                menu.add_command(label=name, command=tk._setit(self.project_var, name))
            fc_log("simulator", f"✅ tk.OptionMenu populado com {len(project_names)} itens")
            
            # Força seleção do primeiro item se houver projetos
            if project_names:
                self.project_var.set(project_names[0])
                fc_log("simulator", f"🔄 Primeiro projeto selecionado: {project_names[0]}")
                # NÃO chama on_project_selected aqui - será chamado após setup_ui completo
        
        # Frame para lista de testes
        tests_frame = ttk.LabelFrame(main_frame, text=t('simulator.test_history'), padding=5)
        tests_frame.pack(fill='both', expand=True, pady=(0, 10))
        
        # Frame interno para TreeView com scrollbar
        tree_container = ttk.Frame(tests_frame)
        tree_container.pack(fill='both', expand=True)
        
        # TreeView para testes (coluna de calibração após EPC, como no Threshold)
        columns = ('description', 'epc', 'calibration', 'date_time', 'type')
        self.tree = ttk.Treeview(tree_container, columns=columns, show='headings', selectmode='browse')
        
        self.tree.heading('description', text=t('simulator.description'))
        self.tree.heading('epc', text=t('simulator.epc'))
        self.tree.heading('calibration', text=t('simulator.calibration_col'))
        self.tree.heading('date_time', text=t('simulator.date_time'))
        self.tree.heading('type', text=t('simulator.type'))
        
        self.tree.column('description', width=180)
        self.tree.column('epc', width=130)
        self.tree.column('calibration', width=160)
        self.tree.column('date_time', width=110)
        self.tree.column('type', width=90)
        
        # Barra de rolagem vertical
        scrollbar = ttk.Scrollbar(tree_container, orient='vertical', command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        # Layout da TreeView e scrollbar
        self.tree.pack(side='left', fill='both', expand=True)
        scrollbar.pack(side='right', fill='y')
        
        self.tree.bind('<<TreeviewSelect>>', self.on_test_selected)
        
        # Frame para informações do teste
        info_frame = ttk.LabelFrame(main_frame, text=t('simulator.test_info'), padding=5)
        info_frame.pack(fill='x', pady=(0, 10))
        
        self.info_text = tk.Text(info_frame, height=5, wrap='word', state='disabled')
        self.info_text.pack(fill='x')
        
        # Botões
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill='x')
        
        self.import_button = ttk.Button(button_frame, text=t('simulator.import_button'), 
                                      command=self.on_import, state='disabled')
        self.import_button.pack(side='right')
        
        ttk.Button(button_frame, text=t('simulator.cancel'), 
                  command=self.on_cancel).pack(side='right', padx=(0, 10))
        
        fc_log("simulator", "✅ UI do TestSelectionPopup configurada completamente")
        
        # Agora que todos os widgets foram criados, pode chamar on_project_selected
        if hasattr(self, 'project_var') and self.project_var.get():
            fc_log("simulator", "🔄 Chamando on_project_selected após setup_ui completo")
            self.on_project_selected()
        
        # Protocolo de fechamento
        self.protocol("WM_DELETE_WINDOW", self.on_cancel)
        
    def on_project_selected(self, event=None):
        """Quando um projeto é selecionado"""
        fc_log("simulator", f"🔄 on_project_selected chamado - event: {event}")
        
        if isinstance(self.project_combobox, ttk.Combobox):
            selection = self.project_combobox.current()
            fc_log("simulator", f"📊 Combobox selection: {selection}")
        else:
            # Para OptionMenu, usa o valor da StringVar
            selected_value = self.project_var.get()
            fc_log("simulator", f"📊 OptionMenu selected_value: {selected_value}")
            # Encontra o índice baseado no valor
            project_names = [f"{p['name']} ({p['test_count']} testes)" for p in self.projects]
            try:
                selection = project_names.index(selected_value)
                fc_log("simulator", f"📊 Índice encontrado: {selection}")
            except ValueError:
                fc_log("simulator", f"⚠️ Valor não encontrado: {selected_value}")
                return
        
        if selection >= 0:
            self.selected_project = self.projects[selection]
            self.current_tests = self.selected_project['data'].get('test_history', [])
            fc_log("simulator", f"✅ Projeto selecionado: {self.selected_project['name']} - {len(self.current_tests)} testes")
            self.populate_tests()
            fc_log("simulator", f"📁 Projeto selecionado: {self.selected_project['name']} ({len(self.current_tests)} testes)")
    
    def populate_tests(self):
        """Popula a lista de testes"""
        # Limpa a árvore
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # Adiciona os testes
        for test in self.current_tests:
            # Determina o tipo de teste baseado nos resultados
            test_type = "N/A"
            if test.get('results'):
                if isinstance(test['results'], list) and test['results']:
                    test_type = "Dados"
                else:
                    test_type = "Vazio"
            
            self.tree.insert('', 'end', values=(
                test.get('description', 'Sem descrição'),
                test.get('epc', 'N/A'),
                _format_threshold_test_calibration_display(test),
                test.get('date_time', 'N/A'),
                test_type
            ))
    
    def on_test_selected(self, event=None):
        """Quando um teste é selecionado"""
        selection = self.tree.selection()
        if selection:
            item = selection[0]
            index = self.tree.index(item)
            test = self.current_tests[index]
            
            # Atualiza informações do teste
            self.update_test_info(test)
            
            # Habilita botão de importar
            self.import_button.config(state='normal')
    
    def update_test_info(self, test):
        """Atualiza as informações do teste selecionado"""
        self.info_text.config(state='normal')
        self.info_text.delete(1.0, tk.END)
        
        info = f"{t('simulator.test_info_desc')} {test.get('description', 'N/A')}\n"
        tag_name = (test.get('tag_name') or '').strip()
        if tag_name:
            info += f"{t('simulator.test_info_tag_name')} {tag_name}\n"
        info += f"{t('simulator.test_info_epc')} {test.get('epc', 'N/A')}\n"
        info += f"{t('simulator.test_info_date')} {test.get('date_time', 'N/A')}\n"
        info += f"{t('simulator.test_info_attenuator')} {test.get('attenuator', 'N/A')} dB\n"
        info += f"{t('simulator.test_info_distance')} {test.get('distance', 'N/A')} m\n"
        _cal_disp = _format_threshold_test_calibration_display(test)
        if _cal_disp:
            info += f"{t('simulator.test_info_calibration')} {_cal_disp}\n"
        info += f"{t('simulator.test_info_duration')} {test.get('test_duration_seconds', 'N/A')} s"
        
        self.info_text.insert(1.0, info)
        self.info_text.config(state='disabled')
    
    def on_import(self):
        """Importa o teste selecionado"""
        selection = self.tree.selection()
        if selection:
            item = selection[0]
            index = self.tree.index(item)
            self.selected_test = self.current_tests[index]
            self.destroy()
    
    def on_cancel(self):
        """Cancela a importação"""
        self.selected_test = None
        self.destroy()

class SimuladorModule(tk.Frame):
    module_name = "Simulator"
    def __init__(self, parent, app_shell=None):
        super().__init__(parent)
        fc_log("simulator", f"🔄 SimuladorModule.__init__ chamado - ID: {id(self)}")
        self.parent = parent
        self.app_shell = app_shell
        # Status inicial de licença (segue padrão dos outros módulos)
        self.is_licensed = False
        try:
            if self.app_shell and hasattr(self.app_shell, 'license_status'):
                self.is_licensed = bool(self.app_shell.license_status)
        except Exception as _e:
            fc_log("simulator", f"⚠️ SimuladorModule: Falha ao ler license_status do AppShell: {_e}")
        self.reader_power_var = tk.StringVar(value="36"); self.reader_sensitivity_var = tk.StringVar(value="-70"); self.margin_db_var = tk.StringVar(value="3.0")
        self._irradiated_power_popup = None
        self.slot_data = [None] * 4; self.annots = [None] * 4; self.toolbars = [None] * 4
        
        # Inicializa variáveis de zoom explicitamente
        self.zoom_factors = [1.0] * 4
        self.zoom_labels = [None] * 4
        fc_log("simulator", "🔍 DEBUG: Variáveis de zoom inicializadas no __init__ do Simulador")
        
        # Atributo para manter a referência da imagem
        self.risk_image = None
        self.risk_image_label = None  # Label que exibe a imagem
        self.risk_frame = None  # Frame que contém a imagem
        # Variáveis para gerenciamento de relatórios
        self.current_report_path = None
        self.current_report_name = None
        # Elementos de UI relacionados ao modo browser
        self.browser_frame = None
        self.browser_mode_label = None
        
        # Sistema de tradução
        self.translator = get_translator()
        self._widget_refs = {}
        self.translator.add_language_change_listener(self._on_language_changed)

        self.setup_ui();
        # Configura mensagem de modo browser e aplica visibilidade inicial
        try:
            self._setup_browser_mode_message()
            self._update_ui_for_license_status()
        except Exception as _e:
            fc_log("simulator", f"⚠️ SimuladorModule: Erro ao configurar mensagem de modo browser: {_e}")
        
        # Atualiza idioma após criar a interface
        self.after(100, self.refresh_language)

        self._load_state()
        # Validação de potência irradiada deve ser executada antes de _on_parameter_change
        self.reader_power_var.trace_add("write", self._validate_irradiated_power)
        self.reader_power_var.trace_add("write", self._on_parameter_change)
        self.reader_sensitivity_var.trace_add("write", self._on_parameter_change)
        self.margin_db_var.trace_add("write", self._on_parameter_change)

    def _save_state(self):
        fc_log("simulator", f"🔄 Salvando estado do Simulador...")
        
        # Verifica se slot_data existe e está inicializado
        if not hasattr(self, 'slot_data'):
            fc_log("simulator", f"⚠️ slot_data não existe, inicializando...")
            self.slot_data = [None] * 4
        
        fc_log("simulator", f"📊 slot_data length: {len(self.slot_data)}")
        
        for i, slot in enumerate(self.slot_data):
            if slot:
                fc_log("simulator", f"📊 Slot {i+1}: {slot.get('description', 'Sem descrição')} - project_path: {slot.get('project_path', 'None')}")
            else:
                fc_log("simulator", f"📊 Slot {i+1}: Vazio")
        
        state = {
            "global_parameters": {
                "reader_power": self.reader_power_var.get(), 
                "reader_sensitivity": self.reader_sensitivity_var.get(), 
                "margin_db": self.margin_db_var.get()
            },
            "slots": [
                {
                    "filepath": slot['filepath'] if slot and 'filepath' in slot else None,
                    "project_path": slot.get('project_path'),
                    "test_id": slot.get('test_id'),
                    "test_description": slot.get('test_description')
                } if slot else {"filepath": None, "project_path": None, "test_id": None, "test_description": None}
                for slot in self.slot_data
            ],
            "current_report": {
                "path": self.current_report_path,
                "name": self.current_report_name
            }
        }
        
        fc_log("simulator", f"📊 Estado a ser salvo: {json.dumps(state, indent=2)}")
        
        try:
            with open(STATE_FILE, 'w', encoding='utf-8') as f: 
                json.dump(state, f, indent=4)
            fc_log("simulator", f"✅ Estado salvo com sucesso em {STATE_FILE}")
        except Exception as e: 
            fc_log("simulator", f"❌ Erro ao salvar estado: {e}")

    def _load_state(self):
        fc_log("simulator", f"🔄 SimuladorModule._load_state chamado - ID: {id(self)}")
        if not os.path.exists(STATE_FILE): 
            fc_log("simulator", f"⚠️ Arquivo de estado não encontrado: {STATE_FILE}")
            return
        try:
            with open(STATE_FILE, 'r', encoding='utf-8') as f: 
                state = json.load(f)
            fc_log("simulator", f"✅ Estado carregado do arquivo: {STATE_FILE}")
            
            # Carrega parâmetros globais
            params = state.get("global_parameters", {})
            self.reader_power_var.set(params.get("reader_power", "36"))
            self.reader_sensitivity_var.set(params.get("reader_sensitivity", "-70"))
            self.margin_db_var.set(params.get("margin_db", "3.0"))
            
            # Carrega slots
            slots_to_load = state.get("slots", [])
            for i, slot_info in enumerate(slots_to_load):
                if i < 4 and slot_info and (slot_info.get("project_path") or slot_info.get("filepath")):
                    # Verifica se é importação de arquivo individual
                    if slot_info.get("filepath") and os.path.exists(slot_info["filepath"]):
                        fc_log("simulator", f"🔄 Carregando arquivo individual para Slot #{i+1}: {slot_info['filepath']}")
                        self._import_file(slot_info["filepath"], i)
                    
                    # Verifica se é importação de projeto
                    elif slot_info.get("project_path") and os.path.exists(slot_info["project_path"]):
                        fc_log("simulator", f"🔄 Carregando projeto para Slot #{i+1}: {slot_info['project_path']}")
                        try:
                            # Carrega o projeto
                            with open(slot_info["project_path"], 'r', encoding='utf-8') as f:
                                project_data = json.load(f)
                            
                            # Encontra o teste específico
                            test_id = slot_info.get("test_id")
                            test_description = slot_info.get("test_description")
                            
                            test_data = None
                            
                            # PRIORIDADE 1: Busca por descrição (mais confiável)
                            if test_description:
                                fc_log("simulator", f"🔍 Buscando teste por descrição: {test_description}")
                                for test in project_data.get('test_history', []):
                                    if test.get('description') == test_description:
                                        test_data = test
                                        fc_log("simulator", f"✅ Teste encontrado por descrição: {test.get('description', 'Sem descrição')} (ID: {test.get('id')})")
                                        break
                            
                            # PRIORIDADE 2: Se não encontrou por descrição, busca por ID
                            if not test_data and test_id:
                                fc_log("simulator", f"🔍 Teste não encontrado por descrição, buscando por ID: {test_id}")
                                for test in project_data.get('test_history', []):
                                    if test.get('id') == test_id:
                                        test_data = test
                                        fc_log("simulator", f"✅ Teste encontrado por ID: {test.get('description', 'Sem descrição')} (ID: {test.get('id')})")
                                        break
                            
                            # PRIORIDADE 3: Se ainda não encontrou, mostra todos os testes disponíveis
                            if not test_data:
                                fc_log("simulator", f"❌ Teste não encontrado! Testes disponíveis no projeto:")
                                for i, test in enumerate(project_data.get('test_history', [])):
                                    fc_log("simulator", f"   {i+1}. ID: {test.get('id')} - Descrição: {test.get('description', 'Sem descrição')}")
                                fc_log("simulator", f"   Procurado: ID={test_id}, Descrição={test_description}")
                            
                            if test_data:
                                fc_log("simulator", f"🔍 DEBUG: Teste encontrado - ID: {test_data.get('id')}, Descrição: {test_data.get('description')}")
                                fc_log("simulator", f"🔍 DEBUG: Procurado - ID: {test_id}, Descrição: {test_description}")
                                
                                # Verifica se já existe um slot com o mesmo teste para evitar duplicação
                                test_already_loaded = False
                                for j, existing_slot in enumerate(self.slot_data):
                                    if (existing_slot and 
                                        existing_slot.get('test_id') == test_data.get('id') and
                                        existing_slot.get('project_path') == slot_info["project_path"] and
                                        j != i):
                                        fc_log("simulator", f"⚠️ Teste já carregado no Slot #{j+1}, pulando Slot #{i+1}")
                                        test_already_loaded = True
                                        break
                                
                                if not test_already_loaded:
                                    fc_log("simulator", f"✅ Teste encontrado: {test_data.get('description', 'Sem descrição')}")
                                    # Adiciona o project_path ao project_data para preservar na importação
                                    project_data['filepath'] = slot_info["project_path"]
                                    # Importa o teste específico (sem mostrar mensagem de sucesso)
                                    self._import_test_to_slot(i, test_data, project_data, show_success_message=False)
                                else:
                                    # Limpa o slot se o teste já está carregado em outro lugar
                                    self.slot_data[i] = None
                            else:
                                fc_log("simulator", f"⚠️ Teste não encontrado no projeto")
                                
                        except Exception as e:
                            fc_log("simulator", f"❌ Erro ao carregar projeto: {e}")
            
            # Carrega informações do relatório atual (sem mostrar texto visível)
            report_info = state.get("current_report", {})
            if report_info.get("path") and os.path.exists(report_info["path"]):
                self.current_report_path = report_info["path"]
                self.current_report_name = report_info["name"]
                self.current_report_label.config(text="", foreground="gray")
            
            # Atualiza os gráficos após carregar todos os dados
            fc_log("simulator", f"🔄 Atualizando gráficos após carregamento do estado...")
            # Atualiza cada slot individualmente
            for i, slot in enumerate(self.slot_data):
                if slot and slot.get('data') is not None:
                    # CORREÇÃO: Verifica se o DataFrame não está vazio de forma segura
                    try:
                        if hasattr(slot['data'], 'empty') and not slot['data'].empty:
                            fc_log("simulator", f"🔄 Atualizando gráfico do Slot #{i+1}")
                            self.run_simulation_and_update_graphs(i)
                    except Exception as e:
                        fc_log("simulator", f"⚠️ Erro ao verificar slot {i+1}: {e}")
                
        except Exception as e: 
            fc_log("simulator", f"Erro ao carregar estado: {e}.")

    def setup_ui(self):
        self.grid_columnconfigure(0, weight=1, minsize=280); self.grid_columnconfigure(1, weight=4, minsize=800); self.grid_rowconfigure(0, weight=1)
        self.setup_sidebar(); self.setup_main_area()

    def setup_sidebar(self):
        sidebar = tk.Frame(self, relief='raised', bd=1, width=280); sidebar.grid(row=0, column=0, sticky='nswe', padx=5, pady=5); sidebar.grid_propagate(False)
        self.params_frame = ttk.LabelFrame(sidebar, text=t('simulator.test_config'), padding=(5, 3)); self.params_frame.pack(fill='x', padx=10, pady=5)
        self._widget_refs['params_frame'] = self.params_frame
        
        power_label = tk.Label(self.params_frame, text=t('simulator.irradiated_power')); power_label.grid(row=0, column=0, sticky='w', pady=1)
        self._widget_refs['power_label'] = power_label
        ttk.Entry(self.params_frame, textvariable=self.reader_power_var, width=10).grid(row=0, column=1, sticky='ew', padx=5)
        
        sensitivity_label = tk.Label(self.params_frame, text=t('simulator.sensitivity')); sensitivity_label.grid(row=1, column=0, sticky='w', pady=1)
        self._widget_refs['sensitivity_label'] = sensitivity_label
        ttk.Entry(self.params_frame, textvariable=self.reader_sensitivity_var, width=10).grid(row=1, column=1, sticky='ew', padx=5)
        
        margin_label = tk.Label(self.params_frame, text=t('simulator.margin')); margin_label.grid(row=2, column=0, sticky='w', pady=1)
        self._widget_refs['margin_label'] = margin_label
        margin_entry = ttk.Entry(self.params_frame, textvariable=self.margin_db_var, width=10)
        margin_entry.grid(row=2, column=1, sticky='ew', padx=5)
        # Validação para aceitar apenas números positivos
        margin_entry.configure(validate='key', validatecommand=(margin_entry.register(self._validate_positive_number), '%P'))

        self._irradiated_power_popup = tk.Label(
            self.params_frame,
            text="",
            bg="#B00020",
            fg="white",
            font=("Segoe UI", 9, "bold"),
            padx=8,
            pady=4,
            wraplength=220,
            justify="left",
        )
        self._irradiated_power_popup.grid(row=3, column=0, columnspan=2, sticky='ew', padx=2, pady=(6, 0))
        self._irradiated_power_popup.grid_remove()
        
        # --- NOVA SEÇÃO: Gerenciamento de Relatórios ---
        reports_frame = ttk.LabelFrame(sidebar, text=t('simulator.reports'), padding=(5, 3))
        reports_frame.pack(fill='x', padx=10, pady=(3, 5))
        self._widget_refs['reports_frame'] = reports_frame
        
        # Botões de relatório em uma linha horizontal
        reports_button_frame = ttk.Frame(reports_frame)
        reports_button_frame.pack(fill='x', padx=3, pady=3)
        
        # Botão Salvar Relatório
        save_report_btn = ttk.Button(reports_button_frame, text=t('simulator.save'), 
                                    command=self.save_simulation_report, 
                                    style='Accent.TButton')
        save_report_btn.pack(side='left', expand=True, fill='x', padx=(0, 2))
        self._widget_refs['save_report_btn'] = save_report_btn
        
        # Botão Importar Relatório
        import_report_btn = ttk.Button(reports_button_frame, text=t('simulator.import'), 
                                      command=self.import_simulation_report)
        import_report_btn.pack(side='left', expand=True, fill='x', padx=2)
        self._widget_refs['import_report_btn'] = import_report_btn
        
        
        # Label para mostrar relatório atual (oculto para economizar espaço)
        self.current_report_label = ttk.Label(reports_frame, text="", 
                                             foreground="gray", wraplength=220)
        self.current_report_label.pack(pady=(0, 0))
        # --- FIM DA NOVA SEÇÃO ---
        
        self.slot_widgets = []; self.slot_frames = []
        for i in range(4):
            slot_frame = ttk.LabelFrame(sidebar, text=f"{t('simulator.slot')} #{i+1}", padding=(5, 3)); slot_frame.pack(fill='x', padx=10, pady=(2, 3)); self.slot_frames.append(slot_frame)
            # Label de arquivo oculto para economizar espaço
            filename_label = ttk.Label(slot_frame, text="", wraplength=220, foreground="gray", font=("Arial", 8)); filename_label.pack(fill='x', pady=0)
            button_frame = ttk.Frame(slot_frame); button_frame.pack(fill='x')
            import_button = ttk.Button(button_frame, text=t('simulator.import_data'), command=lambda idx=i: self.import_data_for_slot(idx), style='Small.TButton'); import_button.pack(side='left', expand=True, fill='x', padx=(0, 1))
            clear_button = ttk.Button(button_frame, text=t('simulator.clear'), command=lambda idx=i: self.clear_slot_data(idx), style='Small.TButton'); clear_button.pack(side='left', expand=True, fill='x', padx=(1, 0))
            self.slot_widgets.append({'label': filename_label, 'frame': slot_frame, 'import_button': import_button, 'clear_button': clear_button})

        # --- INÍCIO DA MODIFICAÇÃO PARA ADICIONAR E REDIMENSIONAR A IMAGEM DE RISCO ---
        # Frame container para título customizado
        risk_container = ttk.Frame(sidebar)
        risk_container.pack(fill='x', padx=10, pady=(2, 5))
        
        # Frame para o título com ícone
        title_frame = ttk.Frame(risk_container)
        title_frame.pack(fill='x', pady=(0, 0))
        
        # Ícone de ampliação (lupa) clicável
        zoom_label = ttk.Label(title_frame, text="🔍", font=("Helvetica", 12), cursor="hand2", foreground="#0066cc")
        zoom_label.pack(side='left', padx=(5, 2))
        
        # Título "RISCO"
        title_label = ttk.Label(title_frame, text=t('simulator.risk'), font=("Helvetica", 9, "bold"))
        title_label.pack(side='left', padx=(2, 5))
        self._widget_refs['risk_title_label'] = title_label
        
        # Frame para a imagem
        risk_frame = ttk.Frame(risk_container, relief='groove', borderwidth=1)
        risk_frame.pack(fill='x', padx=5, pady=(2, 5))
        self.risk_frame = risk_frame  # Armazena referência para atualização
        
        # Adiciona evento de clique no ícone de lupa para abrir imagem ampliada
        zoom_label.bind('<Button-1>', self._show_risk_image_enlarged)
        
        # Carrega a imagem inicial
        self._load_risk_image()

        # Botão para gerar relatório PDF logo abaixo da janela de risco
        pdf_button = ttk.Button(
            risk_container,
            text="Relatório PDF",
            command=self.generate_pdf_report,
            style='Accent.TButton'
        )
        pdf_button.pack(fill='x', padx=5, pady=(0, 5))
        self._widget_refs['pdf_button'] = pdf_button

    def _load_risk_image(self):
        """Carrega a imagem de risco baseada no idioma atual"""
        try:
            # Limpa todos os widgets filhos do risk_frame antes de adicionar novo conteúdo
            if self.risk_frame is not None:
                for widget in self.risk_frame.winfo_children():
                    widget.destroy()
            
            # Remove a referência à imagem anterior
            self.risk_image_label = None
            
            # Obtém a imagem baseada no idioma atual (carrega do módulo embedded_images)
            current_lang = self.translator.get_language()
            img = get_risk_image(current_lang)
            
            if img is not None:
                # Redimensiona a imagem para um tamanho adequado (250 pixels de largura)
                # Mantendo a proporção original
                original_width, original_height = img.size
                new_width = 250  # Largura desejada em pixels
                new_height = int((original_height / original_width) * new_width)
                
                # Image.LANCZOS é um filtro de alta qualidade para redimensionamento
                img = img.resize((new_width, new_height), Image.LANCZOS)
                
                # Converte para PhotoImage e mantém referência
                self.risk_image = ImageTk.PhotoImage(img)
                
                # Cria um label para exibir a imagem diretamente na tela
                if self.risk_frame is not None:
                    self.risk_image_label = ttk.Label(self.risk_frame, image=self.risk_image)
                    self.risk_image_label.pack(pady=2, padx=3)
                    fc_log("simulator", f"✅ Imagem de risco carregada com sucesso (idioma: {current_lang})")
                else:
                    fc_log("simulator", f"⚠️ risk_frame não está disponível ainda")
                
            else:
                # Se a imagem não for carregada, exibe uma mensagem de erro
                if self.risk_frame is not None:
                    error_label = ttk.Label(self.risk_frame, text=f"{t('simulator.image_load_error')}", foreground="red", wraplength=220)
                    error_label.pack(pady=5, padx=3)
                    fc_log("simulator", f"❌ Erro ao carregar imagem de risco")
                
        except Exception as e:
            # Captura outras exceções (ex: Pillow não instalado)
            if self.risk_frame is not None:
                error_label = ttk.Label(self.risk_frame, text=f"{t('simulator.image_load_error')}\n{str(e)}", foreground="red", wraplength=220)
                error_label.pack(pady=5, padx=3)
            fc_log("simulator", f"❌ Erro ao carregar imagem de risco: {e}")

    def _show_risk_image_enlarged(self, event=None):
        """Mostra a imagem de Risco ampliada em uma janela popup (dobro do tamanho)"""
        try:
            # Obtém a imagem baseada no idioma atual (carrega do módulo embedded_images)
            current_lang = self.translator.get_language()
            img = get_risk_image(current_lang)
            
            if img is None:
                fc_log("simulator", f"❌ Imagem de Risco não encontrada")
                return
            
            # Cria janela popup
            popup = tk.Toplevel(self)
            popup.title(t('simulator.risk_image_enlarged'))
            popup.transient(self)
            popup.grab_set()
            
            # Usa a imagem carregada do módulo embedded_images
            
            # Redimensiona para o dobro do tamanho (500 pixels ao invés de 250)
            original_width, original_height = img.size
            new_width = 500  # Dobro do tamanho original (250 * 2)
            new_height = int((original_height / original_width) * new_width)
            
            img_enlarged = img.resize((new_width, new_height), Image.LANCZOS)
            
            # Converte para PhotoImage
            photo = ImageTk.PhotoImage(img_enlarged)
            
            # Cria label para exibir a imagem
            label = ttk.Label(popup, image=photo)
            label.image = photo  # Mantém referência para evitar garbage collection
            label.pack(padx=10, pady=10)
            
            # Botão para fechar
            close_btn = ttk.Button(popup, text=t('simulator.close'), command=popup.destroy)
            close_btn.pack(pady=(0, 10))
            
            # Centraliza a janela na tela
            popup.update_idletasks()
            width = popup.winfo_width()
            height = popup.winfo_height()
            x = (popup.winfo_screenwidth() // 2) - (width // 2)
            y = (popup.winfo_screenheight() // 2) - (height // 2)
            popup.geometry(f'{width}x{height}+{x}+{y}')
            
            fc_log("simulator", "✅ Imagem de Risco ampliada exibida")
            
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao exibir imagem ampliada: {e}")

    def _setup_browser_mode_message(self):
        """Configura mensagem de 'modo browser' como nos outros módulos."""
        try:
            target_parent = getattr(self, 'params_frame', self)
            self.browser_frame = ttk.Frame(target_parent)
            bullet_label = ttk.Label(self.browser_frame, text="•", foreground="blue", font=("Helvetica", 10))
            bullet_label.pack(side='left', anchor='w')
            self.browser_mode_label = ttk.Label(self.browser_frame, text=f" {t('simulator.browser_mode')}", foreground="blue", font=("Helvetica", 10))
            self._widget_refs['browser_mode_label'] = self.browser_mode_label
            self.browser_mode_label.pack(side='left', anchor='w')
        except Exception as e:
            fc_log("simulator", f"⚠️ SimuladorModule: Erro ao criar mensagem de modo browser: {e}")

    def _update_ui_for_license_status(self):
        """Mostra/oculta mensagem de 'modo browser' baseada na licença."""
        try:
            if not hasattr(self, 'browser_frame') or self.browser_frame is None:
                return
            if self.is_licensed:
                # Esconde quando há licença
                try:
                    self.browser_frame.pack_forget()
                except Exception:
                    pass
                fc_log("simulator", "✅ SimuladorModule: Mensagem de modo browser ocultada (licença ativa)")
            else:
                # Mostra quando não há licença
                self.browser_frame.pack(fill="x", pady=(5, 5), padx=5)
                fc_log("simulator", "⚠️ SimuladorModule: Mensagem de modo browser visível (sem licença)")
        except Exception as e:
            fc_log("simulator", f"⚠️ SimuladorModule: Erro ao atualizar UI para licença: {e}")

    def update_license_status(self, new_license_status):
        """Recebe atualizações de licença do AppShell e atualiza a UI."""
        try:
            fc_log("simulator", f"🔔 SimuladorModule: Atualizando status da licença para {new_license_status}")
            self.is_licensed = bool(new_license_status)
            self._update_ui_for_license_status()
            fc_log("simulator", "✅ SimuladorModule: Status da licença atualizado")
        except Exception as e:
            fc_log("simulator", f"❌ SimuladorModule: Erro ao atualizar status da licença: {e}")

    def _on_language_changed(self, old_language=None, new_language=None):
        """Callback quando o idioma é alterado"""
        try:
            self.refresh_language()
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro ao atualizar idioma no Simulator: {e}")
    
    def refresh_language(self):
        """Atualiza todos os textos da interface quando o idioma é alterado"""
        try:
            # Atualiza títulos de frames
            if 'params_frame' in self._widget_refs:
                self._widget_refs['params_frame'].config(text=t('simulator.test_config'))
            if 'reports_frame' in self._widget_refs:
                self._widget_refs['reports_frame'].config(text=t('simulator.reports'))
            
            # Atualiza labels
            if 'power_label' in self._widget_refs:
                self._widget_refs['power_label'].config(text=t('simulator.irradiated_power'))
            if 'sensitivity_label' in self._widget_refs:
                self._widget_refs['sensitivity_label'].config(text=t('simulator.sensitivity'))
            if 'margin_label' in self._widget_refs:
                self._widget_refs['margin_label'].config(text=t('simulator.margin'))
            
            # Atualiza botões
            if 'save_report_btn' in self._widget_refs:
                self._widget_refs['save_report_btn'].config(text=t('simulator.save'))
            if 'import_report_btn' in self._widget_refs:
                self._widget_refs['import_report_btn'].config(text=t('simulator.import'))
            
            # Atualiza slots (mantém título/descrição quando há teste carregado)
            if hasattr(self, 'slot_widgets'):
                for i, slot_widget in enumerate(self.slot_widgets):
                    if 'import_button' in slot_widget:
                        slot_widget['import_button'].config(text=t('simulator.import_data'))
                    if 'clear_button' in slot_widget:
                        slot_widget['clear_button'].config(text=t('simulator.clear'))
                    if 'frame' in slot_widget:
                        if hasattr(self, 'slot_data') and i < len(self.slot_data) and self.slot_data[i] is not None:
                            self._update_slot_display(i, self._slot_test_record_from_stored(self.slot_data[i]))
                        else:
                            slot_widget['frame'].config(text=f"{t('simulator.slot')} #{i+1}")
                            if 'label' in slot_widget:
                                slot_widget['label'].config(text="", foreground="gray")
            
            # Atualiza título de risco
            if 'risk_title_label' in self._widget_refs:
                self._widget_refs['risk_title_label'].config(text=t('simulator.risk'))
            
            # Atualiza imagem de risco baseada no idioma
            try:
                self._load_risk_image()
            except Exception as img_error:
                fc_log("simulator", f"⚠️ Erro ao atualizar imagem de risco no refresh_language: {img_error}")
            
            # Atualiza mensagem de modo browser
            if 'browser_mode_label' in self._widget_refs:
                self._widget_refs['browser_mode_label'].config(text=f" {t('simulator.browser_mode')}")

            if getattr(self, '_irradiated_power_popup', None) and self._irradiated_power_popup.winfo_ismapped():
                max_power = self._get_max_irradiated_power_limit()
                if max_power is not None:
                    self._irradiated_power_popup.config(
                        text=t('simulator.warning_irradiated_power_limit_popup').format(max_power=max_power)
                    )
            
            # Atualiza os gráficos para refletir mudanças de idioma
            try:
                self.update_graphs()
            except Exception as graph_error:
                fc_log("simulator", f"⚠️ Erro ao atualizar gráficos no refresh_language: {graph_error}")
            
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro ao atualizar idioma no Simulator: {e}")
            import traceback
            traceback.print_exc()

    def destroy(self):
        """Chamado quando o módulo é destruído - salva estado automaticamente"""
        fc_log("simulator", f"🔄 SimuladorModule.destroy chamado - ID: {id(self)}")
        try:
            self._close_irradiated_power_popup()
            # Remove listener de mudança de idioma
            if hasattr(self, 'translator') and self.translator:
                try:
                    self.translator.remove_language_change_listener(self._on_language_changed)
                except:
                    pass
            
            self._save_state()
            fc_log("simulator", f"✅ Estado salvo automaticamente ao destruir módulo Simulador")
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao salvar estado ao destruir módulo: {e}")
        super().destroy()

    def _validate_positive_number(self, value):
        """Valida se o valor é um número positivo válido"""
        if value == "" or value == "-":  # Permite campo vazio e sinal negativo temporário
            return True
        try:
            num = float(value)
            if num < 0:  # Se for negativo, mostra alerta e rejeita
                self._show_negative_number_alert()
                return False
            return True  # Aceita números >= 0
        except ValueError:
            return False

    def _show_negative_number_alert(self):
        """Mostra popup de alerta para números negativos"""
        import tkinter.messagebox as msgbox
        msgbox.showwarning(
            t('simulator.invalid_value'), 
            t('simulator.invalid_value_msg')
        )

    def _close_irradiated_power_popup(self, popup_ref=None):
        popup = popup_ref or self._irradiated_power_popup
        if popup and popup.winfo_exists():
            popup.grid_remove()

    def _show_irradiated_power_limit_popup(self, max_power):
        """Mostra popup persistente dentro da configuração do teste."""
        try:
            if not self._irradiated_power_popup or not self._irradiated_power_popup.winfo_exists():
                return
            self._irradiated_power_popup.config(
                text=t('simulator.warning_irradiated_power_limit_popup').format(max_power=max_power)
            )
            self._irradiated_power_popup.grid()
        except Exception as e:
            fc_log("simulator", f"⚠️ Falha ao exibir popup de potência: {e}")

    def _get_max_irradiated_power_limit(self):
        """Obtém o limite máximo de potência irradiada baseado no tipo de licença"""
        try:
            # Se não há licença, retorna None (sem validação)
            if not self.is_licensed or not self.app_shell:
                return None
            
            # Obtém limites da licença
            valid_lics = ["Simulator", "FastChecker"]
            license_limits = self.app_shell._calculate_license_limits(valid_lics)
            
            if not license_limits.get('is_licensed', False):
                return None
            
            # Obtém o nome da licença
            license_name = license_limits.get('license_name', '')
            
            # Requisito: licença ETSI limita em 33 dBm; demais licenças ativas limitam em 36 dBm.
            if license_name and 'ETSI' in license_name.upper():
                return 33
            return 36
        
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro ao obter limite de potência irradiada: {e}")
            return None
    
    def _validate_irradiated_power(self, *args):
        """Valida o valor de potência irradiada baseado nos limites da licença"""
        try:
            power_str = self.reader_power_var.get()
            
            # Permite campo vazio durante digitação
            if not power_str or power_str.strip() == "":
                self._close_irradiated_power_popup()
                return
            
            try:
                power_value = float(power_str)
            except ValueError:
                # Valor inválido, mas não bloqueia para permitir edição
                self._close_irradiated_power_popup()
                return
            
            # Obtém limite máximo
            max_power = self._get_max_irradiated_power_limit()
            
            if max_power is None:
                # Sem licença ou erro, não valida
                self._close_irradiated_power_popup()
                return
            
            # Valida se excede o limite
            if power_value > max_power:
                self._show_irradiated_power_limit_popup(max_power)
            else:
                self._close_irradiated_power_popup()
        
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro ao validar potência irradiada: {e}")

    def _on_parameter_change(self, *args):
        """Chamado quando qualquer parâmetro é alterado - executa simulação e salva estado"""
        try:
            # Executa a simulação
            self.run_simulation_and_update_graphs()
            # Salva o estado automaticamente
            self._save_state()
            fc_log("simulator", f"✅ Estado salvo automaticamente após mudança de parâmetro")
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao salvar estado automaticamente: {e}")

    def save_simulation_report(self):
        """Salva o relatório atual da simulação"""
        try:
            # Solicita local para salvar
            filepath = filedialog.asksaveasfilename(
                title=t('simulator.save_report_title'),
                defaultextension=".json",
                filetypes=[
                    (t('export_popup.filetype_json'), "*.json"),
                    (t('export_popup.filetype_all'), "*.*"),
                ],
                initialfile=f"Simulator {datetime.now().strftime('%d.%m.%y_%H.%M.%S')}.json",
                parent=self,
            )
            
            if not filepath:
                return
                
            # Prepara dados para salvar
            report_data = {
                "metadata": {
                    "timestamp": datetime.now().isoformat(),
                    "version": "1.0",
                    "module": "Simulator RFID"
                },
                "global_parameters": {
                    "reader_power": self.reader_power_var.get(),
                    "reader_sensitivity": self.reader_sensitivity_var.get(),
                    "margin_db": self.margin_db_var.get()
                },
                "slots_data": [],
                "simulation_results": {}
            }
            
            # Adiciona dados dos slots
            for i, slot in enumerate(self.slot_data):
                if slot is not None:
                    slot_info = {
                        "slot_index": i,
                        "filepath": slot.get('filepath', ''),
                        "project_path": slot.get('project_path', ''),
                        "test_id": slot.get('test_id', ''),
                        "test_description": slot.get('test_description', ''),
                        "description": slot['params'].get('description', ''),
                        "distance": slot['params'].get('d_med', 0),
                        "attenuator": slot['params'].get('atenuador', 0),
                        "data_points": len(slot['data']) if 'data' in slot else 0
                    }
                    report_data["slots_data"].append(slot_info)
                    
                    # Adiciona resultados da simulação se disponível
                    if 'data' in slot and hasattr(slot['data'], 'empty') and not slot['data'].empty:
                        df = slot['data']
                        if 'Forward Link' in df.columns:
                            report_data["simulation_results"][f"slot_{i}"] = {
                                "forward_link_max": float(df['Forward Link'].max()),
                                "reverse_link_max": float(df['Reverse Link'].max()),
                                "link_final_max": float(df['Link Final'].max()),
                                "frequency_range": {
                                    "min": float(df['Frequência'].min()),
                                    "max": float(df['Frequência'].max())
                                }
                            }
            
            # Salva o arquivo
            with open(filepath, 'w', encoding='utf-8') as f:
                json.dump(report_data, f, indent=2, ensure_ascii=False)
            
            # Atualiza estado (sem mostrar texto visível)
            self.current_report_path = filepath
            self.current_report_name = os.path.basename(filepath)
            self.current_report_label.config(text="", foreground="gray")
            
            # Mensagem removida para economizar espaço - relatório salvo com sucesso
            
            # Salva estado após salvar relatório
            self._save_state()
            fc_log("simulator", f"✅ Relatório salvo: {filepath}")
            fc_log("simulator", f"✅ Estado salvo automaticamente após salvar relatório")
            
        except Exception as e:
            messagebox.showerror(
                t('simulator.save_error'), 
                t('simulator.save_error_msg').format(error=str(e)),
                parent=self,
            )
            fc_log("simulator", f"❌ Erro ao salvar relatório: {e}")

    def generate_pdf_report(self):
        """Gera um relatório PDF completo da simulação atual."""
        try:
            # Verifica se há ao menos um slot com dados válidos
            has_data = any(
                slot and 'data' in slot and hasattr(slot['data'], 'empty') and not slot['data'].empty
                for slot in self.slot_data
            )
            if not has_data:
                messagebox.showinfo(
                    t('simulator.pdf_report_title'),
                    t('simulator.pdf_no_data_msg'),
                    parent=self
                )
                return

            # Solicita local para salvar o PDF
            filepath = filedialog.asksaveasfilename(
                title=t('simulator.save_pdf_file_title'),
                defaultextension=".pdf",
                filetypes=[
                    (t('fastthreshold.pdf_files'), "*.pdf"),
                    (t('export_popup.filetype_all'), "*.*"),
                ],
                initialfile=f"Relatório_Simulador_{datetime.now().strftime('%d.%m.%y_%H.%M.%S')}.pdf",
                parent=self,
            )
            if not filepath:
                return

            # Monta estrutura de dados esperada pelo SimuladorPDFGenerator
            # Obtém o texto da licença da MESMA forma que os outros relatórios (Noise, Threshold, etc),
            # usando LicenseManager.get_active_license_system_info, que devolve o "License Type" ativo.
            license_label = t('simulator.pdf_license_fallback_browser')
            try:
                if self.app_shell and hasattr(self.app_shell, "license_manager") and self.app_shell.license_manager:
                    system_info = self.app_shell.license_manager.get_active_license_system_info()
                    if isinstance(system_info, dict):
                        lic_text = system_info.get("license") or ""
                        if isinstance(lic_text, str) and lic_text.strip():
                            license_label = lic_text.strip()
                        elif getattr(self, "is_licensed", False):
                            license_label = t('simulator.pdf_license_fallback_licensed')
            except Exception as lic_err:
                fc_log("simulator", f"⚠️ SimuladorModule: falha ao obter texto da licença para o PDF: {lic_err}")

            simulador_data = {
                "reader_power": float(self.reader_power_var.get() or 0),
                "reader_sensitivity": float(self.reader_sensitivity_var.get() or 0),
                "margin_db": float(self.margin_db_var.get() or 0),
                "slot_data": self.slot_data,
                "license": license_label,
            }

            generator = SimuladorPDFGenerator()
            result = generator.generate_pdf_report(simulador_data, filepath)

            # O gerador retorna True/False; mantém compatibilidade com dicionionário se mudar no futuro
            success = False
            error_msg = ""
            if isinstance(result, dict):
                success = bool(result.get("success"))
                error_msg = result.get("error", "")
            else:
                success = bool(result)

            if success:
                # Mostra mensagem de sucesso primeiro (como nos outros módulos)
                messagebox.showinfo(
                    t('simulator.pdf_report_title'),
                    t('simulator.pdf_success_msg').format(filepath=filepath),
                    parent=self
                )
                # Em seguida, tenta abrir o PDF automaticamente (fallbacks se startfile falhar)
                if not open_path_with_default_app(filepath, log_tag="simulator"):
                    fc_log("simulator", "Nao foi possivel abrir o PDF automaticamente (todos os metodos falharam)")
            else:
                messagebox.showerror(
                    t('simulator.pdf_generate_error'),
                    t('simulator.pdf_generate_error_msg').format(
                        error=error_msg or t('simulator.pdf_error_unknown')
                    ),
                    parent=self
                )
        except Exception as e:
            messagebox.showerror(
                t('simulator.pdf_generate_error'),
                t('simulator.pdf_generate_error_msg').format(error=str(e)),
                parent=self
            )
            fc_log("simulator", f"❌ Erro ao gerar relatório PDF do Simulador: {e}")

    def import_simulation_report(self):
        """Importa um relatório de simulação salvo"""
        try:
            filepath = filedialog.askopenfilename(
                title=t('simulator.import_report_title'),
                filetypes=[
                    (t('export_popup.filetype_json'), "*.json"),
                    (t('export_popup.filetype_all'), "*.*"),
                ],
                parent=self,
            )
            
            if not filepath:
                return
                
            # Carrega o relatório
            with open(filepath, 'r', encoding='utf-8') as f:
                report_data = json.load(f)
            
            # Verifica se é um relatório válido
            if not isinstance(report_data, dict) or 'global_parameters' not in report_data:
                messagebox.showerror(
                    t('simulator.invalid_format'), 
                    t('simulator.invalid_format_msg')
                )
                return
            
            # Aplica parâmetros globais
            params = report_data.get('global_parameters', {})
            if 'reader_power' in params:
                self.reader_power_var.set(params['reader_power'])
            if 'reader_sensitivity' in params:
                self.reader_sensitivity_var.set(params['reader_sensitivity'])
            if 'margin_db' in params:
                self.margin_db_var.set(params['margin_db'])
            
            # Tenta carregar dados dos slots se disponível
            slots_data = report_data.get('slots_data', [])
            fc_log("simulator", f"🔍 DEBUG: Encontrados {len(slots_data)} slots no relatório")
            if slots_data:
                for slot_info in slots_data:
                    slot_index = slot_info.get('slot_index', 0)
                    filepath_slot = slot_info.get('filepath', '')
                    project_path = slot_info.get('project_path', '')
                    test_id = slot_info.get('test_id', '')
                    test_description = slot_info.get('test_description', '')
                    
                    fc_log("simulator", f"🔍 DEBUG Slot {slot_index + 1}:")
                    fc_log("simulator", f"   - filepath_slot: {filepath_slot}")
                    fc_log("simulator", f"   - project_path: {project_path}")
                    fc_log("simulator", f"   - test_id: {test_id}")
                    fc_log("simulator", f"   - test_description: {test_description}")
                    
                    # Tenta carregar por arquivo individual primeiro
                    if filepath_slot and os.path.exists(filepath_slot):
                        try:
                            fc_log("simulator", f"🔄 Tentando carregar arquivo: {filepath_slot}")
                            self._import_file(filepath_slot, slot_index, show_success_msg=False)
                            fc_log("simulator", f"✅ Slot {slot_index + 1} carregado (arquivo): {os.path.basename(filepath_slot)}")
                        except Exception as slot_error:
                            fc_log("simulator", f"❌ Erro ao carregar slot {slot_index + 1} (arquivo): {slot_error}")
                            import traceback
                            traceback.print_exc()
                    
                    # CORREÇÃO: Busca arquivo em locais alternativos se não existir
                    elif project_path and test_id:
                        # Tenta o caminho original primeiro
                        if os.path.exists(project_path):
                            actual_project_path = project_path
                        else:
                            # Busca em locais alternativos
                            filename = os.path.basename(project_path)
                            possible_paths = [
                                # Mesmo diretório do relatório atual
                                os.path.join(os.path.dirname(filepath), filename),
                                # Downloads do usuário atual
                                os.path.join(os.path.expanduser("~/Downloads"), filename),
                                # Downloads do usuário genérico
                                os.path.join("C:/Users/User/Downloads", filename),
                                # Diretório atual
                                filename,
                                # Pasta do projeto
                                os.path.join(os.path.dirname(__file__), "..", "..", "..", filename)
                            ]
                            
                            actual_project_path = None
                            for possible_path in possible_paths:
                                if os.path.exists(possible_path):
                                    actual_project_path = possible_path
                                    fc_log("simulator", f"🔍 Arquivo encontrado em: {possible_path}")
                                    break
                        
                        if actual_project_path and os.path.exists(actual_project_path):
                            try:
                                fc_log("simulator", f"🔄 Tentando carregar projeto: {actual_project_path}")
                                # Carrega o projeto
                                with open(actual_project_path, 'r', encoding='utf-8') as f:
                                    project_data = json.load(f)
                                
                                # Encontra o teste específico - PRIORIDADE para descrição
                                test_data = None
                                for test in project_data.get('test_history', []):
                                    # PRIORIDADE 1: Busca por descrição (mais confiável)
                                    if test_description and test.get('description') == test_description:
                                        test_data = test
                                        fc_log("simulator", f"🔍 DEBUG: Teste encontrado por descrição: {test.get('description')} (ID: {test.get('id')})")
                                        break
                                    # PRIORIDADE 2: Se não encontrou por descrição, busca por ID
                                    elif test_id and test.get('id') == test_id and not test_data:
                                        test_data = test
                                        fc_log("simulator", f"🔍 DEBUG: Teste encontrado por ID: {test.get('description')} (ID: {test.get('id')})")
                                
                                if test_data:
                                    # Adiciona o project_path ao project_data
                                    project_data['filepath'] = actual_project_path
                                    # Importa o teste específico
                                    self._import_test_to_slot(slot_index, test_data, project_data, show_success_message=False)
                                    fc_log("simulator", f"✅ Slot {slot_index + 1} carregado (projeto): {test_description}")
                                else:
                                    fc_log("simulator", f"⚠️ Teste não encontrado no projeto para Slot {slot_index + 1}")
                                    
                            except Exception as slot_error:
                                fc_log("simulator", f"❌ Erro ao carregar slot {slot_index + 1} (projeto): {slot_error}")
                                import traceback
                                traceback.print_exc()
                    
                    else:
                        fc_log("simulator", f"⚠️ Slot {slot_index + 1} não pode ser carregado - arquivo/projeto não encontrado")
                        fc_log("simulator", f"   - filepath_slot existe: {filepath_slot and os.path.exists(filepath_slot) if filepath_slot else False}")
                        fc_log("simulator", f"   - project_path existe: {project_path and os.path.exists(project_path) if project_path else False}")
                        fc_log("simulator", f"   - test_id presente: {bool(test_id)}")
            
            # Atualiza estado (sem mostrar texto visível)
            self.current_report_path = filepath
            self.current_report_name = os.path.basename(filepath)
            self.current_report_label.config(text="", foreground="gray")
            
            # DEBUG: Verifica estado dos slots antes da simulação
            fc_log("simulator", f"🔍 DEBUG: Estado dos slots antes da simulação:")
            if hasattr(self, 'slot_data'):
                for i, slot in enumerate(self.slot_data):
                    if slot is not None:
                        data_count = len(slot.get('data', []))
                        fc_log("simulator", f"   Slot {i + 1}: Dados carregados ({data_count} pontos)")
                    else:
                        fc_log("simulator", f"   Slot {i + 1}: Vazio")
            else:
                fc_log("simulator", "   slot_data não existe!")
            
            # Executa simulação com os dados carregados
            try:
                self.run_simulation_and_update_graphs()
            except Exception as sim_error:
                fc_log("simulator", f"❌ Erro na simulação: {sim_error}")
                import traceback
                traceback.print_exc()
            
            # Salva estado após importar relatório
            self._save_state()
            
            # CORREÇÃO: Conta quantos slots foram realmente carregados
            loaded_slots = 0
            for slot_info in slots_data:
                slot_index = slot_info.get('slot_index', 0)
                # CORREÇÃO: Verifica se o slot realmente tem dados carregados de forma segura
                slot_data = self.slot_data[slot_index].get('data') if self.slot_data[slot_index] else None
                if (hasattr(self, 'slot_data') and 
                    self.slot_data[slot_index] is not None and 
                    slot_data is not None and
                    (not hasattr(slot_data, 'empty') or not slot_data.empty)):
                    loaded_slots += 1
                    fc_log("simulator", f"✅ Slot {slot_index + 1} confirmado como carregado")
                else:
                    fc_log("simulator", f"❌ Slot {slot_index + 1} não foi carregado com sucesso")
            
            messagebox.showinfo(
                "Relatório Importado", 
                f"Relatório importado com sucesso!\n\nArquivo: {self.current_report_name}\n\nParâmetros globais aplicados.\nSlots carregados: {loaded_slots}/{len(slots_data)}"
            )
            
            fc_log("simulator", f"✅ Relatório importado: {filepath}")
            fc_log("simulator", f"✅ Estado salvo automaticamente após importar relatório")
            
        except Exception as e:
            messagebox.showerror(
                "Erro ao Importar", 
                f"Erro ao importar relatório:\n{str(e)}"
            )
            fc_log("simulator", f"❌ Erro ao importar relatório: {e}")

    def setup_main_area(self):
        main_area = tk.Frame(self, bg='white'); main_area.grid(row=0, column=1, sticky='nsew', padx=5, pady=5)
        main_area.grid_columnconfigure((0, 1), weight=1); main_area.grid_rowconfigure((0, 1), weight=1)
        self.graph_canvases = []; self.graph_axes = []
        self.pan_active = [False] * 4  # Estado de pan para cada slot
        self.pan_btns = []  # Botões de pan para cada slot
        self.pan_start = [None] * 4  # Posição inicial do pan
        for i in range(4):
            row, col = divmod(i, 2)
            graph_frame = ttk.Frame(main_area, padding=5); graph_frame.grid(row=row, column=col, sticky='nsew')
            graph_frame.grid_rowconfigure(1, weight=1); graph_frame.grid_columnconfigure(0, weight=1)
            
            # Frame para controles de zoom personalizados
            zoom_frame = ttk.Frame(graph_frame)
            zoom_frame.grid(row=0, column=0, sticky='ew', pady=(0, 5))
            
            # Botões de zoom personalizados
            ttk.Button(zoom_frame, text="🔍+", command=lambda idx=i: self._zoom_in(idx), 
                      width=4).pack(side='left', padx=2)
            ttk.Button(zoom_frame, text="🔍-", command=lambda idx=i: self._zoom_out(idx), 
                      width=4).pack(side='left', padx=2)
            ttk.Button(zoom_frame, text="↻", command=lambda idx=i: self._reset_zoom(idx), 
                      width=4).pack(side='left', padx=2)
            fc_log("simulator", f"🔍 DEBUG: Botão reset zoom configurado para slot {i + 1}")
            ttk.Button(zoom_frame, text="📐", command=lambda idx=i: self._fit_to_view(idx), 
                      width=4).pack(side='left', padx=2)
            # Botão Pan (arrastar)
            pan_btn = tk.Button(zoom_frame, text="✋", width=3, height=1,
                               command=lambda idx=i: self._toggle_pan(idx),
                               font=("Arial", 10), relief=tk.RAISED)
            pan_btn.pack(side='left', padx=2)
            self.pan_btns.append(pan_btn)
            
            # Label para mostrar status do zoom
            zoom_label = ttk.Label(zoom_frame, text="Zoom: 1.0x", font=("Arial", 8))
            zoom_label.pack(side='right', padx=5)
            
            fig = Figure(dpi=100); ax = fig.add_subplot(111)
            canvas = FigureCanvasTkAgg(fig, master=graph_frame); canvas.get_tk_widget().grid(row=1, column=0, sticky='nsew')
            canvas.mpl_connect("motion_notify_event", lambda event, idx=i: self.on_motion(event, idx))
            
            # Toolbar padrão do matplotlib removida - mantemos apenas os controles de zoom personalizados
            self.toolbars[i] = None
            self.graph_canvases.append(canvas); self.graph_axes.append(ax)
            
            # Armazena referências para controles de zoom
            self.zoom_labels[i] = zoom_label
        self.update_graphs()

    def on_motion(self, event, slot_index):
        ax = self.graph_axes[slot_index]; canvas = self.graph_canvases[slot_index]; annot = self.annots[slot_index]
        visible = annot.get_visible() if annot else False
        if event.inaxes == ax:
            for line in ax.get_lines():
                cont, ind = line.contains(event)
                if cont:
                    x, y = line.get_data(); idx = ind["ind"][0]
                    if annot is None:
                        self.annots[slot_index] = 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)
                        annot = self.annots[slot_index]
                    
                    # Usa posicionamento inteligente
                    tooltip_text = f"Frequência: {x[idx]:.2f} MHz\nDistância: {y[idx]:.2f} m"
                    self._position_tooltip_smartly(event, x[idx], y[idx], tooltip_text, slot_index)
                    return
        if visible: annot.set_visible(False); canvas.draw_idle()

    def _create_and_position_tooltip(self, event, x_pos, y_pos, tooltip_text, slot_index):
        """Cria e posiciona o tooltip de forma inteligente"""
        try:
            ax = self.graph_axes[slot_index]
            canvas = self.graph_canvases[slot_index]
            
            # Remove tooltip anterior se existir
            if self.annots[slot_index]:
                self.annots[slot_index].remove()
                self.annots[slot_index] = None
            
            # Obtém dimensões do canvas em pixels
            canvas_width = canvas.get_width_height()[0]
            canvas_height = 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
            
            # Cria novo tooltip com posicionamento inteligente
            self.annots[slot_index] = ax.annotate(
                tooltip_text, 
                xy=(x_pos, y_pos), 
                xytext=xytext, 
                textcoords="offset points",
                bbox=dict(boxstyle="round,pad=0.5", fc="yellow", alpha=0.8), 
                arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=0"),
                ha=ha
            )
            
            # Força redesenho para garantir que o tooltip apareça
            canvas.draw_idle()
            
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro ao criar tooltip: {e}")
            # Fallback para posicionamento padrão
            try:
                if self.annots[slot_index]:
                    self.annots[slot_index].remove()
                self.annots[slot_index] = ax.annotate(
                    tooltip_text, 
                    xy=(x_pos, y_pos), 
                    xytext=(20, 20), 
                    textcoords="offset points",
                    bbox=dict(boxstyle="round,pad=0.5", fc="yellow", alpha=0.8), 
                    arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=0")
                )
                canvas.draw_idle()
            except Exception as fallback_error:
                fc_log("simulator", f"⚠️ Erro no fallback do tooltip: {fallback_error}")

    def _zoom_in(self, slot_index):
        """Aplica zoom in no gráfico específico"""
        try:
            ax = self.graph_axes[slot_index]
            canvas = self.graph_canvases[slot_index]
            
            # Obtém limites atuais
            xlim = ax.get_xlim()
            ylim = 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 centralizados
            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)
            
            # Aplica novos limites
            ax.set_xlim(new_xlim)
            ax.set_ylim(new_ylim)
            
            # Atualiza fator de zoom
            self.zoom_factors[slot_index] *= (1/zoom_factor)
            self._update_zoom_label(slot_index)
            
            canvas.draw()
            fc_log("simulator", f"✅ Zoom in aplicado no Slot #{slot_index + 1}")
            
        except Exception as e:
            fc_log("simulator", f"❌ Erro no zoom in do Slot #{slot_index + 1}: {e}")

    def _zoom_out(self, slot_index):
        """Aplica zoom out no gráfico específico"""
        try:
            ax = self.graph_axes[slot_index]
            canvas = self.graph_canvases[slot_index]
            
            # Obtém limites atuais
            xlim = ax.get_xlim()
            ylim = 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 centralizados
            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)
            
            # Aplica novos limites
            ax.set_xlim(new_xlim)
            ax.set_ylim(new_ylim)
            
            # Atualiza fator de zoom
            self.zoom_factors[slot_index] *= zoom_factor
            self._update_zoom_label(slot_index)
            
            canvas.draw()
            fc_log("simulator", f"✅ Zoom out aplicado no Slot #{slot_index + 1}")
            
        except Exception as e:
            fc_log("simulator", f"❌ Erro no zoom out do Slot #{slot_index + 1}: {e}")

    def _reset_zoom(self, slot_index):
        """Reseta o zoom para limites padrão (faixa ampla), sem depender dos dados"""
        fc_log("simulator", f"🔍 DEBUG: Função _reset_zoom chamada para slot {slot_index + 1}")
        try:
            ax = self.graph_axes[slot_index]
            canvas = self.graph_canvases[slot_index]
            # Sempre usa limites padrão amplos no reset
            ax.set_xlim(780, 1020)
            ax.set_ylim(0, 30)
            fc_log("simulator", f"🔍 DEBUG: Reset para limites padrão amplos: X=(780-1020MHz), Y=(0-30m)")
            
            # Reseta fator de zoom
            self.zoom_factors[slot_index] = 1.0
            self._update_zoom_label(slot_index)
            
            canvas.draw()
            fc_log("simulator", f"✅ Zoom resetado no Slot #{slot_index + 1}")
            
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao resetar zoom do Slot #{slot_index + 1}: {e}")
            import traceback
            traceback.print_exc()

    def _fit_to_view(self, slot_index):
        """Ajusta a visualização para enquadrar os dados (com margem)"""
        try:
            ax = self.graph_axes[slot_index]
            canvas = self.graph_canvases[slot_index]
            slot = self.slot_data[slot_index]
            
            if slot is not None and 'data' in slot and hasattr(slot['data'], 'empty') and not slot['data'].empty:
                df = slot['data']
                
                # Calcula limites baseados nos dados
                x_min = df['Frequência'].min()
                x_max = df['Frequência'].max()
                y_min = 0
                
                # Encontra o valor máximo entre todas as colunas de distância
                y_max = 0
                for col in ['Forward Link', 'Reverse Link', 'Link Final']:
                    if col in df.columns:
                        y_max = max(y_max, df[col].max())
                
                # Adiciona margem de 8%
                x_margin = (x_max - x_min) * 0.08
                y_margin = y_max * 0.08
                
                ax.set_xlim(x_min - x_margin, x_max + x_margin)
                ax.set_ylim(0, y_max + y_margin)
                
                # Reseta fator de zoom
                self.zoom_factors[slot_index] = 1.0
                self._update_zoom_label(slot_index)
                
                canvas.draw()
                fc_log("simulator", f"✅ Visualização ajustada aos dados no Slot #{slot_index + 1}")
            else:
                # Se não há dados, volta para visualização padrão
                self._reset_zoom(slot_index)
                
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao ajustar visualização do Slot #{slot_index + 1}: {e}")

    def _update_zoom_label(self, slot_index):
        """Atualiza o label que mostra o fator de zoom atual"""
        try:
            if hasattr(self, 'zoom_labels') and slot_index < len(self.zoom_labels):
                zoom_factor = self.zoom_factors[slot_index]
                zoom_text = f"Zoom: {zoom_factor:.1f}x"
                self.zoom_labels[slot_index].config(text=zoom_text)
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro ao atualizar label de zoom: {e}")

    def _toggle_pan(self, slot_index):
        """Ativa/desativa o modo pan (arrastar gráfico) para um slot"""
        self.pan_active[slot_index] = not self.pan_active[slot_index]
        
        if self.pan_active[slot_index]:
            self.pan_btns[slot_index].config(relief=tk.SUNKEN, bg='#d0d0d0')
            self.graph_canvases[slot_index].mpl_connect('button_press_event', 
                lambda event, idx=slot_index: self._on_pan_press(event, idx))
            self.graph_canvases[slot_index].mpl_connect('button_release_event', 
                lambda event, idx=slot_index: self._on_pan_release(event, idx))
            self.graph_canvases[slot_index].mpl_connect('motion_notify_event', 
                lambda event, idx=slot_index: self._on_pan_motion(event, idx))
            self.pan_start[slot_index] = None
            fc_log("simulator", f"✅ Modo Pan ativado no Slot #{slot_index + 1}")
        else:
            self.pan_btns[slot_index].config(relief=tk.RAISED, bg='SystemButtonFace')
            fc_log("simulator", f"✅ Modo Pan desativado no Slot #{slot_index + 1}")

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

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

    def _on_pan_motion(self, event, slot_index):
        """Callback quando move o mouse no modo pan"""
        if (self.pan_active[slot_index] and self.pan_start[slot_index] and 
            event.inaxes == self.graph_axes[slot_index]):
            dx = self.pan_start[slot_index][0] - event.xdata
            dy = self.pan_start[slot_index][1] - event.ydata
            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.graph_axes[slot_index].set_xlim(new_xlim)
            self.graph_axes[slot_index].set_ylim(new_ylim)
            self.graph_canvases[slot_index].draw_idle()

    def _position_tooltip_smartly(self, event, x_pos, y_pos, tooltip_text, slot_index):
        """Posiciona o tooltip de forma inteligente baseado na posição do mouse"""
        try:
            ax = self.graph_axes[slot_index]
            canvas = self.graph_canvases[slot_index]
            annot = self.annots[slot_index]
            
            # Obtém dimensões do canvas em pixels
            canvas_width = canvas.get_width_height()[0]
            canvas_height = 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
            annot.xy = (x_pos, y_pos)
            annot.xytext = xytext
            annot.set_text(tooltip_text)
            annot.set_ha(ha)
            annot.set_visible(True)
            
            # Força redesenho para garantir que o tooltip apareça
            canvas.draw_idle()
            
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro no posicionamento do tooltip: {e}")
            # Fallback para posicionamento padrão
            try:
                if hasattr(self, 'annots') and slot_index < len(self.annots) and self.annots[slot_index]:
                    annot = self.annots[slot_index]
                    annot.xy = (x_pos, y_pos)
                    annot.set_text(tooltip_text)
                    annot.set_visible(True)
                    canvas.draw_idle()
            except Exception as fallback_error:
                fc_log("simulator", f"⚠️ Erro no fallback do tooltip: {fallback_error}")

    def _import_file(self, filepath, slot_index, show_success_msg=False):
        try:
            data_dict = None; encodings_to_try = ['utf-8', 'latin-1', 'cp1252']
            for enc in encodings_to_try:
                try:
                    with open(filepath, 'r', encoding=enc) as f: data_dict = json.load(f)
                    break
                except UnicodeDecodeError: continue
            if data_dict is None: raise ValueError("Não foi possível decodificar o arquivo.")
            full_json_data = data_dict; test_to_parse = None; filename = os.path.basename(filepath)
            if 'test_history' in full_json_data and isinstance(full_json_data['test_history'], list):
                if not full_json_data['test_history']: raise ValueError("O arquivo de projeto não contém nenhum teste.")
                popup = TestSelectorPopup(self, full_json_data['test_history']); self.wait_window(popup)
                if popup.selected_test:
                    test_to_parse = popup.selected_test; filename += f" -> ID: {test_to_parse.get('id', 'N/A')}"
                else: return
            else: test_to_parse = full_json_data
            if test_to_parse:
                data = self._parse_test_record(test_to_parse, full_json_data); data['filepath'] = filepath
                self.slot_data[slot_index] = data
                self._update_slot_display(slot_index, self._slot_test_record_from_stored(data))
                if show_success_msg: messagebox.showinfo(t('simulator.success_data_loaded'), t('simulator.success_data_loaded_msg').format(slot=slot_index + 1))
                self.run_simulation_and_update_graphs()
                # Salva estado automaticamente após importar
                self._save_state()
                fc_log("simulator", f"✅ Estado salvo automaticamente após importar arquivo no Slot #{slot_index + 1}")
        except Exception as e:
            self.clear_slot_data(slot_index)
            if "cancelada" not in str(e): messagebox.showerror(t('simulator.error_import_failed'), t('simulator.error_import_failed_msg').format(error=str(e), traceback=traceback.format_exc()))

    def import_data_for_slot(self, slot_index):
        """Importa dados para um slot - NOVA IMPLEMENTAÇÃO com seleção de testes"""
        fc_log("simulator", f"🔄 Importando dados para Slot #{slot_index + 1}")
        
        # Primeiro, permite ao usuário escolher o arquivo de projeto
        from tkinter import filedialog
        filepath = filedialog.askopenfilename(
            title=t('simulator.select_threshold_project'),
            filetypes=[
                (t('export_popup.filetype_json'), "*.json"),
                (t('simulator.filetype_project_files'), "projeto_*.json"),
                (t('export_popup.filetype_all'), "*.*"),
            ],
            initialdir=os.path.expanduser("~/Downloads"),  # Começa na pasta Downloads
            parent=self,
        )
        
        if not filepath:
            fc_log("simulator", "❌ Nenhum arquivo selecionado")
            return
        
        fc_log("simulator", f"📁 Arquivo selecionado: {filepath}")
        
        # Carrega o projeto selecionado
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                project_data = json.load(f)
            
            if not isinstance(project_data, dict) or 'name' not in project_data:
                messagebox.showerror(t('simulator.error_invalid_project'), t('simulator.error_invalid_project_msg'))
                return
            
            # Cria a estrutura de projeto
            project_info = {
                'filepath': filepath,
                'name': project_data.get('name', 'Projeto sem nome'),
                'client': project_data.get('client', ''),
                'date_created': project_data.get('date_created', ''),
                'test_count': len(project_data.get('test_history', [])),
                'data': project_data
            }
            
            fc_log("simulator", f"✅ Projeto carregado: {project_info['name']} ({project_info['test_count']} testes)")
            
            # Abre popup para seleção de teste
            self._open_test_selection_popup(slot_index, [project_info])
            
        except Exception as e:
            messagebox.showerror(t('simulator.error_load_project'), t('simulator.error_load_project_msg').format(error=str(e)))
            fc_log("simulator", f"❌ Erro ao carregar projeto: {e}")
            return

    def _get_threshold_projects(self):
        """Busca projetos salvos do módulo Threshold"""
        projects = []
        fc_log("simulator", "🔍 INICIANDO BUSCA DE PROJETOS DO THRESHOLD...")
        
        try:
            # 1. Busca arquivos de projeto na pasta atual
            import glob
            project_files = glob.glob("projeto_*.json")
            fc_log("simulator", f"🔍 Buscando arquivos projeto_*.json na pasta atual: {len(project_files)} encontrados")
            
            # 2. Busca também em subpastas
            project_files.extend(glob.glob("**/projeto_*.json", recursive=True))
            fc_log("simulator", f"🔍 Buscando arquivos projeto_*.json recursivamente: {len(project_files)} encontrados")
            
            # 2.5. Busca na pasta Downloads
            try:
                import os
                downloads_path = os.path.join(os.path.expanduser("~"), "Downloads")
                if os.path.exists(downloads_path):
                    fc_log("simulator", f"🔍 Buscando projetos na pasta Downloads: {downloads_path}")
                    
                    # Busca arquivos projeto_*.json na pasta Downloads
                    downloads_pattern = os.path.join(downloads_path, "projeto_*.json")
                    downloads_projects = glob.glob(downloads_pattern)
                    project_files.extend(downloads_projects)
                    fc_log("simulator", f"📁 Projetos encontrados na Downloads: {len(downloads_projects)}")
                    
                    # Busca também arquivos *.json que possam ser projetos na pasta Downloads
                    downloads_json_pattern = os.path.join(downloads_path, "*.json")
                    downloads_json_files = glob.glob(downloads_json_pattern)
                    fc_log("simulator", f"📁 Arquivos JSON encontrados na Downloads: {len(downloads_json_files)}")
                else:
                    fc_log("simulator", f"⚠️ Pasta Downloads não encontrada: {downloads_path}")
                    
            except Exception as e:
                fc_log("simulator", f"⚠️ Erro ao buscar na pasta Downloads: {e}")
            
            # 3. Busca arquivos JSON que podem ser projetos
            json_files = glob.glob("*.json")
            json_files.extend(glob.glob("**/*.json", recursive=True))
            fc_log("simulator", f"🔍 Buscando arquivos JSON: {len(json_files)} encontrados")
            
            # Processa arquivos de projeto
            for filepath in project_files:
                try:
                    with open(filepath, 'r', encoding='utf-8') as f:
                        project_data = json.load(f)
                    
                    # Verifica se é um projeto válido
                    if isinstance(project_data, dict) and 'name' in project_data:
                        project_info = {
                            'filepath': filepath,
                            'name': project_data.get('name', 'Projeto sem nome'),
                            'client': project_data.get('client', ''),
                            'date_created': project_data.get('date_created', ''),
                            'test_count': len(project_data.get('test_history', [])),
                            'data': project_data
                        }
                        projects.append(project_info)
                        fc_log("simulator", f"📁 Projeto encontrado: {project_info['name']} ({project_info['test_count']} testes)")
                        
                except Exception as e:
                    fc_log("simulator", f"⚠️ Erro ao ler projeto {filepath}: {e}")
                    continue
            
            # 4. Busca no banco de dados interno do Threshold
            try:
                # Tenta acessar o banco de dados do Threshold de diferentes formas
                threshold_db = None
                
                # Método 1: Importação direta
                try:
                    from .threshold_database import ThresholdDatabase
                    threshold_db = ThresholdDatabase()
                    fc_log("simulator", "✅ Banco de dados Threshold acessado via importação direta")
                except Exception as e1:
                    fc_log("simulator", f"⚠️ Erro na importação direta: {e1}")
                    
                    # Método 2: Acesso via arquivo de estado
                    try:
                        import os
                        state_file = os.path.join(os.path.dirname(__file__), "threshold_state.json")
                        if os.path.exists(state_file):
                            with open(state_file, 'r', encoding='utf-8') as f:
                                state_data = json.load(f)
                            
                            internal_projects = state_data.get('projects', [])
                            fc_log("simulator", f"✅ Projetos encontrados no arquivo de estado: {len(internal_projects)}")
                            
                            for project_data in internal_projects:
                                if isinstance(project_data, dict) and 'name' in project_data:
                                    project_info = {
                                        'filepath': f"Banco Interno: {project_data.get('name', 'Projeto sem nome')}",
                                        'name': project_data.get('name', 'Projeto sem nome'),
                                        'client': project_data.get('client', ''),
                                        'date_created': project_data.get('date_created', ''),
                                        'test_count': len(project_data.get('test_history', [])),
                                        'data': project_data
                                    }
                                    projects.append(project_info)
                                    fc_log("simulator", f"📁 Projeto interno encontrado: {project_info['name']} ({project_info['test_count']} testes)")
                        else:
                            fc_log("simulator", f"⚠️ Arquivo de estado não encontrado: {state_file}")
                            
                    except Exception as e2:
                        fc_log("simulator", f"⚠️ Erro ao acessar arquivo de estado: {e2}")
                
                # Se conseguiu acessar via ThresholdDatabase, usa esse método
                if threshold_db:
                    internal_projects = threshold_db.get_projects()
                    fc_log("simulator", f"✅ Projetos encontrados via ThresholdDatabase: {len(internal_projects)}")
                    
                    for project_data in internal_projects:
                        if isinstance(project_data, dict) and 'name' in project_data:
                            project_info = {
                                'filepath': f"Banco Interno: {project_data.get('name', 'Projeto sem nome')}",
                                'name': project_data.get('name', 'Projeto sem nome'),
                                'client': project_data.get('client', ''),
                                'date_created': project_data.get('date_created', ''),
                                'test_count': len(project_data.get('test_history', [])),
                                'data': project_data
                            }
                            projects.append(project_info)
                            fc_log("simulator", f"📁 Projeto interno encontrado: {project_info['name']} ({project_info['test_count']} testes)")
                        
            except Exception as e:
                fc_log("simulator", f"⚠️ Erro ao buscar projetos internos: {e}")
            
            # 5. Se ainda não encontrou nada, permite seleção manual
            if not projects:
                fc_log("simulator", "⚠️ Nenhum projeto encontrado automaticamente")
                result = messagebox.askyesno(
                    t('simulator.no_saved_projects_title'),
                    t('simulator.no_saved_projects_msg'),
                    parent=self,
                )
                
                if result:
                    filepath = filedialog.askopenfilename(
                        title=t('simulator.select_project_file'),
                        filetypes=[
                            (t('export_popup.filetype_json'), "*.json"),
                            (t('export_popup.filetype_all'), "*.*"),
                        ],
                        parent=self,
                    )
                    
                    if filepath:
                        try:
                            with open(filepath, 'r', encoding='utf-8') as f:
                                project_data = json.load(f)
                            
                            if isinstance(project_data, dict) and 'name' in project_data:
                                project_info = {
                                    'filepath': filepath,
                                    'name': project_data.get('name', 'Projeto sem nome'),
                                    'client': project_data.get('client', ''),
                                    'date_created': project_data.get('date_created', ''),
                                    'test_count': len(project_data.get('test_history', [])),
                                    'data': project_data
                                }
                                projects.append(project_info)
                                fc_log("simulator", f"📁 Projeto selecionado manualmente: {project_info['name']} ({project_info['test_count']} testes)")
                                
                        except Exception as e:
                            fc_log("simulator", f"❌ Erro ao ler arquivo selecionado: {e}")
                            messagebox.showerror(t('simulator.error_read_file'), t('simulator.error_read_file_msg').format(error=str(e)))
            
            fc_log("simulator", f"📊 Total de projetos encontrados: {len(projects)}")
            return projects
            
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao buscar projetos: {e}")
            return []

    def _open_test_selection_popup(self, slot_index, projects):
        """Abre popup para seleção de projeto e teste"""
        fc_log("simulator", f"🔄 Abrindo popup para Slot #{slot_index + 1}")
        fc_log("simulator", f"📊 Projetos recebidos: {len(projects)}")
        for i, project in enumerate(projects):
            fc_log("simulator", f"  {i+1}. {project.get('name', 'Sem nome')} - {project.get('test_count', 0)} testes")
        
        popup = TestSelectionPopup(self, slot_index, projects)
        self.wait_window(popup)
        
        if popup.selected_test:
            fc_log("simulator", f"✅ Teste selecionado para Slot #{slot_index + 1}: {popup.selected_test.get('description', 'Sem descrição')}")
            self._import_test_to_slot(slot_index, popup.selected_test, popup.selected_project)
        else:
            fc_log("simulator", f"⚠️ Nenhum teste selecionado para Slot #{slot_index + 1}")

    def _import_test_to_slot(self, slot_index, test_data, project_data, show_success_message=True):
        """Importa um teste específico para um slot"""
        try:
            fc_log("simulator", f"🔄 Importando teste para Slot #{slot_index + 1}")
            
            # Verifica se o slot já tem dados
            slot_has_data = hasattr(self, 'slot_data') and self.slot_data[slot_index] is not None
            
            if slot_has_data:
                # Pergunta se quer substituir
                result = messagebox.askyesno(
                    t('simulator.replace_slot_title'),
                    t('simulator.replace_slot_msg').format(slot=slot_index + 1),
                    parent=self,
                )
                if not result:
                    fc_log("simulator", f"⚠️ Importação cancelada pelo usuário")
                    return
            
            # Cria dados do teste no formato esperado pelo simulador
            test_record = {
                'description': test_data.get('description', 'Teste Importado'),
                'epc': test_data.get('epc', ''),
                'tag_name': test_data.get('tag_name', ''),
                'date_time': test_data.get('date_time', ''),
                'results': test_data.get('results', []),
                'distance': test_data.get('distance', '1.0'),
                'attenuator': test_data.get('attenuator', '0.0'),
                'test_start_time': test_data.get('test_start_time', ''),
                'test_end_time': test_data.get('test_end_time', ''),
                'test_duration_seconds': test_data.get('test_duration_seconds', ''),
                'calibration': test_data.get('calibration', ''),
                'calibration_file': test_data.get('calibration_file', ''),
                # Informações para persistência
                'project_path': project_data.get('filepath', ''),
                'test_id': test_data.get('id'),
                'test_description': test_data.get('description', '')
            }
            
            # Processa o teste usando a função existente
            self._import_file_data(test_record, project_data, slot_index)
            
            # Atualiza a interface do slot
            self._update_slot_display(slot_index, test_record)
            
            # Mostra mensagem de sucesso apenas se solicitado (importação manual)
            if show_success_message:
                messagebox.showinfo(
                    t('simulator.import_slot_done_title'),
                    t('simulator.import_slot_done_msg').format(
                        slot=slot_index + 1,
                        description=test_record['description'],
                        epc=test_record['epc'],
                        date_time=test_record['date_time'],
                    ),
                    parent=self,
                )
            
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao importar teste para slot: {e}")
            messagebox.showerror(
                t('simulator.import_slot_error_title'),
                t('simulator.import_slot_error_msg').format(
                    slot=slot_index + 1,
                    error=str(e),
                ),
                parent=self,
            )

    def _import_file_data(self, test_record, project_data, slot_index):
        """Importa dados de um teste para um slot específico"""
        try:
            fc_log("simulator", f"🔄 Processando dados para Slot #{slot_index + 1}")
            
            # Processa o teste usando a função existente (igual à _import_file original)
            data = self._parse_test_record(test_record, project_data)
            fc_log("simulator", f"✅ Dados processados: {len(data.get('data', []))} linhas")
            
            # Armazena os dados processados no slot (igual à _import_file original)
            if not hasattr(self, 'slot_data'):
                self.slot_data = [None] * 4
            
            self.slot_data[slot_index] = data
            fc_log("simulator", f"✅ Dados armazenados no slot_data[{slot_index}]")
            fc_log("simulator", f"📊 Dados armazenados: {data.get('params', {}).get('description', 'Sem descrição')}")
            fc_log("simulator", f"📊 project_path: {data.get('project_path', 'None')}")
            fc_log("simulator", f"📊 test_id: {data.get('test_id', 'None')}")
            
            # Atualiza a interface do slot (igual à _import_file original)
            summary = self._slot_test_record_from_stored(data)
            self.slot_frames[slot_index].config(text=_slot_frame_title(slot_index, summary))
            fc_log("simulator", f"✅ Frame do slot atualizado: {summary.get('description', 'N/A')}")
            
            # Chama a função correta para atualizar gráficos (igual à _import_file original)
            fc_log("simulator", f"🔄 Chamando run_simulation_and_update_graphs para Slot #{slot_index + 1}")
            try:
                self.run_simulation_and_update_graphs()
                fc_log("simulator", f"✅ run_simulation_and_update_graphs executado com sucesso")
            except Exception as e:
                fc_log("simulator", f"❌ Erro em run_simulation_and_update_graphs: {e}")
            
            # Salva estado automaticamente (igual à _import_file original)
            self._save_state()
            fc_log("simulator", f"✅ Estado salvo automaticamente")
            
            fc_log("simulator", f"✅ Dados importados para Slot #{slot_index + 1}")
            
        except Exception as e:
            fc_log("simulator", f"❌ Erro ao processar dados do teste: {e}")
            raise

    def _slot_test_record_from_stored(self, slot_dict):
        """Monta o dict usado nos rótulos do slot a partir de slot_data (após parse)."""
        if not slot_dict:
            return {'description': '', 'tag_name': '', 'epc': '', 'calibration': '', 'calibration_file': ''}
        p = slot_dict.get('params') or {}
        return {
            'description': p.get('description', ''),
            'tag_name': (slot_dict.get('tag_name') or '').strip(),
            'epc': (slot_dict.get('epc') or '').strip(),
            'calibration': slot_dict.get('calibration', ''),
            'calibration_file': slot_dict.get('calibration_file', ''),
        }

    def _update_slot_display(self, slot_index, test_record):
        """Atualiza a exibição do slot na interface"""
        try:
            fc_log("simulator", f"🔄 Atualizando display do Slot #{slot_index + 1}")
            fc_log("simulator", f"📊 slot_widgets existe: {hasattr(self, 'slot_widgets')}")
            if hasattr(self, 'slot_widgets'):
                fc_log("simulator", f"📊 slot_widgets length: {len(self.slot_widgets)}")
                fc_log("simulator", f"📊 slot_index < length: {slot_index < len(self.slot_widgets)}")
            
            if hasattr(self, 'slot_widgets') and slot_index < len(self.slot_widgets):
                # Atualiza o label do arquivo no slot
                filename_label = self.slot_widgets[slot_index].get('label')
                fc_log("simulator", f"📊 filename_label encontrado: {filename_label is not None}")
                if filename_label:
                    label_txt = _build_slot_sidebar_label_text(test_record)
                    filename_label.config(text=label_txt, foreground="blue")
                    fc_log("simulator", f"✅ Label atualizado (tag/calibração)")
                
                # Também atualiza o título do frame do slot
                if hasattr(self, 'slot_frames') and slot_index < len(self.slot_frames):
                    self.slot_frames[slot_index].config(text=_slot_frame_title(slot_index, test_record))
                    fc_log("simulator", f"✅ Frame do slot atualizado")
                
                fc_log("simulator", f"✅ Interface do Slot #{slot_index + 1} atualizada")
            else:
                fc_log("simulator", f"⚠️ slot_widgets não encontrado ou slot_index inválido")
            
        except Exception as e:
            fc_log("simulator", f"⚠️ Erro ao atualizar interface do slot: {e}")

    def _parse_test_record(self, test_record, full_json_data):
        fc_log("simulator", f"🔄 Processando test_record: {test_record.get('description', 'Sem descrição')}")
        fc_log("simulator", f"📊 Keys no test_record: {list(test_record.keys())}")
        
        params = {}; distance = test_record.get('distance'); attenuator = test_record.get('attenuator')
        fc_log("simulator", f"📊 Distance: {distance}, Attenuator: {attenuator}")
        
        if distance is None or attenuator is None:
            settings = full_json_data.get('settings', {})
            fc_log("simulator", f"📊 Settings encontrados: {list(settings.keys()) if settings else 'Nenhum'}")
            if distance is None: distance = settings.get('distance')
            if attenuator is None: attenuator = settings.get('attenuator')
            fc_log("simulator", f"📊 Após settings - Distance: {distance}, Attenuator: {attenuator}")
        
        if distance is None or attenuator is None:
            fc_log("simulator", f"⚠️ Parâmetros faltando - abrindo popup")
            popup = MissingParametersPopup(self); self.wait_window(popup)
            if popup.cancelled: raise ValueError("Importação cancelada pelo usuário.")
            distance = popup.distance; attenuator = popup.attenuator
            fc_log("simulator", f"📊 Após popup - Distance: {distance}, Attenuator: {attenuator}")
        
        params['d_med'] = float(distance); params['atenuador'] = float(attenuator); params['description'] = test_record.get('description', 'Sem Descrição')
        fc_log("simulator", f"📊 Params finais: {params}")
        
        results = test_record.get('results', [])
        fc_log("simulator", f"📊 Results encontrados: {len(results) if isinstance(results, list) else 'Não é lista'}")
        
        if not isinstance(results, list) or not results: 
            fc_log("simulator", f"❌ Results inválido: {type(results)} - {results}")
            raise ValueError("O registro de teste não contém a lista 'results'.")
        
        # Mostra alguns resultados para debug
        if results:
            fc_log("simulator", f"📊 Primeiro resultado: {results[0] if results else 'Nenhum'}")
            fc_log("simulator", f"📊 Keys do primeiro resultado: {list(results[0].keys()) if results else 'Nenhum'}")
        
        data_rows = []
        for r in results:
            if not all(k in r for k in ['freq_mhz', 'module_power', 'rssi']):
                continue
            row = {
                'Frequência': r['freq_mhz'],
                'module_power': r['module_power'],
                'rssi': r['rssi'],
            }
            # Quando vier de teste do Threshold, aproveita as grandezas já derivadas.
            if 'power_on_tag_forward' in r:
                row['power_on_tag_forward'] = r.get('power_on_tag_forward')
            if 'power_on_tag_reversed' in r:
                row['power_on_tag_reversed'] = r.get('power_on_tag_reversed')
            data_rows.append(row)
        fc_log("simulator", f"📊 Data rows processados: {len(data_rows)}")
        
        if not data_rows: 
            fc_log("simulator", f"❌ Nenhum data row válido")
            raise ValueError("Nenhum resultado válido foi encontrado no teste.")
        
        df = pd.DataFrame(data_rows)
        fc_log("simulator", f"📊 DataFrame criado: {len(df)} linhas, {len(df.columns)} colunas")
        fc_log("simulator", f"📊 Colunas do DataFrame: {list(df.columns)}")
        
        # Preserva informações de persistência do test_record original
        result = {'params': params, 'data': df}
        
        for key in ('tag_name', 'calibration', 'calibration_file', 'epc'):
            val = test_record.get(key)
            if val is not None and str(val).strip() != '':
                result[key] = str(val).strip()
        
        # Adiciona informações de persistência se existirem
        if 'project_path' in test_record:
            result['project_path'] = test_record['project_path']
            fc_log("simulator", f"📊 project_path preservado: {test_record['project_path']}")
        if 'test_id' in test_record:
            result['test_id'] = test_record['test_id']
            fc_log("simulator", f"📊 test_id preservado: {test_record['test_id']}")
        if 'test_description' in test_record:
            result['test_description'] = test_record['test_description']
            fc_log("simulator", f"📊 test_description preservado: {test_record['test_description']}")
        
        return result

    def clear_slot_data(self, slot_index):
        self.slot_data[slot_index] = None
        # Label mantido vazio para economizar espaço
        self.slot_widgets[slot_index]['label'].config(text="", foreground="gray")
        self.slot_frames[slot_index].config(text=f"{t('simulator.slot')} #{slot_index + 1}")
        self.run_simulation_and_update_graphs()
        # Salva estado automaticamente após limpar slot
        self._save_state()
        fc_log("simulator", f"✅ Estado salvo automaticamente após limpar Slot #{slot_index + 1}")

    def run_simulation_and_update_graphs(self, *args):
        try:
            p_leitor = float(self.reader_power_var.get())
            s_leitor = float(self.reader_sensitivity_var.get())
            margin_db = float(self.margin_db_var.get()) if self.margin_db_var.get() else 0.0
        except (ValueError, tk.TclError):
            p_leitor, s_leitor, margin_db = 36.0, -70.0, 0.0
        for i, slot in enumerate(self.slot_data):
            if slot is not None:
                d_med = slot['params']['d_med']; atenuador = slot['params']['atenuador']
                df = slot['data']; p_th = df['module_power']; p_bk = df['rssi']

                freq = df['Frequência'].astype(float)
                wavelength = 300.0 / freq
                path_loss = 20.0 * np.log10((4.0 * np.pi * float(d_med)) / wavelength)

                # Usa POTF/POTR importados do Threshold quando disponíveis;
                # caso contrário, reconstrói no mesmo referencial físico.
                if 'power_on_tag_forward' in df.columns and 'power_on_tag_reversed' in df.columns:
                    potf = df['power_on_tag_forward'].astype(float)
                    potr = df['power_on_tag_reversed'].astype(float)
                else:
                    potf = (p_th.astype(float) - float(atenuador)) - path_loss
                    potr = (p_bk.astype(float) + float(atenuador)) + path_loss

                # Mesma lógica física do Threshold para links máximos.
                df['Forward Link'] = (10 ** ((float(p_leitor) - potf) / 20.0)) * wavelength / (4.0 * np.pi)
                df['Reverse Link'] = [
                    compute_max_fcc_link_reversed_m(float(potr_i), float(freq_i), float(s_leitor))
                    for potr_i, freq_i in zip(potr, freq)
                ]
                df['Link Final'] = np.where(df['Forward Link'] < df['Reverse Link'], df['Forward Link'], (df['Forward Link'] + df['Reverse Link']) / 2)
                p_leitor_margin = float(p_leitor) - float(margin_db)
                s_leitor_margin = float(s_leitor) + float(margin_db)
                df['Forward Link Margin'] = (10 ** ((p_leitor_margin - potf) / 20.0)) * wavelength / (4.0 * np.pi)
                df['Reverse Link Margin'] = [
                    compute_max_fcc_link_reversed_m(float(potr_i), float(freq_i), float(s_leitor_margin))
                    for potr_i, freq_i in zip(potr, freq)
                ]
                df['Link Final Margin'] = np.where(df['Forward Link Margin'] < df['Reverse Link Margin'], df['Forward Link Margin'], (df['Forward Link Margin'] + df['Reverse Link Margin']) / 2)
                self.slot_data[i]['data'] = df
        self.update_graphs()

    def update_graphs(self):
        try:
            margin_val_str = self.margin_db_var.get()
            margin_val_float = float(margin_val_str)
        except (tk.TclError, ValueError):
            margin_val_str = "0.0"; margin_val_float = 0.0

        for i in range(4):
            ax = self.graph_axes[i]; canvas = self.graph_canvases[i]; slot = self.slot_data[i]

            # --- CORREÇÃO DO BUG DO TOOLTIP ---
            # Reseta o objeto de anotação para este eixo. Isso força o on_motion
            # a criar um novo objeto de anotação que está corretamente
            # ligado ao eixo recém-desenhado.
            self.annots[i] = None
            # ---------------------------------

            ax.clear(); ax.axvspan(865, 868, color='lightblue', alpha=0.3); ax.axvspan(902, 907, color='lightblue', alpha=0.3); ax.axvspan(915.5, 928, color='lightblue', alpha=0.3)

            if slot is not None:
                df = slot['data']; description = slot['params'].get('description', f'Slot #{i+1}')
                ax.plot(df['Frequência'], df['Forward Link'], marker='.', linestyle='-', label=t('simulator.graph_forward_link'), linewidth=1.0, markersize=4)
                ax.plot(df['Frequência'], df['Reverse Link'], marker='.', linestyle='-', label=t('simulator.graph_reverse_link'), linewidth=1.0, markersize=4)
                ax.plot(df['Frequência'], df['Link Final'], marker='.', linestyle='-', label=t('simulator.graph_link_final'), linewidth=1.8, color='black', markersize=4)
                if margin_val_float > 0:
                    ax.plot(df['Frequência'], df['Link Final Margin'], marker='.', linestyle='--', color='red', label=t('simulator.graph_link_final_margin').format(margin=margin_val_str), linewidth=1.5, markersize=4)
                ax.set_title(f"{t('simulator.graph_performance')}: {description}", fontsize=10); ax.set_xlabel(t('simulator.graph_frequency')); ax.set_ylabel(t('simulator.graph_distance'))
                # Usa melhor localização automática e caixa semitransparente para não cobrir curvas
                leg = ax.legend(fontsize=7, loc='best', framealpha=0.6)
                
                # Ajusta automaticamente os limites baseado nos dados
                x_min = df['Frequência'].min()
                x_max = df['Frequência'].max()
                y_min = 0
                y_max = max(df['Forward Link'].max(), df['Reverse Link'].max(), df['Link Final'].max())
                if margin_val_float > 0 and 'Link Final Margin' in df.columns:
                    y_max = max(y_max, df['Link Final Margin'].max())
                
                # Adiciona margem de 5%
                x_margin = (x_max - x_min) * 0.05
                y_margin = y_max * 0.05
                
                ax.set_xlim(x_min - x_margin, x_max + x_margin)
                ax.set_ylim(0, y_max + y_margin)
                fc_log("simulator", f"✅ Visualização ajustada aos dados no Slot #{i + 1}")
            else:
                ax.set_title(f"{t('simulator.slot')} #{i+1}", fontsize=10); ax.set_xlabel(t('simulator.graph_frequency')); ax.set_ylabel(t('simulator.graph_distance'))
                ax.text(0.5, 0.5, t('simulator.graph_import_file'), horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=12, color='gray')
                ax.set_xlim(800, 1000)
                ax.set_ylim(0, 10)
            ax.grid(True, which='both', linestyle='--', linewidth=0.5)
            canvas.draw()

if __name__ == '__main__':
    root = tk.Tk()
    root.withdraw()
    if not LIBRARIES_OK:
        # Mensagem de erro atualizada para incluir o Pillow
        messagebox.showerror(t('simulator.error_missing_libraries'), t('simulator.error_missing_libraries_msg'))
        root.destroy()
    else:
        root.title("Simulator de Performance de Tags RFID v4.0")
        root.minsize(1100, 750)
        try:
            style = ttk.Style(root)
            if "clam" in style.theme_names(): style.theme_use("clam")
        except tk.TclError: fc_log("simulator", "Tema 'clam' não encontrado.")
        app = SimuladorModule(root)
        app.pack(fill="both", expand=True)
        def on_closing():
            app._save_state()
            root.destroy()
        root.protocol("WM_DELETE_WINDOW", on_closing)
        root.deiconify()
        root.mainloop()