import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QListWidget, QPushButton, QDialogButtonBox, QLabel, QStyledItemDelegate
from models.knn_classifier import JurusanKNNClassifier
from models.data_processor import DataProcessor
import config
import pandas as pd
from db.database import ambil_semua_hasil_prediksi, simpan_hasil_prediksi, hapus_hasil_prediksi, update_hasil_prediksi
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from sklearn.metrics import roc_curve, auc
from sklearn.preprocessing import label_binarize
from itertools import cycle
from gui.custom_notifications import CustomNotificationDialog, StudentNamePickerDialog

class LineEditDelegate(QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        editor = QLineEdit(parent)
        editor.setPlaceholderText("0-100")
        editor.setAlignment(Qt.AlignCenter)
        editor.setValidator(QIntValidator(0, 100, editor))
        editor.setInputMethodHints(Qt.ImhDigitsOnly)
        editor.setStyleSheet("""
            QLineEdit {
                border: none;
                border-bottom: 1px solid #e2e8f0;
                padding: 6px;
                font-size: 16px;
                background-color: white;
                color: #1e293b;
                font-family: 'Segoe UI', Arial, sans-serif;
                font-weight: 500;
            }
            QLineEdit:focus {
                border: none;
                border-bottom: 1px solid #667eea;
                padding: 6px;
            }
        """)
        return editor
    
    def setEditorData(self, editor, index):
        value = index.model().data(index, Qt.EditRole)
        if value:
            editor.setText(str(value))
        else:
            editor.setText("")
    
    def setModelData(self, editor, model, index):
        model.setData(index, editor.text(), Qt.EditRole)

    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)

class MainWindow(QMainWindow):
    logout_signal = pyqtSignal()

    def handle_logout(self):
        # untuk memastikan hanya tab logout yang aktif saat konfirmasi, set checked dulu baru tampilkan dialog
        self.btn_logout.setChecked(True)
        # bersihkan semua tab lain agar tidak terlihat aktif saat dialog muncul
        for tab in [self.tab_input, self.tab_data, self.tab_performa]:
            tab.setChecked(False)
            
        reply = CustomNotificationDialog.show_question(self, 'Konfirmasi Logout',
                                     'Apakah Anda yakin ingin logout?',
                                     'Ya, Logout', 'Batal')
        if reply:
            self.logout_confirmed()
        else:
            # jika batal logout, kembalikan ke tab yang sebelumnya aktif (default ke Input jika tidak ada yang aktif)
            self.btn_logout.setChecked(False)
            # Set kembali ke tab yang sebelumnya aktif, default ke Input jika tidak ada yang aktif
            current_index = self.stacked_widget.currentIndex()
            if current_index == 0:
                self.tab_input.setChecked(True)
            elif current_index == 1:
                self.tab_data.setChecked(True)
            elif current_index == 2:
                self.tab_performa.setChecked(True)

    def logout_confirmed(self):
        # tutup halaman utama dan kembalikan ke login
        self.close()
        self.logout_signal.emit()

    def parse_float_value(self, text):
        """
        Helper function to parse float value from text input.
        Supports both comma (,) and dot (.) as decimal separator.
        Indonesian users can use 94,5 or 94.5 - both will work.
        """
        if not text or text.strip() == "":
            return 0.0
        # Replace comma with dot for float conversion
        text = text.strip().replace(',', '.')
        try:
            return float(text)
        except ValueError:
            return 0.0

    def _is_valid_score_text(self, text):
        """Return True when score text is empty or an integer between 0 and 100."""
        normalized = (text or "").strip()
        if not normalized:
            return True
        return normalized.isdigit() and 0 <= int(normalized) <= 100

    def _get_nilai_cell_text(self, row, column):
        """Ambil teks dari editor aktif jika ada, fallback ke item tabel."""
        if not hasattr(self, 'nilai_table'):
            return ''

        index = self.nilai_table.model().index(row, column)
        widget = self.nilai_table.indexWidget(index)
        if widget and hasattr(widget, 'text'):
            return widget.text().strip()

        item = self.nilai_table.item(row, column)
        return item.text().strip() if item else ''

    def _set_nilai_cell_text(self, row, column, text):
        """Set teks ke item dan editor aktif agar tampilan tetap sinkron."""
        if not hasattr(self, 'nilai_table'):
            return

        item = self.nilai_table.item(row, column)
        if item:
            item.setText(text)

        index = self.nilai_table.model().index(row, column)
        widget = self.nilai_table.indexWidget(index)
        if widget and hasattr(widget, 'setText'):
            widget.blockSignals(True)
            widget.setText(text)
            widget.blockSignals(False)

    def _calculate_nilai_rata_row(self, row):
        """Hitung dan perbarui rata-rata untuk satu baris mapel."""
        if not hasattr(self, 'nilai_table'):
            return
        if row < 0 or row >= self.nilai_table.rowCount():
            return

        sem1_text = self._get_nilai_cell_text(row, 1)
        sem2_text = self._get_nilai_cell_text(row, 2)
        rata_item = self.nilai_table.item(row, 3)
        if not rata_item:
            return

        sem1_value = self.parse_float_value(sem1_text)
        sem2_value = self.parse_float_value(sem2_text)

        if sem1_text or sem2_text:
            rata_rata = (sem1_value + sem2_value) / 2
            # Format rata-rata: hapus trailing zeros
            # 100 -> "100", 90.50 -> "90.5", 88.55 -> "88.55"
            formatted_rata = f"{rata_rata:.2f}".rstrip('0').rstrip('.')
            rata_item.setText(formatted_rata)
            rata_item.setForeground(QColor('#2c3e50'))
        else:
            rata_item.setText('otomatis')
            rata_item.setForeground(QColor('#94a3b8'))

    def _update_nilai_table_height(self):
        """Resize the input table so the last row ends at the bottom."""
        if not hasattr(self, 'nilai_table'):
            return

        header_height = self.nilai_table.horizontalHeader().height()
        row_height = self.nilai_table.verticalHeader().defaultSectionSize()
        visible_rows = max(1, self.nilai_table.rowCount())
        frame_height = self.nilai_table.frameWidth() * 2
        self.nilai_table.setFixedHeight(header_height + (visible_rows * row_height) + frame_height + 2)

    def _on_nilai_editor_changed(self, row, column, text):
        """Update item tabel dan rata-rata saat user mengetik di editor."""
        if not hasattr(self, 'nilai_table') or self.loading_data:
            return
        if column not in (1, 2):
            return

        self._nilai_syncing = True
        try:
            item = self.nilai_table.item(row, column)
            if item:
                item.setText(text)
        finally:
            self._nilai_syncing = False

        self._calculate_nilai_rata_row(row)

    def on_nilai_changed(self, item):
        """Calculate rata-rata when semester 1 or 2 values change"""
        if not hasattr(self, 'nilai_table') or getattr(self, '_nilai_syncing', False) or self.loading_data:
            return
        
        # Only process if item is in column 1 (Semester 1) or 2 (Semester 2)
        if item.column() not in [1, 2]:
            return
        self._calculate_nilai_rata_row(item.row())

    def open_student_name_picker(self):
        """Open the name picker dialog and fill the form when a name is chosen."""
        current_name = self.nama_input.text().strip() if hasattr(self, 'nama_input') else ''
        selected_name = StudentNamePickerDialog.show_picker(self, initial_name=current_name)
        if selected_name:
            self.nama_input.setText(selected_name)
            self.statusBar().showMessage(f'Nama siswa dipilih: {selected_name}')
        return selected_name
    
    #main window membuat claasifier
    def __init__(self, username="Admin"):
        super().__init__()
        icon_path = config.get_app_icon_path()
        if icon_path:
            self.setWindowIcon(QIcon(icon_path))
        self.username = username  # Store username
        self.classifier = JurusanKNNClassifier()
        self.data_processor = DataProcessor()
        self.current_selected_id = None
        self.loading_data = False  # untuk mencegah reaksi berantai saat mengisi data ke tabel
        self.edit_mode = False  # untuk membedakan antara prediksi baru dan edit data yang sudah ada
        self.original_edit_data = None  # Snapshot data awal saat masuk mode edit
        # Pagination variables
        self.current_page = 1
        self.items_per_page = 10
        self.total_items = 0
        self.all_data = []  # Store all data for pagination
        
        self.initUI()   #membangun tampilan
        self.load_table_data()  #menampilkan data dari database ke tabel
        
        # Initialize button states - start in create mode
        if hasattr(self, 'btn_predict'):
            self.btn_predict.setEnabled(True)
            self.btn_predict.setToolTip("Memprediksi dan menyimpan sebagai data baru")
        if hasattr(self, 'btn_edit'):
            self.btn_edit.setEnabled(False)
            self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
        if hasattr(self, 'btn_preview'):
            self.btn_preview.setEnabled(False)
            self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
        if hasattr(self, 'btn_update'):
            self.btn_update.setEnabled(False)
            self.btn_update.setToolTip("Pilih data terlebih dahulu untuk mengupdate")

    def _get_current_form_snapshot(self):
        """Ambil snapshot isi form saat ini untuk validasi perubahan."""
        nilai_dict = {}
        subject_keys = [
            'nilai_informatika', 'nilai_fisika', 'nilai_kimia', 'nilai_biologi',
            'nilai_big_lanjut', 'nilai_ekonomi', 'nilai_mat_lanjut', 'nilai_sej_lanjut',
            'nilai_sosiologi', 'nilai_geografi'
        ]
        
        if hasattr(self, 'nilai_table'):
            for row in range(min(len(subject_keys), self.nilai_table.rowCount())):
                key = subject_keys[row]
                rata_item = self.nilai_table.item(row, 3)
                if rata_item:
                    nilai_dict[key] = self.parse_float_value(rata_item.text())
        
        return {
            'nama': self.nama_input.text().strip(),
            'nilai_dict': nilai_dict
        }

    def _has_form_changes(self):
        """Cek apakah data form berbeda dari snapshot awal mode edit."""
        if not self.original_edit_data:
            return True

        current_data = self._get_current_form_snapshot()
        if current_data['nama'] != self.original_edit_data['nama']:
            return True

        for key, current_value in current_data['nilai_dict'].items():
            original_value = self.original_edit_data['nilai_dict'].get(key, 0.0)
            if not np.isclose(current_value, original_value, atol=1e-9):
                return True

        return False
        
    def initUI(self):
        self.setWindowTitle('Sistem Klasifikasi Jurusan')
        self.setMinimumSize(1024, 700)

        # Keep default look on normal monitors while fitting smaller screens safely.
        screen = QApplication.primaryScreen()
        if screen:
            available = screen.availableGeometry()
            width = min(1200, max(1024, int(available.width() * 0.95)))
            height = min(800, max(720, int(available.height() * 0.95)))
            self.resize(width, height)
            self.move(
                available.x() + (available.width() - width) // 2,
                available.y() + (available.height() - height) // 2
            )
        else:
            self.setGeometry(100, 100, 1200, 800)
        
        # Central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Main horizontal layout (sidebar + content)
        main_layout = QHBoxLayout()
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)
        
        # Create sidebar
        sidebar = QWidget()
        sidebar.setFixedWidth(250)
        sidebar.setStyleSheet("""
            QWidget {
                background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
                    stop:0 #667eea, stop:1 #764ba2);
            }
        """)
        
        sidebar_layout = QVBoxLayout()
        sidebar_layout.setContentsMargins(0, 0, 0, 0)
        sidebar_layout.setSpacing(0)
        
        # Header Section with logo and title in sidebar
        header_widget = QWidget()
        header_widget.setStyleSheet("background: transparent; padding: 25px 20px;")
        header_layout = QVBoxLayout()
        header_layout.setAlignment(Qt.AlignCenter)
        header_layout.setSpacing(0)
        header_layout.setContentsMargins(0, 0, 0, 0)

        # Logo
        logo_label = QLabel()
        logo_label.setFixedSize(100, 100)
        logo_label.setStyleSheet("background: transparent; border: none;")
        logo_label.setAlignment(Qt.AlignCenter)
        logo_path = config.get_resource_path(os.path.join('img', 'logo_sekolah.png'))
        logo_pixmap = QPixmap(logo_path)

        def crop_transparent(pixmap):
            if pixmap.isNull():
                return pixmap
            img = pixmap.toImage().convertToFormat(QImage.Format_ARGB32)
            w, h = img.width(), img.height()
            left, top = w, h
            right, bottom = 0, 0
            for y in range(h):
                for x in range(w):
                    alpha = (img.pixel(x, y) >> 24) & 0xFF
                    if alpha != 0:
                        if x < left:
                            left = x
                        if x > right:
                            right = x
                        if y < top:
                            top = y
                        if y > bottom:
                            bottom = y
            if right < left or bottom < top:
                return pixmap
            cropped = img.copy(left, top, right - left + 1, bottom - top + 1)
            return QPixmap.fromImage(cropped)

        if not logo_pixmap.isNull():
            # Crop away transparent padding so logo doesn't appear 'boxed'
            logo_pixmap = crop_transparent(logo_pixmap)
            logo_label.setPixmap(logo_pixmap.scaled(80, 80, Qt.KeepAspectRatio, Qt.SmoothTransformation))

        # Title section
        title_main = QLabel("JURUSIFY")
        title_main.setStyleSheet(
            "color: white; font-size: 22px; font-weight: bold; margin: 0px; padding: 0px;"
            " background: transparent; border: none; font-family: 'Segoe UI', Arial, sans-serif; letter-spacing: 0.5px;"
        )
        title_main.setAlignment(Qt.AlignCenter)

        # Layout adjustments for header - vertical stacking with no extra frames
        header_layout = QVBoxLayout()
        header_layout.setContentsMargins(0, 0, 0, 0)
        header_layout.setSpacing(0)
        logo_label.setContentsMargins(0, 0, 0, 0)
        title_main.setContentsMargins(0, 0, 0, 0)
        header_layout.addWidget(logo_label, alignment=Qt.AlignHCenter)
        header_layout.addWidget(title_main, alignment=Qt.AlignHCenter)

        header_widget = QWidget()
        # Increase top padding so the logo is positioned slightly lower above the title
        header_widget.setStyleSheet("background: transparent; border: none; padding: 28px 0 6px 0;")
        header_widget.setLayout(header_layout)

        # Add header layout to sidebar layout and insert larger spacing below title
        sidebar_layout.addWidget(header_widget)
        sidebar_layout.addSpacing(30)
        
        # Navigation menu
        nav_widget = QWidget()
        nav_widget.setStyleSheet("background: transparent;")
        nav_layout = QVBoxLayout()
        nav_layout.setContentsMargins(10, 15, 10, 10)
        nav_layout.setSpacing(5)
        
        # Tab buttons
        self.tab_input = QPushButton("▸  Input Data")
        self.tab_data = QPushButton("▸  Data Siswa") 
        self.tab_performa = QPushButton("▸  Performa")
        self.btn_logout = QPushButton("▸  Logout")
        
        # Style for sidebar navigation
        sidebar_nav_style = """
            QPushButton {
                border: none;
                padding: 16px 22px;
                font-size: 18px;
                color: rgba(255, 255, 255, 0.9);
                background-color: transparent;
                text-align: left;
                font-weight: 500;
                margin: 0;
                border-radius: 8px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background-color: rgba(255, 255, 255, 0.15);
                color: white;
            }
            QPushButton:checked {
                background-color: rgba(255, 255, 255, 0.25);
                color: white;
                font-weight: 600;
                border-left: 3px solid #ffffff;
                border-radius: 8px 0 0 8px;
            }
        """
        
        logout_style = """
            QPushButton {
                border: none;
                padding: 16px 22px;
                font-size: 18px;
                color: rgba(255, 255, 255, 0.9);
                background-color: transparent;
                text-align: left;
                font-weight: 500;
                margin: 8px 0 0 0;
                border-radius: 8px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background-color: rgba(239, 68, 68, 0.9);
                color: white;
            }
        """
        
        # Apply styles and make checkable
        for tab in [self.tab_input, self.tab_data, self.tab_performa]:
            tab.setCheckable(True)
            tab.setFocusPolicy(Qt.NoFocus)
            tab.setStyleSheet(sidebar_nav_style)
            nav_layout.addWidget(tab)
        
        # Add separator before logout
        nav_layout.addStretch()
        separator = QFrame()
        separator.setFrameShape(QFrame.HLine)
        separator.setStyleSheet("background-color: rgba(255, 255, 255, 0.2); max-height: 1px; margin: 10px 15px;")
        nav_layout.addWidget(separator)
        
        self.btn_logout.setStyleSheet(logout_style)
        self.btn_logout.setFocusPolicy(Qt.NoFocus)
        nav_layout.addWidget(self.btn_logout)
        nav_layout.addSpacing(10)
        
        nav_widget.setLayout(nav_layout)
        sidebar_layout.addWidget(nav_widget)
        
        sidebar.setLayout(sidebar_layout)
        main_layout.addWidget(sidebar)
        
        # Content area
        content_widget = QWidget()
        content_widget.setStyleSheet("background-color: #f8f9fa;")
        content_layout = QVBoxLayout()
        content_layout.setContentsMargins(0, 0, 0, 0)
        
        # Header for content area
        content_header = QWidget()
        content_header.setFixedHeight(65)
        content_header.setStyleSheet("background-color: white; border-bottom: 2px solid #e0e6ed;")
        content_header_layout = QHBoxLayout()
        content_header_layout.setContentsMargins(30, 0, 30, 0)
        
        # Spacer to push username and icon to the right
        content_header_layout.addStretch()
        
        # Username label
        self.username_label = QLabel(f"{self.username}")
        self.username_label.setStyleSheet("""
            color: #64748b; 
            font-size: 16px; 
            font-weight: 600; 
            margin-right: 8px; 
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        content_header_layout.addWidget(self.username_label)
        
        # User icon with modern styling
        user_icon_container = QWidget()
        user_icon_container.setFixedSize(36, 36)
        user_icon_container.setStyleSheet("""
            QWidget {
                background-color: #667eea;
                border-radius: 18px;
                border: none;
                outline: none;
            }
        """)
        user_icon_layout = QVBoxLayout(user_icon_container)
        user_icon_layout.setContentsMargins(0, 0, 0, 0)
        user_icon_layout.setAlignment(Qt.AlignCenter)
        
        user_icon = QLabel("👤")
        user_icon.setStyleSheet("color: white; font-size: 18px; background: transparent; border: none; font-family: 'Segoe UI', Arial, sans-serif;")
        user_icon.setAlignment(Qt.AlignCenter)
        user_icon_layout.addWidget(user_icon)
        
        content_header_layout.addWidget(user_icon_container)
        
        content_header.setLayout(content_header_layout)
        content_layout.addWidget(content_header)
        
        # Stacked Widget for different tabs
        self.stacked_widget = QStackedWidget()
        self.stacked_widget.setStyleSheet("background-color: #f8f9fa;")
        
        # Create tab pages
        self.create_input_tab()
        self.create_data_tab()
        self.create_performa_tab()
        
        content_layout.addWidget(self.stacked_widget)
        content_widget.setLayout(content_layout)
        main_layout.addWidget(content_widget)
        
        central_widget.setLayout(main_layout)
        
        # Connect signals
        self.tab_input.clicked.connect(lambda: self.switch_tab(0))
        self.tab_data.clicked.connect(lambda: self.switch_tab(1))
        self.tab_performa.clicked.connect(lambda: self.switch_tab(2))
        self.btn_logout.clicked.connect(self.handle_logout)
        
        # Set default tab
        self.switch_tab(0)
        
        # Load initial stats and performance
        print("Loading model and performance metrics...")
        self.generate_performance_display()  # Train first to get fresh accuracy
        self.refresh_performa_stats()  # Then update stats with fresh accuracy
        print("✓ Application ready!\n")
        
        # Load model from writable path first, then bundled model, then retrain.
        self.initialize_model()

    #load model dulu, train kalau belum ada
    def initialize_model(self):
        """Inisialisasi classifier."""
        model_candidates = [config.MODEL_PATH]
        bundled_model = getattr(config, 'BUNDLED_MODEL_PATH', None)
        if bundled_model and bundled_model not in model_candidates:
            model_candidates.append(bundled_model)

        for model_path in model_candidates:
            try:
                if os.path.exists(model_path):
                    self.classifier.load_model(model_path)
                    self.statusBar().showMessage('Model berhasil dimuat')
                    return
            except Exception as e:
                print(f"Gagal memuat model dari {model_path}: {e}")

        # untuk training awal, coba muat dataset dari path yang bisa ditulis (jika ada), jika tidak fallback ke dataset sumber
        self.train_model_background()
    
    def switch_tab(self, index):
        """Switch between tabs"""
        # If switching away from Input tab (index 0), reset edit mode
        if hasattr(self, 'stacked_widget') and self.stacked_widget.currentIndex() == 0 and index != 0:
            self.edit_mode = False
        
        # Update tab appearance
        tabs = [self.tab_input, self.tab_data, self.tab_performa]
        for i, tab in enumerate(tabs):
            tab.setChecked(i == index)
        
        # Switch stacked widget page
        self.stacked_widget.setCurrentIndex(index)
        
        # Update status based on tab
        if index == 0:
            # When switching to Input tab, clear form if not in edit mode
            if not self.edit_mode:
                self.clear_form()
            self.statusBar().showMessage('Input Data - Masukkan data siswa untuk prediksi')
        elif index == 1:
            self.statusBar().showMessage('Data Siswa - Kelola data hasil prediksi')
            # Always reset search state when returning to Data Siswa.
            if hasattr(self, 'search_input'):
                self.search_input.blockSignals(True)
                self.search_input.clear()
                self.search_input.blockSignals(False)
            self.current_page = 1
            if hasattr(self, 'load_table_data'):
                self.load_table_data()
            # Clear table selection when switching to Data Siswa tab if no data should be selected
            if hasattr(self, 'table') and not self.current_selected_id:
                self.table.clearSelection()
                # Ensure buttons are disabled
                if hasattr(self, 'btn_edit'):
                    self.btn_edit.setEnabled(False)
                if hasattr(self, 'btn_preview'):
                    self.btn_preview.setEnabled(False)
            # Avoid auto-focus on search field so it doesn't look pre-clicked.
            if hasattr(self, 'search_input'):
                self.search_input.clearFocus()
            if hasattr(self, 'table'):
                self.table.clearFocus()
            self.stacked_widget.setFocus()
        elif index == 2:
            self.statusBar().showMessage('Performa - Analisis performa sistem')
            # Only refresh stats (lightweight), don't retrain model every time
            self.refresh_performa_stats()
            # Performance display already generated on init, no need to regenerate
    
    def create_input_tab(self):
        """Create Input Data tab with modern simplified design"""
        input_page = QWidget()
        input_page.setStyleSheet("""
            QWidget {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
                    stop:0 #f5f7fa, stop:1 #e8ecf1);
            }
        """)

        page_layout = QVBoxLayout(input_page)
        page_layout.setContentsMargins(0, 0, 0, 0)

        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setFrameShape(QFrame.NoFrame)
        scroll_area.setStyleSheet("QScrollArea { border: none; background: transparent; }")

        input_widget = QWidget()
        input_widget.setStyleSheet("background: transparent;")
        
        # Main layout
        input_layout = QVBoxLayout(input_widget)
        input_layout.setContentsMargins(40, 30, 40, 30)
        input_layout.setSpacing(20)
        
        # Header with gradient
        header_widget = QWidget()
        header_widget.setStyleSheet("""
            QWidget {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #667eea, stop:1 #764ba2);
                border-radius: 12px;
            }
        """)
        header_layout = QVBoxLayout(header_widget)
        header_layout.setContentsMargins(30, 20, 30, 20)
        header_layout.setSpacing(5)
        
        title_label = QLabel("📝 Input Data Siswa")
        title_label.setStyleSheet("""
            font-size: 30px; 
            font-weight: bold; 
            color: white;
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        header_layout.addWidget(title_label)
        
        subtitle_label = QLabel("Masukkan data siswa untuk mendapatkan prediksi jurusan yang sesuai")
        subtitle_label.setStyleSheet("""
            font-size: 18px; 
            color: rgba(255, 255, 255, 0.9);
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        header_layout.addWidget(subtitle_label)
        
        input_layout.addWidget(header_widget)
        
        # Main Form Card - Simple white card
        form_card = QWidget()
        form_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 12px;
                border: 1px solid #e0e6ed;
            }
        """)
        shadow = QGraphicsDropShadowEffect()
        shadow.setBlurRadius(15)
        shadow.setXOffset(0)
        shadow.setYOffset(5)
        shadow.setColor(QColor(0, 0, 0, 30))
        form_card.setGraphicsEffect(shadow)
        
        form_layout = QVBoxLayout(form_card)
        form_layout.setContentsMargins(30, 30, 30, 30)
        form_layout.setSpacing(25)
        
        # Nama Siswa Section - plain label
        nama_label = QLabel("Nama Lengkap Siswa")
        nama_label.setStyleSheet("""
            font-size: 19px; 
            color: #2c3e50; 
            font-weight: bold;
            background: transparent;
            border: none;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        form_layout.addWidget(nama_label)
        
        self.nama_input = QLineEdit()
        self.nama_input.setMinimumHeight(56)
        self.nama_input.setMaximumHeight(56)
        self.nama_input.setMaxLength(50)
        self.nama_input.setReadOnly(True)
        self.nama_input.setCursor(Qt.PointingHandCursor)
        self.nama_input.setToolTip("Klik untuk memilih nama siswa")
        self.nama_input.setStyleSheet("""
            QLineEdit {
                border: 2px solid #e0e6ed;
                border-radius: 8px;
                padding: 8px 14px;
                font-size: 19px;
                font-weight: 400;
                background-color: #f8fafc;
                color: #1e293b;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QLineEdit:focus {
                border-color: #667eea;
                background-color: white;
            }
            QLineEdit:hover {
                border-color: #cbd5e1;
            }
        """)
        self.nama_input.installEventFilter(self)
        self.nama_input.setPlaceholderText("Klik untuk pilih nama siswa")
        self.nama_input.textChanged.connect(self.validate_nama_input)
        form_layout.addWidget(self.nama_input)

        # Separator line
        separator = QFrame()
        separator.setFrameShape(QFrame.HLine)
        separator.setStyleSheet("background-color: #e0e6ed; max-height: 1px;")
        form_layout.addWidget(separator)
        
        # Nilai Section - plain label
        nilai_label = QLabel("Nilai Mata Pelajaran (Range: 0-100)")
        nilai_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        nilai_label.setIndent(0)
        nilai_label.setContentsMargins(0, 0, 0, 0)
        nilai_label.setStyleSheet("""
            font-size: 19px; 
            font-weight: bold; 
            color: #2c3e50;
            background: transparent;
            border: none;
            font-family: 'Segoe UI', Arial, sans-serif;
            margin-bottom: 5px;
        """)
        form_layout.addWidget(nilai_label)
        
        # Create table widget for nilai input
        self.nilai_table = QTableWidget()
        self.nilai_table.setColumnCount(4)
        self.nilai_table.setHorizontalHeaderLabels(['Mapel', 'Semester 1', 'Semester 2', 'Rata-rata'])
        self.nilai_table.setMinimumHeight(350)
        self.nilai_table.setStyleSheet("""
            QTableWidget {
                border: 1px solid #e2e8f0;
                border-radius: 6px;
                background-color: #ffffff;
                gridline-color: #e2e8f0;
            }
            QTableWidget::item {
                padding: 4px;
                background-color: white;
            }
            QHeaderView::section {
                background-color: #f1f5f9;
                color: #2c3e50;
                padding: 14px 8px;
                border: none;
                border-right: 1px solid #e2e8f0;
                border-bottom: 1px solid #e2e8f0;
                font-weight: bold;
                font-size: 18px;
            }
        """)
        
        # Set header alignment and properties
        header = self.nilai_table.horizontalHeader()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(0, QHeaderView.Stretch)  # Mapel
        header.setSectionResizeMode(1, QHeaderView.Stretch)  # Semester 1
        header.setSectionResizeMode(2, QHeaderView.Stretch)  # Semester 2
        header.setSectionResizeMode(3, QHeaderView.Stretch)  # Rata-rata
        
        self.nilai_table.verticalHeader().setVisible(False)
        self.nilai_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.nilai_table.setSelectionMode(QAbstractItemView.NoSelection)
        self.nilai_table.setFocusPolicy(Qt.NoFocus)
        # Set custom delegate for input columns (Semester 1 and 2)
        # Must set delegate before creating persistent editors so the editors
        # created by openPersistentEditor inherit the delegate's alignment/style.
        delegate = LineEditDelegate()
        self.nilai_table.setItemDelegateForColumn(1, delegate)
        self.nilai_table.setItemDelegateForColumn(2, delegate)
        
        # Mata pelajaran list
        mapel_list = [
            ('Informatika', 'nilai_informatika'),
            ('Fisika', 'nilai_fisika'), 
            ('Kimia', 'nilai_kimia'),
            ('Biologi', 'nilai_biologi'),
            ('BIG Lanjut', 'nilai_big_lanjut'),
            ('Ekonomi', 'nilai_ekonomi'),
            ('MAT Lanjut', 'nilai_mat_lanjut'),
            ('SEJ Lanjut', 'nilai_sej_lanjut'),
            ('Sosiologi', 'nilai_sosiologi'),
            ('Geografi', 'nilai_geografi')
        ]
        
        self.nilai_inputs = {}
        self.nilai_sem1_inputs = {}  # Store semester 1 inputs
        self.nilai_sem2_inputs = {}  # Store semester 2 inputs
        
        self.nilai_table.setRowCount(len(mapel_list))
        
        for row, (mapel, key) in enumerate(mapel_list):
            # Column 0: Mapel name (read-only)
            mapel_item = QTableWidgetItem(mapel)
            mapel_item.setFlags(mapel_item.flags() & ~Qt.ItemIsEditable)
            mapel_item.setFont(QFont('Segoe UI', 10))
            mapel_item.setForeground(QColor('#2c3e50'))
            self.nilai_table.setItem(row, 0, mapel_item)
            
            # Column 1: Semester 1 input
            sem1_item = QTableWidgetItem('')
            sem1_item.setFlags(sem1_item.flags() | Qt.ItemIsEditable)
            sem1_item.setTextAlignment(Qt.AlignCenter)
            sem1_item.setFont(QFont('Segoe UI', 14))
            self.nilai_table.setItem(row, 1, sem1_item)
            self.nilai_sem1_inputs[key] = sem1_item
            self.nilai_table.openPersistentEditor(sem1_item)
            
            # Column 2: Semester 2 input
            sem2_item = QTableWidgetItem('')
            sem2_item.setFlags(sem2_item.flags() | Qt.ItemIsEditable)
            sem2_item.setTextAlignment(Qt.AlignCenter)
            sem2_item.setFont(QFont('Segoe UI', 14))
            self.nilai_table.setItem(row, 2, sem2_item)
            self.nilai_sem2_inputs[key] = sem2_item
            self.nilai_table.openPersistentEditor(sem2_item)
            
            # Column 3: Rata-rata (read-only, calculated)
            rata_item = QTableWidgetItem('otomatis')
            rata_item.setFlags(rata_item.flags() & ~Qt.ItemIsEditable)
            rata_item.setTextAlignment(Qt.AlignCenter)
            rata_item.setFont(QFont('Segoe UI', 10))
            rata_item.setForeground(QColor('#94a3b8'))
            self.nilai_table.setItem(row, 3, rata_item)
            
            # Store in nilai_inputs for backward compatibility with predict function
            self.nilai_inputs[key] = rata_item

            # Connect persistent editors so rata-rata updates as soon as user types
            sem1_index = self.nilai_table.model().index(row, 1)
            sem2_index = self.nilai_table.model().index(row, 2)
            sem1_editor = self.nilai_table.indexWidget(sem1_index)
            sem2_editor = self.nilai_table.indexWidget(sem2_index)

            if sem1_editor and hasattr(sem1_editor, 'textChanged'):
                sem1_editor.textChanged.connect(
                    lambda text, r=row: self._on_nilai_editor_changed(r, 1, text)
                )
            if sem2_editor and hasattr(sem2_editor, 'textChanged'):
                sem2_editor.textChanged.connect(
                    lambda text, r=row: self._on_nilai_editor_changed(r, 2, text)
                )
        
        # Set row height
        for row in range(len(mapel_list)):
            self.nilai_table.setRowHeight(row, 45)

        self._update_nilai_table_height()
        
        # Connect item changed signal to calculate rata-rata
        self.nilai_table.itemChanged.connect(self.on_nilai_changed)
        
        form_layout.addWidget(self.nilai_table)
        input_layout.addWidget(form_card)
        
        # Action Buttons - Simple layout without card
        btn_layout = QHBoxLayout()
        btn_layout.setSpacing(15)
        btn_layout.setContentsMargins(0, 10, 0, 0)
        
        # Predict button
        self.btn_predict = QPushButton("🔮 Prediksi")
        self.btn_predict.setFixedHeight(50)
        self.btn_predict.setCursor(Qt.PointingHandCursor)
        self.btn_predict.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #667eea, stop:1 #764ba2);
                color: white;
                border: none;
                border-radius: 8px;
                font-size: 17px;
                font-weight: bold;
                padding: 12px 32px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #5568d3, stop:1 #6a3b91);
            }
            QPushButton:pressed {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #4a5abc, stop:1 #5a3080);
            }
            QPushButton:disabled {
                background: #cbd5e1;
                color: #94a3b8;
            }
        """)
        self.btn_predict.clicked.connect(self.predict_and_save_data)
        self.btn_predict.setToolTip("Memprediksi dan menyimpan sebagai data baru")
        btn_layout.addWidget(self.btn_predict)
        
        # Update button
        self.btn_update = QPushButton("✏️ Update")
        self.btn_update.setFixedHeight(50)
        self.btn_update.setCursor(Qt.PointingHandCursor)
        self.btn_update.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #11998e, stop:1 #38ef7d);
                color: white;
                border: none;
                border-radius: 8px;
                font-size: 17px;
                font-weight: bold;
                padding: 12px 32px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #0d7a72, stop:1 #2dd465);
            }
            QPushButton:disabled {
                background: #cbd5e1;
                color: #94a3b8;
            }
        """)
        self.btn_update.clicked.connect(self.update_data)
        self.btn_update.setToolTip("Mengupdate data yang dipilih dengan prediksi baru")
        btn_layout.addWidget(self.btn_update)
        
        # Clear Form button
        self.btn_clear = QPushButton("🗑️ Clear Form")
        self.btn_clear.setFixedHeight(50)
        self.btn_clear.setCursor(Qt.PointingHandCursor)
        self.btn_clear.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #ee0979, stop:1 #ff6a00);
                color: white;
                border: none;
                border-radius: 8px;
                font-size: 17px;
                font-weight: bold;
                padding: 12px 32px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #d4076a, stop:1 #e65900);
            }
        """)
        self.btn_clear.clicked.connect(lambda: self.clear_form(show_empty_notification=True))
        btn_layout.addWidget(self.btn_clear)
        
        btn_layout.addStretch()
        input_layout.addLayout(btn_layout)
        input_layout.addStretch()

        scroll_area.setWidget(input_widget)
        page_layout.addWidget(scroll_area)

        self.stacked_widget.addWidget(input_page)
    
    #ui untuk tab data siswa
    def create_data_tab(self):
        data_widget = QWidget()
        data_widget.setStyleSheet("""
            QWidget {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
                    stop:0 #f5f7fa, stop:1 #e8ecf1);
            }
        """)
        
        # Main layout
        data_layout = QVBoxLayout(data_widget)
        data_layout.setContentsMargins(32, 20, 32, 20)
        data_layout.setSpacing(14)
        
        # Header with gradient
        header_widget = QWidget()
        header_widget.setStyleSheet("""
            QWidget {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #667eea, stop:1 #764ba2);
                border-radius: 12px;
            }
        """)
        header_layout = QVBoxLayout(header_widget)
        header_layout.setContentsMargins(30, 20, 30, 20)
        header_layout.setSpacing(5)
        
        title_label = QLabel("📊 Data Siswa")
        title_label.setStyleSheet("""
            font-size: 30px; 
            font-weight: bold; 
            color: white;
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        header_layout.addWidget(title_label)
        
        subtitle_label = QLabel("Kelola dan lihat semua data hasil prediksi siswa")
        subtitle_label.setStyleSheet("""
            font-size: 18px; 
            color: rgba(255, 255, 255, 0.9);
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        header_layout.addWidget(subtitle_label)
        
        data_layout.addWidget(header_widget)
        
        # Main Content Card
        content_card = QWidget()
        content_card.setObjectName("dataContentCard")
        content_card.setStyleSheet("""
            QWidget#dataContentCard {
                background-color: white;
                border-radius: 12px;
                border: 1px solid #e0e6ed;
            }
        """)
        shadow = QGraphicsDropShadowEffect()
        shadow.setBlurRadius(15)
        shadow.setXOffset(0)
        shadow.setYOffset(5)
        shadow.setColor(QColor(0, 0, 0, 30))
        content_card.setGraphicsEffect(shadow)
        
        content_layout = QVBoxLayout(content_card)
        content_layout.setContentsMargins(24, 18, 24, 18)
        content_layout.setSpacing(14)
        
        # Search Section
        search_layout = QHBoxLayout()
        search_layout.setSpacing(10)
        
        search_label = QLabel("Pencarian:")
        search_label.setStyleSheet("""
            font-size: 16px;
            color: #2c3e50;
            font-weight: bold;
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        search_layout.addWidget(search_label)
        
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("Cari nama siswa...")
        self.search_input.setMinimumHeight(56)
        self.search_input.setMaximumHeight(56)
        self.search_input.setMinimumWidth(300)
        self.search_input.setStyleSheet("""
            QLineEdit {
                border: 2px solid #e0e6ed;
                border-radius: 8px;
                padding: 8px 14px;
                font-size: 19px;
                background-color: #f8fafc;
                color: #1e293b;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QLineEdit:focus {
                border-color: #667eea;
                background-color: white;
            }
            QLineEdit:hover {
                border-color: #cbd5e1;
            }
        """)
        
        # Validator untuk pencarian - hanya huruf dan spasi
        search_validator = QRegExpValidator(QRegExp("[a-zA-Z ]*"))
        self.search_input.setValidator(search_validator)
        self.search_input.setFocusPolicy(Qt.ClickFocus)
        
        # Connect search input signals
        self.search_input.textChanged.connect(self.search_by_name)
        self.search_input.returnPressed.connect(self.search_by_name)
        
        search_layout.addWidget(self.search_input)
        
        # Clear search button
        btn_clear_search = QPushButton("✖ Clear")
        btn_clear_search.setFixedHeight(56)
        btn_clear_search.setCursor(Qt.PointingHandCursor)
        btn_clear_search.setStyleSheet("""
            QPushButton {
                background-color: #dc3545;
                color: white;
                border: none;
                border-radius: 8px;
                font-weight: bold;
                font-size: 16px;
                padding: 10px 20px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background-color: #c82333;
            }
        """)
        btn_clear_search.clicked.connect(self.clear_search)
        btn_clear_search.setToolTip("Hapus pencarian")
        search_layout.addWidget(btn_clear_search)
        search_layout.addStretch()
        
        content_layout.addLayout(search_layout)
        
        # Action buttons
        action_layout = QHBoxLayout()
        action_layout.setSpacing(12)
        
        # Delete button
        btn_delete = QPushButton("Delete")
        btn_delete.setFixedHeight(45)
        btn_delete.setCursor(Qt.PointingHandCursor)
        btn_delete.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #ee0979, stop:1 #ff6a00);
                color: white;
                border: none;
                border-radius: 8px;
                font-weight: bold;
                font-size: 16px;
                padding: 10px 24px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #d4076a, stop:1 #e65900);
            }
        """)
        btn_delete.clicked.connect(self.delete_data)
        action_layout.addWidget(btn_delete)
        
        # Edit button
        self.btn_edit = QPushButton("Edit")
        self.btn_edit.setFixedHeight(45)
        self.btn_edit.setCursor(Qt.PointingHandCursor)
        self.btn_edit.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #667eea, stop:1 #764ba2);
                color: white;
                border: none;
                border-radius: 8px;
                font-weight: bold;
                font-size: 16px;
                padding: 10px 24px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #5568d3, stop:1 #6a3b91);
            }
            QPushButton:disabled {
                background: #cbd5e1;
                color: #94a3b8;
            }
        """)
        self.btn_edit.clicked.connect(self.edit_selected_data)
        self.btn_edit.setEnabled(False)
        self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
        action_layout.addWidget(self.btn_edit)
        
        # Preview button
        self.btn_preview = QPushButton("Preview")
        self.btn_preview.setFixedHeight(45)
        self.btn_preview.setCursor(Qt.PointingHandCursor)
        self.btn_preview.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #a770ef, stop:1 #cf8bf3);
                color: white;
                border: none;
                border-radius: 8px;
                font-weight: bold;
                font-size: 16px;
                padding: 10px 24px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #9560d8, stop:1 #bd7ae0);
            }
            QPushButton:disabled {
                background: #cbd5e1;
                color: #94a3b8;
            }
        """)
        self.btn_preview.clicked.connect(self.preview_prediction)
        self.btn_preview.setEnabled(False)
        self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
        action_layout.addWidget(self.btn_preview)
        
        # Export button
        btn_export = QPushButton("Export")
        btn_export.setFixedHeight(45)
        btn_export.setCursor(Qt.PointingHandCursor)
        btn_export.setStyleSheet("""
            QPushButton {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #11998e, stop:1 #38ef7d);
                color: white;
                border: none;
                border-radius: 8px;
                font-weight: bold;
                font-size: 16px;
                padding: 10px 24px;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #0d7a72, stop:1 #2dd465);
            }
        """)
        btn_export.clicked.connect(self.export_to_excel)
        action_layout.addWidget(btn_export)
        
        action_layout.addStretch()
        content_layout.addLayout(action_layout)
        
        # Hidden info label (masih dibutuhkan untuk fungsi internal tapi tidak ditampilkan)
        self.info_label = QLabel()
        self.info_label.setVisible(False)
        
        # Table
        self.table = QTableWidget()
        self.setup_table()
        self.table.installEventFilter(self)
        self.table.viewport().installEventFilter(self)
        content_layout.addWidget(self.table, 1)
        
        # Pagination
        pagination_layout = QHBoxLayout()
        pagination_layout.setContentsMargins(0, 4, 0, 0)
        pagination_layout.setSpacing(14)
        pagination_layout.setAlignment(Qt.AlignCenter)
        pagination_layout.addStretch()
        
        self.btn_prev = QPushButton("◀ Previous")
        self.btn_prev.setFixedHeight(40)
        self.btn_prev.setCursor(Qt.PointingHandCursor)
        self.btn_prev.setStyleSheet("""
            QPushButton {
                background-color: #64748b; 
                color: white; 
                padding: 10px 20px; 
                font-weight: bold;
                font-weight: 600; 
                font-size: 16px;
                border-radius: 8px;
                border: none;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background-color: #475569;
            }
            QPushButton:disabled {
                background-color: #cbd5e1;
                color: #94a3b8;
            }
        """)
        self.btn_prev.clicked.connect(self.previous_page)
        pagination_layout.addWidget(self.btn_prev)
        
        self.page_label = QLabel()
        self.page_label.setAlignment(Qt.AlignCenter)
        self.page_label.setMinimumWidth(180)
        self.page_label.setStyleSheet("""
            margin: 0px 20px; 
            font-weight: 600; 
            font-size: 16px; 
            color: #2c3e50;
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        pagination_layout.addWidget(self.page_label)
        
        self.btn_next = QPushButton("Next ▶")
        self.btn_next.setFixedHeight(40)
        self.btn_next.setCursor(Qt.PointingHandCursor)
        self.btn_next.setStyleSheet("""
            QPushButton {
                background-color: #64748b; 
                color: white; 
                padding: 10px 20px; 
                font-weight: bold;
                font-weight: 600; 
                font-size: 18px;
                border-radius: 8px;
                border: none;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton:hover {
                background-color: #475569;
            }
            QPushButton:disabled {
                background-color: #cbd5e1;
                color: #94a3b8;
            }
        """)
        self.btn_next.clicked.connect(self.next_page)
        pagination_layout.addWidget(self.btn_next)
        
        pagination_layout.addStretch()

        pagination_widget = QWidget()
        pagination_widget.setStyleSheet("background: transparent;")
        pagination_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        pagination_widget.setFixedHeight(52)
        pagination_widget.setLayout(pagination_layout)
        content_layout.addWidget(pagination_widget)
        
        # Let the content card consume available vertical space so table rows remain visible.
        data_layout.addWidget(content_card, 1)
        
        # Install event filter to detect clicks outside table
        data_widget.installEventFilter(self)
        
        self.stacked_widget.addWidget(data_widget)
    
    #ui untuk tab performa sistem
    def create_performa_tab(self):
        performa_widget = QWidget()
        performa_widget.setStyleSheet("""
            QWidget {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
                    stop:0 #f5f7fa, stop:1 #e8ecf1);
            }
        """)
        
        # Main layout dengan scroll area
        main_performa_layout = QVBoxLayout(performa_widget)
        main_performa_layout.setContentsMargins(0, 0, 0, 0)
        
        # Create scroll area
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setFrameShape(QFrame.NoFrame)
        scroll_area.setStyleSheet("QScrollArea { border: none; background: transparent; }")
        
        # Content widget for scroll area
        content_widget = QWidget()
        content_widget.setStyleSheet("background: transparent;")
        performa_layout = QVBoxLayout(content_widget)
        performa_layout.setContentsMargins(40, 30, 40, 30)
        performa_layout.setSpacing(25)
        
        # Header with gradient
        header_widget = QWidget()
        header_widget.setStyleSheet("""
            QWidget {
                background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                    stop:0 #667eea, stop:1 #764ba2);
                border-radius: 12px;
            }
        """)
        header_layout = QVBoxLayout(header_widget)
        header_layout.setContentsMargins(30, 20, 30, 20)
        header_layout.setSpacing(5)
        
        title_label = QLabel("📈 Performa Sistem")
        title_label.setStyleSheet("""
            font-size: 30px; 
            font-weight: bold; 
            color: white;
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        header_layout.addWidget(title_label)
        
        subtitle_label = QLabel("Analisis dan evaluasi performa klasifikasi K-Nearest Neighbors")
        subtitle_label.setStyleSheet("""
            font-size: 18px; 
            color: rgba(255, 255, 255, 0.9);
            background: transparent;
            font-family: 'Segoe UI', Arial, sans-serif;
        """)
        header_layout.addWidget(subtitle_label)
        
        performa_layout.addWidget(header_widget)
        
        # Statistics Cards Container
        self.stats_container = QWidget()
        self.stats_container.setStyleSheet("background-color: transparent;")
        self.stats_layout = QHBoxLayout(self.stats_container)
        self.stats_layout.setSpacing(15)
        self.stats_layout.setContentsMargins(0, 0, 0, 0)
        
        performa_layout.addWidget(self.stats_container)
        
        # Section 1: Confusion Matrix
        confusion_card = QWidget()
        confusion_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 12px;
                border: 1px solid #e0e6ed;
            }
        """)
        shadow1 = QGraphicsDropShadowEffect()
        shadow1.setBlurRadius(15)
        shadow1.setXOffset(0)
        shadow1.setYOffset(5)
        shadow1.setColor(QColor(0, 0, 0, 30))
        confusion_card.setGraphicsEffect(shadow1)
        
        confusion_card_layout = QVBoxLayout(confusion_card)
        confusion_card_layout.setContentsMargins(25, 20, 25, 20)
        confusion_card_layout.setSpacing(15)
        
        confusion_title = QLabel("Confusion Matrix")
        confusion_title.setStyleSheet("""
            font-size: 17px;
            font-weight: bold;
            color: #2c3e50;
            background: transparent;
            border: none;
            font-family: 'Segoe UI', Arial, sans-serif;
            margin-bottom: 5px;
        """)
        confusion_card_layout.addWidget(confusion_title)
        
        # Horizontal layout for matrix and explanation
        confusion_content = QHBoxLayout()
        confusion_content.setSpacing(20)
        
        # Matrix visualization
        self.perf_figure = Figure(figsize=(6, 4), dpi=100)
        self.perf_figure.patch.set_facecolor('white')
        self.perf_canvas = FigureCanvas(self.perf_figure)
        self.perf_canvas.setFixedHeight(320)
        confusion_content.addWidget(self.perf_canvas, 3)
        
        # Explanation
        self.perf_confusion_explanation = QLabel()
        self.perf_confusion_explanation.setFont(QFont("Segoe UI", 11))
        self.perf_confusion_explanation.setWordWrap(True)
        self.perf_confusion_explanation.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.perf_confusion_explanation.setStyleSheet("""
            QLabel {
                background: transparent;
                color: #2c3e50;
                font-family: 'Segoe UI', Arial, sans-serif;
                line-height: 1.6;
                padding: 10px;
            }
        """)
        confusion_content.addWidget(self.perf_confusion_explanation, 2)
        confusion_card_layout.addLayout(confusion_content)
        
        performa_layout.addWidget(confusion_card)
        
        # Section 2: ROC Curves
        roc_card = QWidget()
        roc_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 12px;
                border: 1px solid #e0e6ed;
            }
        """)
        shadow2 = QGraphicsDropShadowEffect()
        shadow2.setBlurRadius(15)
        shadow2.setXOffset(0)
        shadow2.setYOffset(5)
        shadow2.setColor(QColor(0, 0, 0, 30))
        roc_card.setGraphicsEffect(shadow2)
        
        roc_card_layout = QVBoxLayout(roc_card)
        roc_card_layout.setContentsMargins(25, 20, 25, 20)
        roc_card_layout.setSpacing(15)
        
        roc_title = QLabel("ROC AUC Curve")
        roc_title.setStyleSheet("""
            font-size: 17px;
            font-weight: bold;
            color: #2c3e50;
            background: transparent;
            border: none;
            font-family: 'Segoe UI', Arial, sans-serif;
            margin-bottom: 5px;
        """)
        roc_card_layout.addWidget(roc_title)
        
        # Horizontal layout for ROC and explanation
        roc_content = QHBoxLayout()
        roc_content.setSpacing(20)
        
        # ROC visualization
        self.roc_figure = Figure(figsize=(6, 4), dpi=100)
        self.roc_figure.patch.set_facecolor('white')
        self.roc_canvas = FigureCanvas(self.roc_figure)
        self.roc_canvas.setFixedHeight(320)
        roc_content.addWidget(self.roc_canvas, 3)
        
        # Explanation
        self.perf_roc_explanation = QLabel()
        self.perf_roc_explanation.setFont(QFont("Segoe UI", 11))
        self.perf_roc_explanation.setWordWrap(True)
        self.perf_roc_explanation.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.perf_roc_explanation.setStyleSheet("""
            QLabel {
                background: transparent;
                color: #2c3e50;
                font-family: 'Segoe UI', Arial, sans-serif;
                line-height: 1.6;
                padding: 10px;
            }
        """)
        roc_content.addWidget(self.perf_roc_explanation, 2)
        roc_card_layout.addLayout(roc_content)
        
        performa_layout.addWidget(roc_card)
        
        # Section 3: Classification Report
        class_card = QWidget()
        class_card.setStyleSheet("""
            QWidget {
                background-color: white;
                border-radius: 12px;
                border: 1px solid #e0e6ed;
            }
        """)
        shadow3 = QGraphicsDropShadowEffect()
        shadow3.setBlurRadius(15)
        shadow3.setXOffset(0)
        shadow3.setYOffset(5)
        shadow3.setColor(QColor(0, 0, 0, 30))
        class_card.setGraphicsEffect(shadow3)
        
        class_card_layout = QVBoxLayout(class_card)
        class_card_layout.setContentsMargins(25, 20, 25, 20)
        class_card_layout.setSpacing(15)
        
        class_title = QLabel("Classification Report")
        class_title.setStyleSheet("""
            font-size: 17px;
            font-weight: bold;
            color: #2c3e50;
            background: transparent;
            border: none;
            font-family: 'Segoe UI', Arial, sans-serif;
            margin-bottom: 5px;
        """)
        class_card_layout.addWidget(class_title)
        
        # Horizontal layout for report and explanation
        class_content = QHBoxLayout()
        class_content.setSpacing(20)
        
        # Classification report
        self.perf_report_text = QTextEdit()
        self.perf_report_text.setReadOnly(True)
        self.perf_report_text.setFont(QFont("Consolas", 11))
        self.perf_report_text.setFixedHeight(300)
        self.perf_report_text.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.perf_report_text.setWordWrapMode(QTextOption.NoWrap)
        self.perf_report_text.setFrameShape(QFrame.NoFrame)
        self.perf_report_text.setStyleSheet("""
            QTextEdit {
                background: transparent;
                border: none;
                padding: 0px;
                font-family: 'Consolas', 'Monaco', monospace;
                line-height: 1.4;
                color: #2c3e50;
            }
        """)
        class_content.addWidget(self.perf_report_text, 3)
        
        # Explanation
        self.perf_class_explanation = QLabel()
        self.perf_class_explanation.setFont(QFont("Segoe UI", 11))
        self.perf_class_explanation.setWordWrap(True)
        self.perf_class_explanation.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        self.perf_class_explanation.setStyleSheet("""
            QLabel {
                background: transparent;
                color: #2c3e50;
                font-family: 'Segoe UI', Arial, sans-serif;
                line-height: 1.6;
                padding: 10px;
            }
        """)
        class_content.addWidget(self.perf_class_explanation, 2)
        class_card_layout.addLayout(class_content)
        
        performa_layout.addWidget(class_card)
        performa_layout.addStretch()
        
        # Set content widget to scroll area and add to main layout
        scroll_area.setWidget(content_widget)
        main_performa_layout.addWidget(scroll_area)
        
        self.stacked_widget.addWidget(performa_widget)
    
    #fungsi untuk update total siswa dan jumlah siswa per paket pada halaman performa
    def refresh_performa_stats(self):
        try:
            # Clear existing cards
            for i in reversed(range(self.stats_layout.count())):
                child = self.stats_layout.itemAt(i).widget()
                if child:
                    child.deleteLater()
            
            # Get data from database
            data = ambil_semua_hasil_prediksi()
            total_siswa = len(data)
            
            # Count students by paket
            paket_counts = {}
            for item in data:
                paket = item.get('paket_prediksi', 0)
                paket_counts[paket] = paket_counts.get(paket, 0) + 1
            
            # Define modern colors for each paket
            paket_colors = {
                1: "#667eea",   # Purple-blue (primary theme)
                2: "#764ba2",   # Purple
                3: "#f093fb",   # Pink
                4: "#4facfe",   # Light blue
                5: "#43e97b",   # Green
                6: "#fa709a"    # Rose
            }
            
            # Create Total Siswa card
            total_card = self.create_stat_card("Total Siswa", str(total_siswa), "#2c3e50")
            self.stats_layout.addWidget(total_card)
            
            # Create cards for each paket (1-6)
            for paket in range(1, 7):
                count = paket_counts.get(paket, 0)
                color = paket_colors.get(paket, "#95a5a6")
                paket_card = self.create_stat_card(f"Paket {paket}", str(count), color)
                self.stats_layout.addWidget(paket_card)
                
        except Exception as e:
            print(f"Error refreshing performa stats: {str(e)}")
            # Create default cards if error
            total_card = self.create_stat_card("Total Siswa", "0", "#34495e")
            self.stats_layout.addWidget(total_card)
            
            for paket in range(1, 7):
                paket_card = self.create_stat_card(f"Paket {paket}", "0", "#95a5a6")
                self.stats_layout.addWidget(paket_card)
    
    #Fungsi halaman performa untuk latih ulang model dan tampilkan metrik performa terbaru
    def generate_performance_display(self):
        try:
            # Always retrain model to get current accurate performance
            # print("🔄 Retraining model for current performance metrics...")
            
            # Create fresh classifier instance
            fresh_classifier = JurusanKNNClassifier()
            
            # Train with current dataset to get fresh results
            accuracy, _ = fresh_classifier.train(
                config.DATASET_PATH,
                test_data_path=getattr(config, 'TEST_DATASET_PATH', None)
            )
            
            # Update main classifier with fresh results
            self.classifier = fresh_classifier
            
            # print(f"✅ Fresh model accuracy: {accuracy:.4f} ({accuracy:.2%})")
            
            # Use fresh evaluation results
            if (hasattr(self.classifier, 'confusion_matrix') and 
                hasattr(self.classifier, 'y_test') and 
                hasattr(self.classifier, 'y_pred')):
                
                cm = self.classifier.confusion_matrix
                y_test = self.classifier.y_test
                y_pred = self.classifier.y_pred
                
                # Generate confusion matrix plot
                self.plot_confusion_matrix_perf(cm, sorted(set(y_test)))
                self.display_confusion_matrix_explanation_perf()
                
                # Generate ROC AUC curve plot  
                if hasattr(self.classifier, 'y_pred_proba'):
                    self.plot_roc_curve_perf(y_test, self.classifier.y_pred_proba)
                else:
                    # Fallback: use predicted classes (less accurate for ROC)
                    self.plot_roc_curve_perf(y_test, y_pred)
                self.display_roc_explanation_perf()
                
                # Generate classification report with fresh data
                from sklearn.metrics import classification_report
                report = classification_report(y_test, y_pred, output_dict=True, zero_division=0)
                self.display_classification_report_perf(report)
                
                # Update status bar with current accuracy
                self.statusBar().showMessage(f'Performance updated - Current Accuracy: {accuracy:.2%}')
                
                # print(f"✅ Performance display updated successfully with accuracy: {accuracy:.2%}")
                
            else:
                self.show_error_perf("Fresh training data not available")
                
        except Exception as e:
            # print(f"Error in generate_performance_display: {str(e)}")
            self.show_error_perf(f"Error generating performance report: {str(e)}")
    
    # Fungsi untuk plot confusion matrix pada halaman performa
    def plot_confusion_matrix_perf(self, cm, classes):
        # Clear previous plot
        self.perf_figure.clear()
        
        ax = self.perf_figure.add_subplot(111)
        im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues, aspect='auto')
        
        # Add colorbar
        cbar = self.perf_figure.colorbar(im, ax=ax, shrink=0.8)
        cbar.ax.tick_params(labelsize=10)
        
        # Set labels
        ax.set_xticks(np.arange(cm.shape[1]))
        ax.set_yticks(np.arange(cm.shape[0]))
        ax.set_xticklabels(classes, fontsize=11)
        ax.set_yticklabels(classes, fontsize=11)
        
        # Set axis labels
        ax.set_ylabel('True Labels', fontsize=12, fontweight='bold')
        ax.set_xlabel('Predicted Labels', fontsize=12, fontweight='bold')
        
        # Rotate the tick labels
        plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")
        
        # Add text annotations
        thresh = cm.max() / 2.
        for i in range(cm.shape[0]):
            for j in range(cm.shape[1]):
                text_color = "white" if cm[i, j] > thresh else "black"
                ax.text(j, i, format(cm[i, j], 'd'),
                       ha="center", va="center",
                       color=text_color,
                       fontweight='bold',
                       fontsize=11)
        
        # Set title with accuracy information - REMOVED to avoid text cutoff
        # accuracy = self.classifier.training_accuracy if hasattr(self.classifier, 'training_accuracy') else 0
        # ax.set_title(f"Confusion Matrix\nAccuracy: {accuracy:.2%}", fontweight='bold', fontsize=14, pad=20)
        
        # Adjust layout with better margins
        self.perf_figure.subplots_adjust(left=0.12, bottom=0.25, right=0.9, top=0.95)
        self.perf_canvas.draw()
    
    # Fungsi untuk plot ROC curve pada halaman performa
    def display_classification_report_perf(self, report_dict):
        # Get accuracy for header
        accuracy = report_dict['accuracy']
        
        # Create header with prominent accuracy display
        accuracy_header = f"🎯 SYSTEM ACCURACY: {accuracy:.4f} ({accuracy:.2%})\n"
        accuracy_header += f"{'='*65}\n\n"
        
        # Create formatted text report
        header = f"{'':>12} {'precision':>10} {'recall':>10} {'f1-score':>10} {'support':>8}\n"
        separator = "=" * 65 + "\n"
        
        report_text = accuracy_header + header + separator
        
        # Add metrics for each class
        classes = [key for key in report_dict.keys() if key not in ['accuracy', 'macro avg', 'weighted avg']]
        
        # Sort classes numerically
        try:
            sorted_classes = sorted(classes, key=lambda x: int(x))
        except (ValueError, TypeError):
            sorted_classes = sorted(classes)
        
        for class_name in sorted_classes:
            metrics = report_dict[class_name]
            class_label = f"Paket {class_name}" if str(class_name).isdigit() else str(class_name)
            report_text += f"{class_label:>12} {metrics['precision']:>10.2f} {metrics['recall']:>10.2f} {metrics['f1-score']:>10.2f} {int(metrics['support']):>8}\n"
        
        # Add separator line
        report_text += "\n" + "-" * 65 + "\n"
        
        # Add accuracy
        total_support = sum(report_dict[key]['support'] for key in classes)
        report_text += f"{'accuracy':>12} {'':>10} {'':>10} {accuracy:>10.2f} {int(total_support):>8}\n"
        
        # Add macro avg
        macro_avg = report_dict['macro avg']
        report_text += f"{'macro avg':>12} {macro_avg['precision']:>10.2f} {macro_avg['recall']:>10.2f} {macro_avg['f1-score']:>10.2f} {int(macro_avg['support']):>8}\n"
        
        # Add weighted avg
        weighted_avg = report_dict['weighted avg']
        report_text += f"{'weighted avg':>12} {weighted_avg['precision']:>10.2f} {weighted_avg['recall']:>10.2f} {weighted_avg['f1-score']:>10.2f} {int(weighted_avg['support']):>8}\n"
        
        # Add footer note
        report_text += f"\n{'='*65}\n"
        report_text += f"📊 Test samples: {int(total_support)} | Train source: {config.DATASET_PATH}\n"
        report_text += (
            f"🔧 Algorithm: K-NN (k={self.classifier.knn.n_neighbors}, "
            f"weights={self.classifier.knn.weights}, metric={self.classifier.knn.metric}) | "
            f"CV: {config.CROSS_VALIDATION_FOLDS}-fold\n"
        )
        
        self.perf_report_text.setPlainText(report_text)
        
        # Also display explanation
        self.display_classification_explanation_perf()
    
    #penjelasan grafik
    def display_confusion_matrix_explanation_perf(self):
        cm = getattr(self.classifier, 'confusion_matrix', None)
        if cm is not None and cm.sum() > 0:
            total_prediksi_benar = int(np.trace(cm))
            total_data = int(cm.sum())
            summary = f"Total prediksi benar yang berada pada diagonal adalah {total_prediksi_benar} dari {total_data} data."
        else:
            summary = "Data confusion matrix belum tersedia."

        explanation_text = (
            "Grafik ini menggambarkan sebaran prediksi untuk setiap kelas.\n"
            "Nilai pada diagonal menunjukkan prediksi yang tepat, sedangkan perbedaan intensitas warna biru menunjukkan banyaknya jumlah data pada setiap sel.\n\n"
            f"{summary}"
        )

        self.perf_confusion_explanation.setText(explanation_text)
    
    def display_roc_explanation_perf(self):
        """Display explanation for ROC curves in performa tab"""
        auc_values = getattr(self, 'perf_auc_values', [])
        if auc_values:
            avg_auc = float(np.mean(auc_values))
            min_auc = float(np.min(auc_values))
            summary = f"Nilai AUC rata-rata sebesar {avg_auc:.3f} dan nilai minimum {min_auc:.3f}, yang menunjukkan masih ada kelas yang relatif lebih sulit dibedakan."
        else:
            summary = "Nilai AUC belum tersedia."

        explanation_text = """
Grafik ini menunjukkan kemampuan model dalam membedakan setiap paket.
Semakin mendekati 1, nilai AUC menandakan kemampuan pemisahan kelas yang semakin baik.

{summary}
        """

        self.perf_roc_explanation.setText(
            explanation_text.strip().format(summary=summary)
        )
    
    def display_classification_explanation_perf(self):
        acc = getattr(self.classifier, 'training_accuracy', None)
        if acc is None:
            result = "Akurasi belum tersedia."
        else:
            level = "Sangat Baik" if acc >= 0.85 else "Baik" if acc >= 0.75 else "Cukup" if acc >= 0.65 else "Perlu perbaikan"
            result = f"Akurasi model mencapai {acc:.2%}, sehingga performanya tergolong {level.lower()}."

        explanation_text = """
Grafik ini merangkum kualitas prediksi untuk setiap paket.
Precision menunjukkan ketepatan prediksi, recall menunjukkan kemampuan menemukan data yang benar, dan F1-score menggambarkan keseimbangan keduanya.

{result}
        """

        self.perf_class_explanation.setText(explanation_text.strip().format(result=result))
    
    def plot_roc_curve_perf(self, y_test, y_pred_proba):
        """Plot ROC AUC curve for performa tab"""
        try:
            # Clear previous plot
            self.roc_figure.clear()
            self.perf_auc_values = []
            
            # Get unique classes from classifier
            classes = self.classifier.knn.classes_
            n_classes = len(classes)
            
            # Binarize the true labels
            y_test_bin = label_binarize(y_test, classes=classes)
            
            # Handle single class case
            if n_classes == 2:
                # For binary classification, use probability of positive class
                y_score = y_pred_proba[:, 1]
                y_true = (y_test == classes[1]).astype(int)
                
                # Compute ROC curve and ROC area
                fpr, tpr, _ = roc_curve(y_true, y_score)
                roc_auc = auc(fpr, tpr)
                self.perf_auc_values = [float(roc_auc)]
                
                # Plot ROC curve
                ax = self.roc_figure.add_subplot(111)
                ax.plot(fpr, tpr, color='darkorange', lw=2, 
                       label=f'ROC curve (AUC = {roc_auc:.3f})')
                ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', 
                       label='Random Classifier')
                ax.set_xlim([0.0, 1.0])
                ax.set_ylim([0.0, 1.05])
                ax.set_xlabel('False Positive Rate', fontsize=10)
                ax.set_ylabel('True Positive Rate', fontsize=10)
                ax.set_title('ROC Curve (Binary)', fontweight='bold', fontsize=11)
                ax.legend(loc="lower right", fontsize=9)
                ax.grid(True, alpha=0.3)
                
            else:
                # Multi-class ROC
                ax = self.roc_figure.add_subplot(111)
                
                # Compute ROC curve and ROC area for each class
                fpr = dict()
                tpr = dict()
                roc_auc = dict()
                
                for i in range(n_classes):
                    # Get true binary labels for this class
                    y_true_class = (y_test == classes[i]).astype(int)
                    # Get predicted probabilities for this class
                    y_score_class = y_pred_proba[:, i]
                    
                    fpr[i], tpr[i], _ = roc_curve(y_true_class, y_score_class)
                    roc_auc[i] = auc(fpr[i], tpr[i])

                self.perf_auc_values = [float(roc_auc[i]) for i in range(n_classes)]
                
                # Plot ROC curve for each class
                colors = ['blue', 'red', 'green', 'orange', 'purple', 'brown']
                for i in range(n_classes):
                    color = colors[i % len(colors)]
                    ax.plot(fpr[i], tpr[i], color=color, lw=2,
                           label=f'Paket {classes[i]} (AUC = {roc_auc[i]:.3f})')
                
                # Plot random classifier line
                ax.plot([0, 1], [0, 1], 'k--', lw=2, label='Random Classifier', alpha=0.8)
                
                # Compute macro-average ROC curve and ROC area
                try:
                    all_fpr = np.unique(np.concatenate([
                        fpr[i] for i in range(n_classes)
                    ]))
                    mean_tpr = np.zeros_like(all_fpr)

                    for i in range(n_classes):
                        mean_tpr += np.interp(all_fpr, fpr[i], tpr[i])

                    mean_tpr /= n_classes
                    roc_auc_macro = float(np.mean([roc_auc[i] for i in range(n_classes)]))

                    ax.plot(all_fpr, mean_tpr,
                           label=f'Macro-avg (AUC = {roc_auc_macro:.3f})',
                           color='gold', linestyle=':', linewidth=3)
                except:
                    pass  # Skip macro-average if calculation fails
                
                ax.set_xlim([0.0, 1.0])
                ax.set_ylim([0.0, 1.05])
                ax.set_xlabel('False Positive Rate', fontsize=10)
                ax.set_ylabel('True Positive Rate', fontsize=10)
                ax.set_title('Multi-class ROC Curves', fontweight='bold', fontsize=11)
                
                # Position legend to avoid overlap
                ax.legend(loc="center left", bbox_to_anchor=(1, 0.5), fontsize=8)
                ax.grid(True, alpha=0.3)
            
            # Adjust layout
            self.roc_figure.subplots_adjust(left=0.15, bottom=0.15, right=0.75, top=0.9)
            self.roc_canvas.draw()
            
        except Exception as e:
            print(f"Error plotting ROC curve: {e}")
            # Show error in ROC canvas
            ax = self.roc_figure.add_subplot(111)
            ax.text(0.5, 0.5, f'Error plotting ROC curve:\\n{str(e)}', 
                   ha='center', va='center', transform=ax.transAxes,
                   fontsize=10, color='red')
            ax.set_title("ROC Plot Error", fontweight='bold')
            self.roc_canvas.draw()
    
    #ketika terjadi error pada halaman performa, tampilkan pesan error di semua area yang relevan
    def show_error_perf(self, message):
        # Show error in confusion matrix
        ax1 = self.perf_figure.add_subplot(111)
        ax1.text(0.5, 0.5, message, 
               ha='center', va='center', transform=ax1.transAxes,
               fontsize=12, color='red')
        ax1.set_title("Error", fontweight='bold')
        self.perf_canvas.draw()
        
        # Show error in ROC curve
        ax2 = self.roc_figure.add_subplot(111)
        ax2.text(0.5, 0.5, message, 
               ha='center', va='center', transform=ax2.transAxes,
               fontsize=12, color='red')
        ax2.set_title("Error", fontweight='bold')
        self.roc_canvas.draw()
        
        # Show error in all text areas
        self.perf_report_text.setPlainText(f"Error: {message}")
        self.perf_confusion_explanation.setText(f"Error: {message}")
        self.perf_roc_explanation.setText(f"Error: {message}")
        self.perf_class_explanation.setText(f"Error: {message}")
    
    def create_stat_card(self, title, value, color):
        """Create a modern statistics card"""
        card = QWidget()
        card.setFixedHeight(110)
        card.setStyleSheet(f"""
            QWidget {{
                background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
                    stop:0 {color}, stop:1 {self.darken_color(color)});
                border-radius: 10px;
            }}
        """)
        
        # Add shadow effect
        shadow = QGraphicsDropShadowEffect()
        shadow.setBlurRadius(12)
        shadow.setXOffset(0)
        shadow.setYOffset(4)
        shadow.setColor(QColor(0, 0, 0, 25))
        card.setGraphicsEffect(shadow)
        
        layout = QVBoxLayout(card)
        layout.setAlignment(Qt.AlignCenter)
        layout.setContentsMargins(15, 15, 15, 15)
        
        value_label = QLabel(value)
        value_label.setStyleSheet("""
            font-family: 'Segoe UI', Arial, sans-serif; 
            font-size: 40px; 
            font-weight: bold; 
            color: white;
            background: transparent;
        """)
        value_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(value_label)
        
        title_label = QLabel(title)
        title_label.setStyleSheet("""
            font-family: 'Segoe UI', Arial, sans-serif; 
            font-size: 18px; 
            color: rgba(255, 255, 255, 0.95);
            background: transparent;
            margin-top: -5px;
        """)
        title_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(title_label)
        
        return card
    
    def darken_color(self, hex_color):
        """Darken a hex color by 15% for gradient effect"""
        # Remove # if present
        hex_color = hex_color.lstrip('#')
        # Convert to RGB
        r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
        # Darken by 15%
        r = max(0, int(r * 0.85))
        g = max(0, int(g * 0.85))
        b = max(0, int(b * 0.85))
        # Convert back to hex
        return f'#{r:02x}{g:02x}{b:02x}'
        
        # Update existing data
        self.btn_update = QPushButton("✏️ Update")
        self.btn_update.setStyleSheet("""
            background-color: #2196F3; 
            color: white; 
            padding: 10px 20px; 
            font-weight: bold; 
            font-size: 14px;
            border-radius: 5px;
            margin: 2px;
        """)
        self.btn_update.clicked.connect(self.update_data)
        self.btn_update.setToolTip("Mengupdate data yang dipilih dengan prediksi baru")
        btn_row1_layout.addWidget(self.btn_update)
        
        # Clear form
        self.btn_clear = QPushButton("🗑️ Clear Form")
        self.btn_clear.setStyleSheet("""
            background-color: #607D8B; 
            color: white; 
            padding: 10px 20px; 
            font-weight: bold; 
            font-size: 14px;
            border-radius: 5px;
            margin: 2px;
        """)
        self.btn_clear.clicked.connect(self.clear_form)
        btn_row1_layout.addWidget(self.btn_clear)
        
        btn_container.addLayout(btn_row1_layout)
        
        # Add a small separator line between button rows
        separator = QFrame()
        separator.setFrameShape(QFrame.HLine)
        separator.setFrameShadow(QFrame.Sunken)
        separator.setStyleSheet("color: #cccccc; margin: 5px 0px;")
        btn_container.addWidget(separator)
        
        # Label for secondary actions
        secondary_label = QLabel("🔧 Manajemen Tabel & Analisis:")
        secondary_label.setStyleSheet("font-weight: bold; color: #1565C0; margin: 2px 0px 2px 0px; font-size: 12px; font-family: 'Segoe UI', Arial, sans-serif;")
        btn_container.addWidget(secondary_label)
        
        # Row 2: Table Management & Analysis Actions (Secondary Actions)
        btn_row2_layout = QHBoxLayout()
        
        # Search data
        self.btn_search = QPushButton("🔍 Search")
        self.btn_search.setStyleSheet("""
            background-color: #9C27B0; 
            color: white; 
            padding: 10px 20px; 
            font-family: 'Segoe UI', Arial, sans-serif;
            font-weight: bold; 
            font-size: 14px;
            border-radius: 5px;
            margin: 2px;
        """)
        self.btn_search.clicked.connect(self.search_data)
        btn_row2_layout.addWidget(self.btn_search)
        
        # Delete selected data
        self.btn_delete_new = QPushButton("🗂️ Delete")
        self.btn_delete_new.setStyleSheet("""
            background-color: #FF9800; 
            color: white; 
            padding: 10px 20px; 
            font-family: 'Segoe UI', Arial, sans-serif;
            font-weight: bold; 
            font-size: 14px;
            border-radius: 5px;
            margin: 2px;
        """)
        self.btn_delete_new.clicked.connect(self.delete_data)
        btn_row2_layout.addWidget(self.btn_delete_new)
        
        # Refresh table
        self.btn_refresh = QPushButton("🔄 Refresh Table")
        self.btn_refresh.setStyleSheet("""
            background-color: #00BCD4; 
            color: white; 
            padding: 10px 20px; 
            font-family: 'Segoe UI', Arial, sans-serif;
            font-weight: bold; 
            font-size: 14px;
            border-radius: 5px;
            margin: 2px;
        """)
        self.btn_refresh.clicked.connect(self.refresh_table)
        btn_row2_layout.addWidget(self.btn_refresh)
        
        # Show performance report
        self.btn_performance = QPushButton("📊 Performance")
        self.btn_performance.setStyleSheet("""
            background-color: #FF5722; 
            color: white; 
            padding: 10px 20px; 
            font-family: 'Segoe UI', Arial, sans-serif;
            font-weight: bold; 
            font-size: 14px;
            border-radius: 5px;
            margin: 2px;
        """)
        self.btn_performance.clicked.connect(self.show_performance)
        btn_row2_layout.addWidget(self.btn_performance)
        
        # Export data to Excel
        self.btn_export = QPushButton("📁 Export")
        self.btn_export.setStyleSheet("""
            background-color: #4CAF50; 
            color: white; 
            padding: 10px 20px; 
            font-family: 'Segoe UI', Arial, sans-serif;
            font-weight: bold; 
            font-size: 14px;
            border-radius: 5px;
            margin: 2px;
        """)
        self.btn_export.clicked.connect(self.export_to_excel)
        btn_row2_layout.addWidget(self.btn_export)
        
        btn_container.addLayout(btn_row2_layout)
        layout.addLayout(btn_container)
        
        # Table Section
        self.table = QTableWidget()
        self.setup_table()
        layout.addWidget(self.table)
        
        # Pagination Section
        pagination_layout = QHBoxLayout()
        pagination_layout.setAlignment(Qt.AlignCenter)
        
        # Previous button
        self.btn_prev = QPushButton("◀ Previous")
        self.btn_prev.setStyleSheet("""
            QPushButton {
                background-color: #6c757d; 
                color: white; 
                padding: 8px 18px; 
                font-family: 'Segoe UI', Arial, sans-serif;
                font-weight: bold; 
                border-radius: 5px;
                margin: 2px;
            }
            QPushButton:hover {
                background-color: #5a6268;
            }
            QPushButton:disabled {
                background-color: #cccccc;
                color: #666666;
            }
        """)
        self.btn_prev.clicked.connect(self.previous_page)
        pagination_layout.addWidget(self.btn_prev)
        
        # Page info label
        self.page_info_label = QLabel("Halaman 1 dari 1")
        self.page_info_label.setStyleSheet("""
            QLabel {
                font-family: 'Segoe UI', Arial, sans-serif;
                font-weight: bold;
                padding: 8px 16px;
                color: #495057;
            }
        """)
        pagination_layout.addWidget(self.page_info_label)
        
        # Next button
        self.btn_next = QPushButton("Next ▶")
        self.btn_next.setStyleSheet("""
            QPushButton {
                background-color: #6c757d; 
                color: white; 
                padding: 8px 16px; 
                font-family: 'Segoe UI', Arial, sans-serif;
                font-weight: bold; 
                border-radius: 5px;
                margin: 2px;
            }
            QPushButton:hover {
                background-color: #5a6268;
            }
            QPushButton:disabled {
                background-color: #cccccc;
                color: #666666;
            }
        """)
        self.btn_next.clicked.connect(self.next_page)
        pagination_layout.addWidget(self.btn_next)
        
        # Items per page info
        items_info_label = QLabel(f"Menampilkan {self.items_per_page} data per halaman")
        items_info_label.setStyleSheet("""
            QLabel {
                font-style: italic;
                color: #6c757d;
                padding: 8px 16px;
            }
        """)
        pagination_layout.addWidget(items_info_label)
        
        layout.addLayout(pagination_layout)
        
        # Status bar
        self.statusBar().showMessage('Ready')
        
        central_widget.setLayout(layout)
        
        # Load trained model if exists
        try:
            self.classifier.load_model(config.MODEL_PATH)
        except:
            # Train model with dummy data if no model exists
            self.train_model_background()

    #struktur tabel data siswa    
    def setup_table(self):
        columns = ['No', 'Nama Siswa', 'Informatika', 'Fisika', 'Kimia', 'Biologi', 
                  'BIG Lanjut', 'Ekonomi', 'MAT Lanjut', 'SEJ Lanjut', 'Sosiologi', 
                  'Geografi', 'Paket Prediksi', 'ID']
        
        self.table.setColumnCount(len(columns))
        self.table.setHorizontalHeaderLabels(columns)
        
        # Make sure horizontal header is visible
        self.table.horizontalHeader().setVisible(True)
        
        # Set header properties
        header = self.table.horizontalHeader()
        header.setDefaultSectionSize(100)
        header.setMinimumSectionSize(50)
        header.setSectionResizeMode(QHeaderView.Interactive)
        header.setStretchLastSection(True)
        header.setDefaultAlignment(Qt.AlignCenter)
        
        # Set header height
        header.setMinimumHeight(55)
        header.setMaximumHeight(55)
        
        # Hide the ID column
        self.table.setColumnHidden(13, True)
        
        # Hide vertical header (row numbers) since we have our own No column
        self.table.verticalHeader().setVisible(False)
        
        # Set column widths - lebih lebar untuk menampung text header
        self.table.setColumnWidth(0, 60)   # No
        self.table.setColumnWidth(1, 250)  # Nama Siswa - diperlebar
        self.table.setColumnWidth(2, 130)  # Informatika
        self.table.setColumnWidth(3, 90)   # Fisika
        self.table.setColumnWidth(4, 90)   # Kimia
        self.table.setColumnWidth(5, 90)   # Biologi
        self.table.setColumnWidth(6, 120)  # BIG Lanjut - diperlebar lagi
        self.table.setColumnWidth(7, 100)  # Ekonomi
        self.table.setColumnWidth(8, 120)  # MAT Lanjut - diperlebar lagi
        self.table.setColumnWidth(9, 120)  # SEJ Lanjut - diperlebar lagi
        self.table.setColumnWidth(10, 100) # Sosiologi
        self.table.setColumnWidth(11, 100) # Geografi
        self.table.setColumnWidth(12, 150) # Paket Prediksi
        
        self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table.setFocusPolicy(Qt.NoFocus)
        self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.table.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        
        # Connect signals
        self.table.cellClicked.connect(self.on_table_cell_clicked)
        self.table.itemSelectionChanged.connect(self.on_table_selection_changed)
        
        # Table styling
        self.table.setShowGrid(True)
        self.table.setGridStyle(Qt.SolidLine)
        self.table.setAlternatingRowColors(True)
        
        # Keep row height compact so 10 rows can fit in the available viewport.
        row_height = 36
        self.table.verticalHeader().setDefaultSectionSize(row_height)
        
        # Set stylesheet for table with modern clean styling
        self.table.setStyleSheet("""
            QTableWidget {
                gridline-color: #e0e6ed;
                background-color: white;
                alternate-background-color: #f8fafc;
                selection-background-color: #667eea;
                selection-color: white;
                font-family: 'Segoe UI', Arial, sans-serif;
                font-size: 18px;
                border: 1px solid #e0e6ed;
                border-radius: 8px;
            }
            QTableWidget::item {
                padding: 14px 8px;
            }
            QTableWidget::item:selected {
                background-color: #667eea;
                color: white;
            }
            QTableWidget::item:hover {
                background-color: #f1f5f9;
            }
            QTableWidget::item:focus {
                outline: none;
                border: none;
            }
            QHeaderView::section {
                background-color: #f8fafc;
                border: 0px;
                border-right: 1px solid #e0e6ed;
                border-bottom: 1px solid #e0e6ed;
                padding: 14px 8px;
                font-weight: bold;
                font-size: 18px;
                color: #2c3e50;
                text-align: center;
            }
            QHeaderView::section:hover {
                background-color: #e8ecf1;
            }
        """)

        # Keep table responsive on smaller screens while preserving enough rows.
        header_height = 55
        table_min_height = header_height + (4 * row_height) + 8
        self.table.setMinimumHeight(table_min_height)
        self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

    def format_nilai(self, nilai):
        """
        Format nilai untuk tampilan di tabel.
        Menghilangkan trailing zeros, tetapi tetap mempertahankan dua digit
        jika memang ada nilai desimal.
        Contoh: 94.50 -> 94.50 ; 77.00 -> 77 ; 80.0 -> 80
        """
        try:
            # Convert to float first
            if isinstance(nilai, str):
                nilai = float(nilai.replace(',', '.'))
            else:
                nilai = float(nilai)
            
            # Format: jika bulat tampilkan tanpa desimal, jika desimal hapus trailing zero
            if nilai == int(nilai):
                return str(int(nilai))
            else:
                # Format with two decimals then strip trailing zero if present
                formatted = f"{nilai:.2f}".rstrip('0').rstrip('.')
                return formatted
        except (ValueError, TypeError):
            return str(nilai)
    
    #untuk load data ke tabel
    def load_table_data(self):
        try:
            # Disconnect signals to prevent conflicts
            self.table.itemSelectionChanged.disconnect()
            
            # Set flag to prevent selection change handling during load
            self.loading_data = True
            
            # Load all data from database
            self.all_data = ambil_semua_hasil_prediksi()
            self.total_items = len(self.all_data)
            
            # print(f"Loading {self.total_items} total rows of data")
            
            # Display current page
            self.display_current_page()
            
        except Exception as e:
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal memuat data:\n{str(e)}', 'OK')
            # Debug: print(f"Error loading table data: {str(e)}")
        finally:
            # Always reset the flag and reconnect signals
            self.loading_data = False
            # Reconnect the signal
            self.table.itemSelectionChanged.connect(self.on_table_selection_changed)

    def show_table_empty_message(self, message):
        """Show a centered message inside table when there is no data to display."""
        self.table.clearContents()
        self.table.clearSpans()
        self.table.setSelectionMode(QAbstractItemView.NoSelection)
        self.table.setRowCount(1)

        # Keep the hidden ID column (index 13) excluded from span.
        visible_columns = max(1, self.table.columnCount() - 1)
        self.table.setSpan(0, 0, 1, visible_columns)

        message_item = QTableWidgetItem(message)
        message_item.setTextAlignment(Qt.AlignCenter)
        message_item.setFlags(Qt.ItemIsEnabled)

        font = message_item.font()
        font.setPointSize(12)
        font.setItalic(True)
        message_item.setFont(font)
        message_item.setForeground(QBrush(QColor("#64748b")))

        self.table.setItem(0, 0, message_item)
        self.table.setRowHeight(0, 55)
    
    #menampilkan data sesuai halaman yang dipilih
    def display_current_page(self):
        try:
            total_pages = max(1, (self.total_items + self.items_per_page - 1) // self.items_per_page)
            if self.current_page > total_pages:
                self.current_page = total_pages

            # Calculate pagination
            start_index = (self.current_page - 1) * self.items_per_page
            end_index = start_index + self.items_per_page
            current_page_data = self.all_data[start_index:end_index]

            if not current_page_data:
                search_text = self.search_input.text().strip() if hasattr(self, 'search_input') else ""
                empty_message = "Data tidak ditemukan" if search_text else "Belum ada data siswa"
                self.show_table_empty_message(empty_message)
                self.update_pagination_controls()

                self.current_selected_id = None
                self.table.clearSelection()

                if hasattr(self, 'btn_edit'):
                    self.btn_edit.setEnabled(False)
                    self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
                if hasattr(self, 'btn_preview'):
                    self.btn_preview.setEnabled(False)
                    self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
                if hasattr(self, 'btn_update'):
                    self.btn_update.setEnabled(False)
                    self.btn_update.setToolTip("Pilih data terlebih dahulu untuk mengupdate")
                if hasattr(self, 'btn_predict'):
                    self.btn_predict.setEnabled(True)
                    self.btn_predict.setToolTip("Memprediksi dan menyimpan sebagai data baru")
                return

            self.table.setSelectionMode(QAbstractItemView.SingleSelection)
            self.table.clearContents()
            self.table.clearSpans()
            
            # Set table row count
            self.table.setRowCount(len(current_page_data))
            
            # Populate table with current page data
            for row, item in enumerate(current_page_data):
                # Calculate correct row number (continuous across pages)
                actual_row_number = start_index + row + 1
                
                # Create table items with proper alignment
                no_item = QTableWidgetItem(str(actual_row_number))
                no_item.setTextAlignment(Qt.AlignCenter)
                self.table.setItem(row, 0, no_item)
                
                nama_item = QTableWidgetItem(str(item.get('nama_siswa', '')))
                nama_item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)
                self.table.setItem(row, 1, nama_item)
                
                # Mata pelajaran scores
                for col, field in enumerate(['nilai_informatika', 'nilai_fisika', 'nilai_kimia', 'nilai_biologi', 
                                           'nilai_big_lanjut', 'nilai_ekonomi', 'nilai_mat_lanjut', 
                                           'nilai_sej_lanjut', 'nilai_sosiologi', 'nilai_geografi'], start=2):
                    # Format nilai tanpa trailing zeros
                    nilai_raw = item.get(field, 0)
                    nilai_formatted = self.format_nilai(nilai_raw)
                    score_item = QTableWidgetItem(nilai_formatted)
                    score_item.setTextAlignment(Qt.AlignCenter)
                    self.table.setItem(row, col, score_item)
                
                # Paket Prediksi
                paket_item = QTableWidgetItem(str(item.get('paket_prediksi', '')))
                paket_item.setTextAlignment(Qt.AlignCenter)
                self.table.setItem(row, 12, paket_item)
                
                # Hidden ID column
                id_item = QTableWidgetItem(str(item.get('id', '')))
                self.table.setItem(row, 13, id_item)
                
                # Set row height consistently with default section size.
                self.table.setRowHeight(row, 36)
            
            # Update pagination controls
            self.update_pagination_controls()
            
            # Clear selection
            self.current_selected_id = None
            self.table.clearSelection()
            
            # Disable edit and preview buttons when no selection
            if hasattr(self, 'btn_edit'):
                self.btn_edit.setEnabled(False)
                self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
            if hasattr(self, 'btn_preview'):
                self.btn_preview.setEnabled(False)
                self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
            
            # print(f"Displayed page {self.current_page} with {len(current_page_data)} items")
            
        except Exception as e:
            # print(f"Error displaying current page: {str(e)}")
            pass
    
    #untuk update tombol pagination dan label halaman
    def update_pagination_controls(self):
        total_pages = max(1, (self.total_items + self.items_per_page - 1) // self.items_per_page)
        
        # Update page info label
        self.page_label.setText(f"Halaman {self.current_page} dari {total_pages}")
        
        # Update button states
        self.btn_prev.setEnabled(self.current_page > 1)
        self.btn_next.setEnabled(self.current_page < total_pages)

        if self.total_items == 0:
            self.statusBar().showMessage('Tidak ada data untuk ditampilkan')
            return
        
        # Update status bar
        start_item = (self.current_page - 1) * self.items_per_page + 1
        end_item = min(self.current_page * self.items_per_page, self.total_items)
        self.statusBar().showMessage(f'Menampilkan {start_item}-{end_item} dari {self.total_items} data')
    
    #button pagination previous dan next
    def previous_page(self):
        if self.current_page > 1:
            self.current_page -= 1
            self.display_current_page()
    
    def next_page(self):
        total_pages = max(1, (self.total_items + self.items_per_page - 1) // self.items_per_page)
        if self.current_page < total_pages:
            self.current_page += 1
            self.display_current_page()
    
    def debug_table_contents(self, context=""):
        """Debug function - disabled for production"""
        # Uncomment below for debugging
        pass
        # print(f"=== DEBUG TABLE CONTENTS ({context}) ===")
        # print(f"Table row count: {self.table.rowCount()}")
        # print(f"Table column count: {self.table.columnCount()}")
        # for row in range(min(3, self.table.rowCount())):
        #     row_data = []
        #     for col in range(self.table.columnCount()):
        #         item = self.table.item(row, col)
        #         if item:
        #             row_data.append(f"Col{col}:'{item.text()}'")
        #         else:
        #             row_data.append(f"Col{col}:None")
        #     print(f"Row {row}: {', '.join(row_data)}")
        # print("=== END DEBUG ===")

    #untuk menangani perubahan seleksi pada tabel
    def on_table_selection_changed(self):
        # Prevent recursion when loading data
        if self.loading_data:
            # print("Loading data in progress, skipping selection change")
            return
        
        # Debug table contents before processing
        self.debug_table_contents("before selection change")
            
        current_row = self.table.currentRow()
        # print(f"Selection changed - Current row: {current_row}")
        # print(f"Table row count: {self.table.rowCount()}")
        
        # Check if table has valid data
        if self.table.rowCount() == 0:
            # print("Table is empty, clearing form")
            self.clear_form()
            self.current_selected_id = None
            return
        
        # Only process if a valid row is selected
        if current_row >= 0 and current_row < self.table.rowCount():
            # Set flag to prevent recursion
            self.loading_data = True
            
            try:
                # First check if the row has valid data (nama siswa di kolom 1)
                nama_item = self.table.item(current_row, 1)
                if nama_item is None or not nama_item.text().strip():
                    # print(f"Row {current_row} has no name data, keeping current form state")
                    self.loading_data = False  # Reset flag before returning
                    return
                
                # print(f"Loading data from valid row {current_row}")
                
                # Clear current selection ID first
                self.current_selected_id = None
                
                # Don't load data to form here - only when Edit button is clicked
                # self.load_selected_data_to_form(current_row)
                
                # Get the ID (sekarang di kolom 13)
                id_item = self.table.item(current_row, 13)  # Use hidden ID column
                print(f"ID item for row {current_row}: {id_item}")
                
                if id_item is not None and id_item.text().strip():
                    self.current_selected_id = id_item.text().strip()
                    print(f"Successfully selected ID: {self.current_selected_id}")
                    self.statusBar().showMessage(f'Data dipilih: {self.current_selected_id}')
                    # Enable Edit, Preview, and Update buttons when data is selected
                    if hasattr(self, 'btn_edit'):
                        self.btn_edit.setEnabled(True)
                        self.btn_edit.setToolTip("Klik untuk mengedit data yang dipilih")
                    if hasattr(self, 'btn_preview'):
                        self.btn_preview.setEnabled(True)
                        self.btn_preview.setToolTip("Klik untuk melihat preview prediksi data yang dipilih")
                    if hasattr(self, 'btn_update'):
                        self.btn_update.setEnabled(True)
                        self.btn_update.setToolTip("Klik untuk mengupdate data yang dipilih dengan prediksi baru")
                    # Keep predict button enabled when data is selected (not in edit mode yet)
                    if hasattr(self, 'btn_predict'):
                        self.btn_predict.setEnabled(True)
                        self.btn_predict.setToolTip("Memprediksi dan menyimpan sebagai data baru")
                else:
                    print(f"No valid ID found for row {current_row}")
                    self.statusBar().showMessage('Data dipilih tanpa ID')
                    # Disable Edit, Preview, and Update buttons if no valid ID
                    if hasattr(self, 'btn_edit'):
                        self.btn_edit.setEnabled(False)
                    if hasattr(self, 'btn_preview'):
                        self.btn_preview.setEnabled(False)
                    if hasattr(self, 'btn_update'):
                        self.btn_update.setEnabled(False)
                    
            except Exception as e:
                print(f"Error in selection changed: {str(e)}")
                self.statusBar().showMessage(f'Error: {str(e)}')
                self.debug_table_contents("after error")
            finally:
                # Always reset the flag
                self.loading_data = False
        else:
            # Only clear form if explicitly no selection (current_row == -1)
            if current_row == -1:
                print("No row selected, clearing form")
                self.clear_form()
                self.current_selected_id = None
                self.statusBar().showMessage('Tidak ada data yang dipilih')
                # Disable Edit, Preview, and Update buttons when no selection
                if hasattr(self, 'btn_edit'):
                    self.btn_edit.setEnabled(False)
                    self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
                if hasattr(self, 'btn_preview'):
                    self.btn_preview.setEnabled(False)
                    self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
                if hasattr(self, 'btn_update'):
                    self.btn_update.setEnabled(False)
                    self.btn_update.setToolTip("Pilih data terlebih dahulu untuk mengupdate")
                # Enable predict button when no selection (create mode)
                if hasattr(self, 'btn_predict'):
                    self.btn_predict.setEnabled(True)
                    self.btn_predict.setToolTip("Memprediksi dan menyimpan sebagai data baru")
            else:
                print(f"Invalid row {current_row}, keeping current form state")
        
        # Debug table contents after processing
        self.debug_table_contents("after selection change")
    
    #untuk load data yang dipilih ke form untuk diedit
    def load_selected_data_to_form(self, row, edit_mode=False):
        """Load selected row data to form for editing"""
        try:
            self.loading_data = True
            # Check if row is valid
            if row < 0 or row >= self.table.rowCount():
                print(f"Invalid row: {row}")
                return
                
            print(f"Loading data from row {row}, edit_mode: {edit_mode}")
            
            # Fill nama siswa (kolom index 1, karena index 0 adalah kolom No)
            nama_item = self.table.item(row, 1)
            if nama_item and nama_item.text():
                nama = nama_item.text().strip()
                self.nama_input.setText(nama)
                print(f"Nama yang dimuat: {nama}")  # Logging tambahan
            else:
                print("Nama item is None or empty")
                self.nama_input.clear()
            
            # Fill nilai mata pelajaran (mulai dari kolom index 2)
            # Fill into nilai_table semester values and auto-calculate rata-rata
            subject_keys = [
                'nilai_informatika', 'nilai_fisika', 'nilai_kimia', 'nilai_biologi',
                'nilai_big_lanjut', 'nilai_ekonomi', 'nilai_mat_lanjut', 'nilai_sej_lanjut',
                'nilai_sosiologi', 'nilai_geografi'
            ]
            
            if hasattr(self, 'nilai_table'):
                for i, key in enumerate(subject_keys):
                    col_index = i + 2  # Mulai dari kolom 2 (karena 0=No, 1=Nama)
                    value_item = self.table.item(row, col_index)
                    if value_item and value_item.text().strip():
                        try:
                            # Parse nilai
                            value = self.parse_float_value(value_item.text().strip())
                            
                            # Check if database has semester 1 and 2 values
                            # Get the data dict from current selected row in all_data
                            if hasattr(self, 'all_data') and len(self.all_data) > 0:
                                # Find the current data in all_data
                                current_data = None
                                current_page_start = (self.current_page - 1) * self.items_per_page
                                page_row_index = current_page_start + row
                                if page_row_index < len(self.all_data):
                                    current_data = self.all_data[page_row_index]
                                
                                if current_data:
                                    # Try to get semester 1 and 2 values from database
                                    sem1_key = f'{key}_sem1'
                                    sem2_key = f'{key}_sem2'
                                    sem1_value = current_data.get(sem1_key)
                                    sem2_value = current_data.get(sem2_key)
                                    
                                    sem1_item = self.nilai_table.item(i, 1)
                                    sem2_item = self.nilai_table.item(i, 2)
                                    
                                    # Fill semester 1 dan 2 secara sinkron ke item dan editor
                                    if sem1_item:
                                        if sem1_value is not None and sem1_value != '':
                                            sem1_text = self.format_nilai(sem1_value)
                                        else:
                                            sem1_text = ""
                                        self._set_nilai_cell_text(i, 1, sem1_text)
                                        print(f"Loaded {key} semester 1: {sem1_value}")
                                    
                                    if sem2_item:
                                        if sem2_value is not None and sem2_value != '':
                                            sem2_text = self.format_nilai(sem2_value)
                                        else:
                                            sem2_text = ""
                                        self._set_nilai_cell_text(i, 2, sem2_text)
                                        print(f"Loaded {key} semester 2: {sem2_value}")

                                    self._calculate_nilai_rata_row(i)
                        except Exception as err:
                            print(f"Error converting {key} value: {value_item.text()} - {err}")
                    else:
                        print(f"Empty or None value for {key}")
            
            print(f"Successfully loaded data from row {row}")
            self.statusBar().showMessage('Data berhasil dimuat ke form untuk diedit')

            if edit_mode:
                self.original_edit_data = self._get_current_form_snapshot()
            
            # Enable edit and preview buttons when data is loaded
            if hasattr(self, 'btn_edit'):
                self.btn_edit.setEnabled(True)
                self.btn_edit.setToolTip("Mengedit data yang dipilih")
            if hasattr(self, 'btn_preview'):
                self.btn_preview.setEnabled(True)
                self.btn_preview.setToolTip("Melihat preview prediksi untuk data yang dipilih")
            
            # Only disable predict button when explicitly in edit mode
            if edit_mode and hasattr(self, 'btn_predict'):
                self.btn_predict.setEnabled(False)
                self.btn_predict.setToolTip("Gunakan tombol Update untuk memperbarui data yang dipilih")
            elif hasattr(self, 'btn_predict'):
                self.btn_predict.setEnabled(True)
                self.btn_predict.setToolTip("Memprediksi dan menyimpan sebagai data baru")
            
            self.loading_data = False
        except Exception as e:
            self.loading_data = False
            print(f"Error loading data to form: {str(e)}")
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal memuat data ke form:\n{str(e)}', 'OK')
    
    def add_data(self):
        """Add new student data"""
        if not self.validate_form():
            return
        
        try:
            nama = self.nama_input.text().strip()
            
            # Extract nilai dari tabel (simpan semester 1, semester 2, dan rata-rata)
            nilai_dict = {}
            subject_keys = [
                'nilai_informatika', 'nilai_fisika', 'nilai_kimia', 'nilai_biologi',
                'nilai_big_lanjut', 'nilai_ekonomi', 'nilai_mat_lanjut', 'nilai_sej_lanjut',
                'nilai_sosiologi', 'nilai_geografi'
            ]
            
            if hasattr(self, 'nilai_table'):
                for row in range(min(len(subject_keys), self.nilai_table.rowCount())):
                    key = subject_keys[row]
                    # Get semester 1 (kolom 1)
                    sem1_item = self.nilai_table.item(row, 1)
                    sem1_value = self.parse_float_value(sem1_item.text()) if sem1_item else 0
                    
                    # Get semester 2 (kolom 2)
                    sem2_item = self.nilai_table.item(row, 2)
                    sem2_value = self.parse_float_value(sem2_item.text()) if sem2_item else 0
                    
                    # Get rata-rata (kolom 3)
                    rata_item = self.nilai_table.item(row, 3)
                    rata_value = self.parse_float_value(rata_item.text()) if rata_item else 0
                    
                    # Store all three values
                    nilai_dict[f'{key}_sem1'] = sem1_value
                    nilai_dict[f'{key}_sem2'] = sem2_value
                    nilai_dict[key] = rata_value
            
            # Predict paket (using rata-rata values)
            prediction_dict = {key: nilai_dict[key] for key in subject_keys if key in nilai_dict}
            if self.classifier.is_trained:
                prediction, _ = self.classifier.predict(prediction_dict)
            else:
                prediction = 1  # Default
            
            simpan_hasil_prediksi(nama, nilai_dict, prediction)
            self.load_table_data()
            self.clear_form()
            CustomNotificationDialog.show_success(self, 'Berhasil!', 'Data berhasil ditambahkan!', 'OK')
            self.statusBar().showMessage('Data berhasil ditambahkan')
        except Exception as e:
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal menambah data:\n{str(e)}', 'OK')
    
    #Fungsi Update Data dengan Prediksi Baru 
    def update_data(self):
        if not self.current_selected_id:
            CustomNotificationDialog.show_warning(self, 'Warning!', 'Pilih data yang akan diupdate!', 'OK')
            return
        
        if not self.validate_form():
            return

        if self.edit_mode and not self._has_form_changes():
            CustomNotificationDialog.show_warning(
                self,
                'Tidak Ada Perubahan',
                'Data belum diubah. Silakan lakukan perubahan terlebih dahulu sebelum update.',
                'OK'
            )
            return
        
        if not self.classifier.is_trained:
            CustomNotificationDialog.show_warning(self, 'Warning!', 'Model belum dilatih!\nLatih model terlebih dahulu.', 'OK')
            return
        
        try:
            # Get form data
            nama = self.nama_input.text().strip()
            
            # Extract nilai dari tabel (simpan semester 1, semester 2, dan rata-rata)
            nilai_dict = {}
            subject_keys = [
                'nilai_informatika', 'nilai_fisika', 'nilai_kimia', 'nilai_biologi',
                'nilai_big_lanjut', 'nilai_ekonomi', 'nilai_mat_lanjut', 'nilai_sej_lanjut',
                'nilai_sosiologi', 'nilai_geografi'
            ]
            
            if hasattr(self, 'nilai_table'):
                for row in range(min(len(subject_keys), self.nilai_table.rowCount())):
                    key = subject_keys[row]
                    # Get semester 1 (kolom 1)
                    sem1_item = self.nilai_table.item(row, 1)
                    sem1_value = self.parse_float_value(sem1_item.text()) if sem1_item else 0
                    
                    # Get semester 2 (kolom 2)
                    sem2_item = self.nilai_table.item(row, 2)
                    sem2_value = self.parse_float_value(sem2_item.text()) if sem2_item else 0
                    
                    # Get rata-rata (kolom 3)
                    rata_item = self.nilai_table.item(row, 3)
                    rata_value = self.parse_float_value(rata_item.text()) if rata_item else 0
                    
                    # Store all three values
                    nilai_dict[f'{key}_sem1'] = sem1_value
                    nilai_dict[f'{key}_sem2'] = sem2_value
                    nilai_dict[key] = rata_value
            
            # Make new prediction with updated values (using rata-rata)
            prediction_dict = {key: nilai_dict[key] for key in subject_keys if key in nilai_dict}
            prediction, top_3 = self.classifier.predict(prediction_dict)
            
            # Show updated prediction result
            paket_info = self.data_processor.get_paket_info(prediction)
            kelas_info = f" ({paket_info.get('kelas_tersedia', 1)} kelas tersedia)"
            
            result_text = f"Prediksi Ulang untuk {nama}:\n\n"
            result_text += f"Paket Terpilih: {prediction}\n"
            result_text += f"Detail: {paket_info.get('nama', 'Unknown')}{kelas_info}\n"
            result_text += "\nData akan diupdate dengan hasil prediksi terbaru."
            
            # Show prediction result
            reply = CustomNotificationDialog.show_question(self, "Konfirmasi Update", 
                                       result_text + "\n\nLanjutkan update?",
                                       "Ya, Update", "Batal")
            
            if reply:
                # Update data with new prediction
                update_hasil_prediksi(self.current_selected_id, nama, nilai_dict, prediction)
                
                # Refresh table and clear form
                self.load_table_data()
                self.clear_form()
                
                # Show success message
                CustomNotificationDialog.show_success(self, 'Berhasil!', 'Data berhasil diupdate dengan prediksi terbaru!', 'OK')
                self.statusBar().showMessage('Data berhasil diupdate dengan prediksi terbaru')
            
        except Exception as e:
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal mengupdate data:\n{str(e)}', 'OK')
    
    def hapus_data(self):
        """Hapus data yang dipilih dari tabel"""
        if not self.current_selected_id:
            CustomNotificationDialog.show_warning(self, 'Warning!', 'Pilih data yang akan dihapus!', 'OK')
            return

        reply = CustomNotificationDialog.show_question(self, 'Konfirmasi Hapus', 
                                   'Apakah Anda yakin ingin menghapus data ini?',
                                   'Ya, Hapus', 'Batal')
        if reply:
            try:
                hapus_hasil_prediksi(self.current_selected_id)
                self.load_table_data()
                self.clear_form()
                CustomNotificationDialog.show_success(self, 'Berhasil!', 'Data berhasil dihapus!', 'OK')
                self.statusBar().showMessage('Data berhasil dihapus')
            except Exception as e:
                CustomNotificationDialog.show_error(self, 'Error!', f'Gagal menghapus data:\n{str(e)}', 'OK')

    def search_data(self):
        """Search data by name and update table to show only matching results"""
        search_text, ok = QInputDialog.getText(self, 'Search', 'Masukkan nama siswa:')
        if ok and search_text.strip():
            try:
                # Set loading flag to prevent selection events during search
                self.loading_data = True
                
                # Get all data and filter
                all_data = ambil_semua_hasil_prediksi()
                filtered_data = [item for item in all_data if search_text.lower() in item['nama_siswa'].lower()]
                
                print(f"Search found {len(filtered_data)} results")
                
                # Update pagination data with filtered results
                self.all_data = filtered_data
                self.total_items = len(filtered_data)
                self.current_page = 1  # Reset to first page
                
                # Display current page
                self.display_current_page()
                
                if not filtered_data:
                    CustomNotificationDialog.show_warning(self, 'Tidak Ditemukan', 'Data tidak ditemukan!\nCoba dengan kata kunci lain.', 'OK')
                    # Reset to all data if no results found
                    self.load_table_data()
                else:
                    print(f"Search completed successfully - showing {len(filtered_data)} results")
                    
            except Exception as e:
                print(f"Error in search_data: {str(e)}")
                CustomNotificationDialog.show_error(self, 'Error!', f'Gagal mencari data:\n{str(e)}', 'OK')
            finally:
                # Reset loading flag
                self.loading_data = False
        elif ok and not search_text.strip():
            # If empty search, reload all data
            self.load_table_data()
    
    #pencarian data berdasarkan nama
    def search_by_name(self):
        search_text = self.search_input.text().strip()
        
        try:
            # untuk mencegah diklik berkali-kali saat loading data
            self.loading_data = True
            
            # Ambil semua data dari database
            all_data = ambil_semua_hasil_prediksi()
            
            if search_text:
                # Filter data berdasarkan nama siswa
                filtered_data = [item for item in all_data if search_text.lower() in item['nama_siswa'].lower()]
                print(f"Search by name found {len(filtered_data)} results for '{search_text}'")
            else:
                # Jika search kosong, tampilkan semua data
                filtered_data = all_data
            
            # Update data untuk pagination
            self.all_data = filtered_data
            self.total_items = len(filtered_data)
            self.current_page = 1  # Reset ke halaman pertama
            
            # Display current page
            self.display_current_page()
            self.update_pagination_controls()
            
            # Update info label
            if search_text:
                self.info_label.setText(f"Menampilkan {len(filtered_data)} hasil pencarian untuk '{search_text}'")
            else:
                self.info_label.setText(f"Menampilkan semua data ({len(filtered_data)} total)")
                
        except Exception as e:
            print(f"Error in search_by_name: {str(e)}")
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal mencari data:\n{str(e)}', 'OK')
        finally:
            # Reset loading flag
            self.loading_data = False
    
    #menghapus hasil pencarian dan menampilkan semua data kembali
    def clear_search(self):
        self.search_input.clear()
        self.load_table_data()
        self.info_label.setText("")
    
    def clear_form(self, show_empty_notification=False):
        """Clear all form inputs"""
        form_has_data = False
        if hasattr(self, 'nama_input') and self.nama_input.text().strip():
            form_has_data = True
        elif hasattr(self, 'nilai_table'):
            for row in range(self.nilai_table.rowCount()):
                sem1_item = self.nilai_table.item(row, 1)
                sem2_item = self.nilai_table.item(row, 2)
                if (sem1_item and sem1_item.text().strip()) or (sem2_item and sem2_item.text().strip()):
                    form_has_data = True
                    break

        if show_empty_notification and not form_has_data:
            CustomNotificationDialog.show_warning(self, 'Info', 'Tidak ada data dalam form.', 'OK')
            return

        # Set loading flag to prevent recursion
        self.loading_data = True
        
        # Clear table selection first to prevent on_table_selection_changed from being called
        if hasattr(self, 'table'):
            self.table.clearSelection()
        
        self.nama_input.clear()
        
        # Clear nilai table
        if hasattr(self, 'nilai_table'):
            for row in range(self.nilai_table.rowCount()):
                sem1_item = self.nilai_table.item(row, 1)
                sem2_item = self.nilai_table.item(row, 2)
                rata_item = self.nilai_table.item(row, 3)
                
                if sem1_item:
                    self._set_nilai_cell_text(row, 1, '')
                if sem2_item:
                    self._set_nilai_cell_text(row, 2, '')
                if rata_item:
                    rata_item.setText('otomatis')
                    rata_item.setForeground(QColor('#94a3b8'))
        
        self.current_selected_id = None
        self.edit_mode = False  # Reset edit mode when clearing form
        self.original_edit_data = None
        
        # Disable edit and preview buttons when form is cleared
        if hasattr(self, 'btn_edit'):
            self.btn_edit.setEnabled(False)
            self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
        if hasattr(self, 'btn_preview'):
            self.btn_preview.setEnabled(False)
            self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
        if hasattr(self, 'btn_update'):
            self.btn_update.setEnabled(False)
            self.btn_update.setToolTip("Pilih data terlebih dahulu untuk mengupdate")

        # Enable predict button when form is cleared (create mode)
        if hasattr(self, 'btn_predict'):
            self.btn_predict.setEnabled(True)
            self.btn_predict.setToolTip("Memprediksi dan menyimpan sebagai data baru")
        
        # Reset loading flag
        self.loading_data = False
    
    #mengahapus data terpilih dari database
    def delete_data(self):
        if not self.current_selected_id:
            CustomNotificationDialog.show_warning(self, 'Warning!', 'Pilih data yang akan dihapus!', 'OK')
            return
        
        reply = CustomNotificationDialog.show_question(self, 'Konfirmasi Hapus', 
                                   'Apakah Anda yakin ingin menghapus data ini?',
                                   'Ya, Hapus', 'Batal')
        if reply:
            try:
                hapus_hasil_prediksi(self.current_selected_id)
                self.load_table_data()
                self.clear_form()
                CustomNotificationDialog.show_success(self, 'Berhasil!', 'Data berhasil dihapus!', 'OK')
                self.statusBar().showMessage('Data berhasil dihapus')
            except Exception as e:
                CustomNotificationDialog.show_error(self, 'Error!', f'Gagal menghapus data:\n{str(e)}', 'OK')
    
    #fungsi edit data
    def edit_selected_data(self):
        """Edit selected data by switching to Input tab with data loaded"""
        print(f"Edit button clicked. Current selected ID: {self.current_selected_id}")
        
        if not self.current_selected_id:
            CustomNotificationDialog.show_warning(self, 'Warning!', 'Pilih data yang akan diedit!', 'OK')
            return
        
        try:
            # Set edit mode flag BEFORE switching tab
            self.edit_mode = True
            print(f"Edit mode set to: {self.edit_mode}")
            
            # Load data to form from the selected row
            selected_rows = self.table.selectedIndexes()
            print(f"Selected rows count: {len(selected_rows)}")
            
            if selected_rows:
                current_row = selected_rows[0].row()
                print(f"Loading data from row: {current_row}")
                self.load_selected_data_to_form(current_row, edit_mode=True)
            else:
                CustomNotificationDialog.show_warning(self, 'Warning!', 'Tidak ada baris yang dipilih!', 'OK')
                self.edit_mode = False
                return
            
            # Switch to Input Data tab (index 0)
            print("Switching to Input tab...")
            self.switch_tab(0)
            
            # Update status to indicate edit mode
            self.statusBar().showMessage(f'Mode Edit - Data ID {self.current_selected_id} dimuat untuk diedit')
            
            # Ensure predict button is disabled in edit mode
            if hasattr(self, 'btn_predict'):
                self.btn_predict.setEnabled(False)
                self.btn_predict.setToolTip("Gunakan tombol Update untuk memperbarui data yang dipilih")
            
            # Enable update button in edit mode
            if hasattr(self, 'btn_update'):
                self.btn_update.setEnabled(True)
                self.btn_update.setToolTip("Klik untuk update data dengan prediksi baru")
            
            print("Data loaded successfully, showing info dialog...")
            # Show info message - now using custom notification
            message = "Data telah dimuat ke form untuk diedit.\n\n"
            message += "Silakan ubah nilai yang diperlukan, kemudian:\n"
            message += "• Klik 'Update' untuk menyimpan perubahan dengan prediksi baru\n"
            message += "• Atau klik 'Clear Form' untuk kembali ke mode input baru"
            CustomNotificationDialog.show_success(self, "Mode Edit", message, "OK")
            
        except Exception as e:
            print(f"Error in edit_selected_data: {str(e)}")
            import traceback
            traceback.print_exc()
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal memuat data untuk edit:\n{str(e)}', 'OK')
            self.edit_mode = False
    
    #Fungsi Utama untuk Prediksi dan Validasi Simpan/Batal
    def predict_and_save_data(self):
        """Predict and show confirmation dialog before saving"""
        if not self.validate_form():
            return
        
        if not self.classifier.is_trained:
            CustomNotificationDialog.show_warning(self, 'Warning!', 'Model belum dilatih!\nLatih model terlebih dahulu.', 'OK')
            return
        
        try:
            # mengambil nama siswa dari form input
            nama = self.nama_input.text().strip()
            
            # Extract nilai dari tabel (simpan semester 1, semester 2, dan rata-rata)
            nilai_dict = {}
            subject_keys = [
                'nilai_informatika', 'nilai_fisika', 'nilai_kimia', 'nilai_biologi',
                'nilai_big_lanjut', 'nilai_ekonomi', 'nilai_mat_lanjut', 'nilai_sej_lanjut',
                'nilai_sosiologi', 'nilai_geografi'
            ]
            
            if hasattr(self, 'nilai_table'):
                for row in range(min(len(subject_keys), self.nilai_table.rowCount())):
                    key = subject_keys[row]
                    # menyimpan nilai semester 1 (kolom 1)
                    sem1_item = self.nilai_table.item(row, 1)
                    sem1_value = self.parse_float_value(sem1_item.text()) if sem1_item else 0
                    
                    # menyimpan nilai semester 2 (kolom 2)
                    sem2_item = self.nilai_table.item(row, 2)
                    sem2_value = self.parse_float_value(sem2_item.text()) if sem2_item else 0
                    
                    # menyimpan rata-rata (kolom 3)
                    rata_item = self.nilai_table.item(row, 3)
                    rata_value = self.parse_float_value(rata_item.text()) if rata_item else 0
                    
                    # untuk prediksi, kita akan menggunakan nilai rata-rata (kolom 3), tapi kita juga menyimpan semester 1 dan 2 untuk referensi di database
                    nilai_dict[f'{key}_sem1'] = sem1_value
                    nilai_dict[f'{key}_sem2'] = sem2_value
                    nilai_dict[key] = rata_value
            
            # membuat prediksi dengan nilai rata-rata (kolom 3) untuk setiap mata pelajaran
            prediction_dict = {key: nilai_dict[key] for key in subject_keys if key in nilai_dict}
            prediction, top_3 = self.classifier.predict(prediction_dict) #menghasilkan jurusan
            
            # melihat hasil prediksi dengan informasi paket dan kelas yang tersedia
            paket_info = self.data_processor.get_paket_info(prediction)
            
            # untuk menampilkan hasil prediksi dengan opsi simpan atau batal, gunakan custom notification dialog
            from gui.custom_notifications import CustomPredictionDialog
            save_result = CustomPredictionDialog.show_prediction(self, nama, prediction, paket_info)
            
            # Hasil dari dialog akan menentukan apakah kita menyimpan data baru dengan prediksi atau membatalkan dan tetap di form input
            if save_result:
                # untuk menyimpan hasil prediksi sebagai data baru, kita akan menggunakan fungsi simpan_hasil_prediksi dari database handler
                simpan_hasil_prediksi(nama, nilai_dict, prediction)
                success_msg = "Data berhasil diprediksi dan disimpan sebagai data baru!"
                status_msg = 'Data berhasil disimpan sebagai data baru'
                
                # Refresh table and clear form
                self.load_table_data()
                self.clear_form()
                
                # Show success message
                CustomNotificationDialog.show_success(self, 'Berhasil!', success_msg, 'OK')
                self.statusBar().showMessage(status_msg)
            else:
                # Cancel - do nothing, stay on input form
                self.statusBar().showMessage('Prediksi dibatalkan')
            
        except Exception as e:
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal melakukan prediksi:\n{str(e)}', 'OK')
    
    #Fungsi Preview Prediksi
    def preview_prediction(self):
        """Preview prediction with option to save - works from both form input and selected table data"""
        if not self.classifier.is_trained:
            CustomNotificationDialog.show_warning(self, 'Warning!', 'Model belum dilatih!\nLatih model terlebih dahulu.', 'OK')
            return
        
        try:
            # Check if we're working with selected data from table or form input
            if hasattr(self, 'current_selected_id') and self.current_selected_id:
                # Working with selected data from table
                current_row = self.table.currentRow()
                if current_row < 0:
                    CustomNotificationDialog.show_warning(self, 'Warning!', 'Pilih data dari tabel terlebih dahulu!', 'OK')
                    return
                
                # Get data from table
                nama = self.table.item(current_row, 1).text()  # Nama siswa di kolom 1
                
                # Get values from table (columns 2-11 untuk mata pelajaran)
                # Use parse_float_value to support comma and dot format
                nilai_dict = {
                    'nilai_informatika': self.parse_float_value(self.table.item(current_row, 2).text()),
                    'nilai_fisika': self.parse_float_value(self.table.item(current_row, 3).text()),
                    'nilai_kimia': self.parse_float_value(self.table.item(current_row, 4).text()),
                    'nilai_biologi': self.parse_float_value(self.table.item(current_row, 5).text()),
                    'nilai_big_lanjut': self.parse_float_value(self.table.item(current_row, 6).text()),
                    'nilai_ekonomi': self.parse_float_value(self.table.item(current_row, 7).text()),
                    'nilai_mat_lanjut': self.parse_float_value(self.table.item(current_row, 8).text()),
                    'nilai_sej_lanjut': self.parse_float_value(self.table.item(current_row, 9).text()),
                    'nilai_sosiologi': self.parse_float_value(self.table.item(current_row, 10).text()),
                    'nilai_geografi': self.parse_float_value(self.table.item(current_row, 11).text())
                }
                
                source_text = "dari data tabel yang dipilih"
            else:
                # Working with form input
                if not self.validate_form():
                    return
                
                # Get form data
                nama = self.nama_input.text().strip()
                
                # Extract nilai dari tabel (simpan semester 1, semester 2, dan rata-rata)
                nilai_dict = {}
                subject_keys = [
                    'nilai_informatika', 'nilai_fisika', 'nilai_kimia', 'nilai_biologi',
                    'nilai_big_lanjut', 'nilai_ekonomi', 'nilai_mat_lanjut', 'nilai_sej_lanjut',
                    'nilai_sosiologi', 'nilai_geografi'
                ]
                
                if hasattr(self, 'nilai_table'):
                    for row in range(min(len(subject_keys), self.nilai_table.rowCount())):
                        key = subject_keys[row]
                        # Get rata-rata (kolom 3) for prediction
                        rata_item = self.nilai_table.item(row, 3)
                        if rata_item:
                            nilai_dict[key] = self.parse_float_value(rata_item.text())
                
                source_text = "dari form input"
            
            # Make prediction
            prediction, top_3 = self.classifier.predict(nilai_dict)
            
            # Show prediction result
            paket_info = self.data_processor.get_paket_info(prediction)
            
            result_text = f"Preview Prediksi untuk {nama} ({source_text}):\n\n"
            result_text += f"Paket Terpilih: {prediction}\n"
            result_text += f"Detail: {paket_info.get('nama', 'Unknown')}"
            
            # Show preview with custom notification
            CustomNotificationDialog.show_success(self, "Preview Prediksi", result_text, "OK")
            
            # Just preview, don't save
            self.statusBar().showMessage('Preview selesai - data tidak disimpan')
            
        except Exception as e:
            import traceback
            traceback.print_exc()
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal melakukan preview prediksi:\n{str(e)}', 'OK')
    
    def show_performance(self):
        """Show performance report preview"""
        if not self.classifier.is_trained:
            # Train model first
            self.train_model_background()
        
        # Show performance report dialog
        dialog = PerformanceDialog(self.classifier, self)
        dialog.exec_()
    
    #Fungsi Validasi Form
    def validate_form(self):
        """Validate form inputs with enhanced validation"""
        nama = self.nama_input.text().strip()
        
        # Validasi nama siswa tidak boleh kosong
        if not nama:
            CustomNotificationDialog.show_warning(self, 'Validation', 'Nama siswa harus diisi!', 'OK')
            self.nama_input.setFocus()
            return False
        
        # Validasi nama siswa hanya boleh huruf, spasi, titik, tanda petik, dan tanda hubung
        import re
        if not re.match(r"^[a-zA-ZÀ-ÿ][a-zA-ZÀ-ÿ\s\.\-']*$", nama):
            CustomNotificationDialog.show_warning(self, 'Validation', 'Nama siswa hanya boleh berisi huruf, spasi, titik, tanda petik, dan tanda hubung!', 'OK')
            self.nama_input.setFocus()
            return False
        
        # Validasi panjang nama minimal 2 karakter dan maksimal 50
        if len(nama) < 2:
            CustomNotificationDialog.show_warning(self, 'Validation', 'Nama siswa minimal 2 karakter!', 'OK')
            self.nama_input.setFocus()
            return False
        
        if len(nama) > 50:
            CustomNotificationDialog.show_warning(self, 'Validation', 'Nama siswa maksimal 50 karakter!', 'OK')
            self.nama_input.setFocus()
            return False
        
        # Validasi nama tidak boleh hanya spasi
        if nama.isspace():
            CustomNotificationDialog.show_warning(self, 'Validation', 'Nama siswa tidak boleh hanya berisi spasi!', 'OK')
            self.nama_input.setFocus()
            return False
        
        # Validasi tidak boleh ada spasi berturut-turut
        if '  ' in nama:
            CustomNotificationDialog.show_warning(self, 'Validation', 'Nama siswa tidak boleh mengandung spasi berturut-turut!', 'OK')
            self.nama_input.setFocus()
            return False
        
        # Validasi WAJIB: Semua nilai mata pelajaran harus diisi (kedua semester atau salah satu)
        empty_subjects = []
        subject_names = [
            'Informatika', 'Fisika', 'Kimia', 'Biologi', 'BIG Lanjut',
            'Ekonomi', 'MAT Lanjut', 'SEJ Lanjut', 'Sosiologi', 'Geografi'
        ]
        
        if hasattr(self, 'nilai_table'):
            for row in range(self.nilai_table.rowCount()):
                sem1_item = self.nilai_table.item(row, 1)
                sem2_item = self.nilai_table.item(row, 2)
                mapel = subject_names[row] if row < len(subject_names) else f"Subject {row}"
                
                sem1_text = sem1_item.text().strip() if sem1_item else ""
                sem2_text = sem2_item.text().strip() if sem2_item else ""
                
                # Cek input kosong (kedua semester belum diisi)
                if sem1_text == "" and sem2_text == "":
                    empty_subjects.append(mapel)
        
        # Jika ada mata pelajaran yang belum diisi
        if empty_subjects:
            if len(empty_subjects) == 1:
                CustomNotificationDialog.show_warning(self, 'Validasi Input', 
                    f"Nilai {empty_subjects[0]} harus diisi minimal satu semester!\n\n"
                    f"Isi minimal Semester 1 atau Semester 2 untuk setiap mata pelajaran.",
                    'OK')
            else:
                subjects_text = ", ".join(empty_subjects[:-1]) + f" dan {empty_subjects[-1]}"
                CustomNotificationDialog.show_warning(self, 'Validasi Input', 
                    f"Nilai {subjects_text} harus diisi!\n\n"
                    f"Isi minimal Semester 1 atau Semester 2 untuk setiap mata pelajaran.",
                    'OK')
            return False
        
        # Validasi nilai mata pelajaran dengan range yang tepat
        if hasattr(self, 'nilai_table'):
            for row in range(self.nilai_table.rowCount()):
                sem1_item = self.nilai_table.item(row, 1)
                sem2_item = self.nilai_table.item(row, 2)
                mapel = subject_names[row] if row < len(subject_names) else f"Subject {row}"

                sem1_text = sem1_item.text().strip() if sem1_item else ""
                sem2_text = sem2_item.text().strip() if sem2_item else ""

                if not self._is_valid_score_text(sem1_text):
                    CustomNotificationDialog.show_warning(self, 'Validation', f'Nilai {mapel} Semester 1 harus berupa angka 0-100!', 'OK')
                    return False
                if not self._is_valid_score_text(sem2_text):
                    CustomNotificationDialog.show_warning(self, 'Validation', f'Nilai {mapel} Semester 2 harus berupa angka 0-100!', 'OK')
                    return False
                
                sem1_value = self.parse_float_value(sem1_text)
                sem2_value = self.parse_float_value(sem2_text)
                
                # Validasi nilai dalam range 0-100 (jika diisi)
                if sem1_text and (sem1_value < 0 or sem1_value > 100):
                    CustomNotificationDialog.show_warning(self, 'Validation', f'Nilai {mapel} Semester 1 harus antara 0-100!', 'OK')
                    return False
                if sem2_text and (sem2_value < 0 or sem2_value > 100):
                    CustomNotificationDialog.show_warning(self, 'Validation', f'Nilai {mapel} Semester 2 harus antara 0-100!', 'OK')
                    return False
        
        # Validasi rata-rata nilai minimal
        total_nilai = 0
        count_nilai = 0
        if hasattr(self, 'nilai_table'):
            for row in range(self.nilai_table.rowCount()):
                rata_item = self.nilai_table.item(row, 3)
                if rata_item and rata_item.text() != 'otomatis':
                    try:
                        nilai = float(rata_item.text())
                        total_nilai += nilai
                        count_nilai += 1
                    except ValueError:
                        pass
        
        if count_nilai > 0:
            rata_rata = total_nilai / count_nilai
        else:
            rata_rata = 0
        
        if rata_rata < 50:
            reply = CustomNotificationDialog.show_question(self, 'Konfirmasi', 
                f"Rata-rata nilai ({rata_rata:.1f}) sangat rendah.\nApakah data sudah benar?",
                'Ya, Benar', 'Cek Lagi')
            if not reply:
                return False
        
        return True
    
    def validate_nama_input(self, text):
        """Validate nama input in real-time"""
        # Filter out invalid characters but keep common punctuation found in dataset names
        import re
        filtered_text = re.sub(r"[^a-zA-ZÀ-ÿ\s\.\-']", '', text)
        
        # Remove multiple consecutive spaces
        filtered_text = re.sub(r' +', ' ', filtered_text)
        
        # If the text was changed, update the input
        if filtered_text != text:
            cursor_pos = self.nama_input.cursorPosition()
            self.nama_input.setText(filtered_text)
            # Restore cursor position (adjusted for removed characters)
            new_pos = min(cursor_pos, len(filtered_text))
            self.nama_input.setCursorPosition(new_pos)
    
    def train_model_background(self):
        """Train model in background"""
        try:
            # Always train with current configured training dataset
            data_path = config.DATASET_PATH
            if os.path.exists(data_path):
                os.makedirs(os.path.dirname(config.MODEL_PATH), exist_ok=True)
                print(f"Training model with {data_path}")
                accuracy, _ = self.classifier.train(
                    data_path,
                    test_data_path=getattr(config, 'TEST_DATASET_PATH', None)
                )
                self.classifier.save_model(config.MODEL_PATH)
                print(f"Model trained with accuracy: {accuracy:.2%}")
                self.statusBar().showMessage(f'Model trained - Accuracy: {accuracy:.2%}')
            else:
                print(f"Dataset not found: {data_path}")
                self.statusBar().showMessage(f'Dataset not found: {data_path}')
        except Exception as e:
            print(f"Training failed: {str(e)}")
            self.statusBar().showMessage(f'Training failed: {str(e)}')

    def refresh_table(self):
        """Refresh table to show all data"""
        self.current_page = 1  # Reset to first page
        self.load_table_data()
        self.statusBar().showMessage('Tabel berhasil dimuat ulang')
    
    def eventFilter(self, source, event):
        """Event filter to detect mouse clicks outside table"""
        if hasattr(self, 'nama_input') and source == self.nama_input and event.type() == QEvent.MouseButtonPress:
            if event.button() == Qt.LeftButton:
                self.open_student_name_picker()
                return True

        if event.type() == QEvent.MouseButtonPress:
            # Check if click happened outside the table
            if hasattr(self, 'table') and source == self.table.viewport():
                # Get the position of the click relative to the table viewport
                pos = event.pos()
                item = self.table.itemAt(pos)
                
                # If click is in empty area of table (no item), clear selection
                if item is None:
                    self.table.clearSelection()
                    self.current_selected_id = None
                    self.load_table_data()
                    
                    # Disable edit and preview buttons
                    if hasattr(self, 'btn_edit'):
                        self.btn_edit.setEnabled(False)
                        self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
                    if hasattr(self, 'btn_preview'):
                        self.btn_preview.setEnabled(False)
                        self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
                    
                    self.statusBar().showMessage('Seleksi dibersihkan dan tabel direfresh')
            
            elif hasattr(self, 'table') and source != self.table:
                # Check if the click is outside the table area entirely
                if hasattr(source, 'geometry'):
                    # Get global position of click
                    global_pos = event.globalPos()
                    # Get table geometry in global coordinates
                    table_rect = self.table.geometry()
                    table_global_pos = self.table.mapToGlobal(table_rect.topLeft())
                    table_global_rect = QRect(table_global_pos, table_rect.size())
                    
                    # If click is outside table, clear selection
                    if not table_global_rect.contains(global_pos):
                        self.table.clearSelection()
                        self.current_selected_id = None
                        self.load_table_data()
                        
                        # Disable edit and preview buttons
                        if hasattr(self, 'btn_edit'):
                            self.btn_edit.setEnabled(False)
                            self.btn_edit.setToolTip("Pilih data terlebih dahulu untuk mengedit")
                        if hasattr(self, 'btn_preview'):
                            self.btn_preview.setEnabled(False)
                            self.btn_preview.setToolTip("Pilih data terlebih dahulu untuk melihat preview prediksi")
                        
                        self.statusBar().showMessage('Seleksi dibersihkan dan tabel direfresh')
        
        return super().eventFilter(source, event)
    
    def on_table_cell_clicked(self, row, column):
        """Handle cell click event by selecting the row."""
        print(f"Cell clicked - Row: {row}, Column: {column}")
        
        # Ensure the row is valid and the table has data
        if row >= 0 and row < self.table.rowCount() and self.table.rowCount() > 0:
            print(f"Selecting row {row}")
            # Select the entire row - this will trigger itemSelectionChanged
            self.table.selectRow(row)
            # Force the selection to be processed immediately
            QApplication.processEvents()
        else:
            print(f"Invalid row clicked: {row} or table is empty")
    
    def export_to_excel(self):
        """Export data hasil prediksi ke file Excel dengan rata-rata nilai dan keterangan paket"""
        try:
            # Ambil semua data dari database
            data = ambil_semua_hasil_prediksi()
            
            if not data:
                CustomNotificationDialog.show_warning(self, 'Info', 'Tidak ada data untuk diekspor.\nTambahkan data terlebih dahulu.', 'OK')
                return
            
            # Konversi ke DataFrame
            df = pd.DataFrame(data)
            
            # Tentukan kolom yang akan diekspor (hanya rata-rata, tidak semester values)
            columns_to_keep = [
                'nama_siswa',
                'nilai_informatika',
                'nilai_fisika',
                'nilai_kimia',
                'nilai_biologi',
                'nilai_big_lanjut',
                'nilai_ekonomi',
                'nilai_mat_lanjut',
                'nilai_sej_lanjut',
                'nilai_sosiologi',
                'nilai_geografi',
                'paket_prediksi'
            ]
            
            # Hapus kolom yang tidak diperlukan
            df = df[[col for col in columns_to_keep if col in df.columns]]
            
            # Tambahkan kolom No sebagai nomor urut di awal
            df.insert(0, 'No', range(1, len(df) + 1))
            
            # Buat nama kolom yang lebih user-friendly
            column_mapping = {
                'No': 'No',
                'nama_siswa': 'Nama Siswa',
                'nilai_informatika': 'Informatika',
                'nilai_fisika': 'Fisika',
                'nilai_kimia': 'Kimia',
                'nilai_biologi': 'Biologi',
                'nilai_big_lanjut': 'BIG Lanjut',
                'nilai_ekonomi': 'Ekonomi',
                'nilai_mat_lanjut': 'MAT Lanjut',
                'nilai_sej_lanjut': 'SEJ Lanjut',
                'nilai_sosiologi': 'Sosiologi',
                'nilai_geografi': 'Geografi',
                'paket_prediksi': 'Paket Prediksi'
            }
            
            # Rename kolom
            df = df.rename(columns=column_mapping)
            
            # Tambahkan kolom keterangan paket
            paket_info = {
                1: '1 (Informatika,Fisika,Kimia,Biologi)',
                2: '2 (Informatika,BIG Lanjut,Kimia,Biologi)',
                3: '3 (Informatika,Ekonomi,BIG Lanjut,MAT Lanjut)',
                4: '4 (Informatika,Ekonomi,MAT Lanjut,SEJ Lanjut)',
                5: '5 (Informatika,Ekonomi,Sosiologi,SEJ Lanjut)',
                6: '6 (Informatika,Ekonomi,Sosiologi,Geografi)'
            }
            
            df['Keterangan Paket'] = df['Paket Prediksi'].map(paket_info)
            
            # Hapus kolom Paket Prediksi karena sudah ada Keterangan Paket
            if 'Paket Prediksi' in df.columns:
                df = df.drop(columns=['Paket Prediksi'])

            # Tampilkan nilai numerik tanpa suffix .00, tetapi tetap simpan 2 desimal
            # bila nilai memang memiliki pecahan.
            score_columns = [
                'Informatika', 'Fisika', 'Kimia', 'Biologi', 'BIG Lanjut',
                'Ekonomi', 'MAT Lanjut', 'SEJ Lanjut', 'Sosiologi', 'Geografi'
            ]
            for column_name in score_columns:
                if column_name in df.columns:
                    df[column_name] = df[column_name].apply(self.format_nilai)
            
            # Dialog untuk memilih lokasi penyimpanan
            from datetime import datetime
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            file_path, _ = QFileDialog.getSaveFileName(
                self,
                "Simpan File Excel",
                f"Hasil_Prediksi_Jurusan_{timestamp}.xlsx",
                "Excel Files (*.xlsx);;All Files (*)"
            )
            
            if file_path:
                # Pastikan file path berakhiran .xlsx
                if not file_path.endswith('.xlsx'):
                    file_path += '.xlsx'
                
                # Export ke Excel dengan formatting
                with pd.ExcelWriter(file_path, engine='openpyxl') as writer:
                    df.to_excel(writer, sheet_name='Hasil Prediksi', index=False)
                    
                    # Get worksheet untuk formatting
                    worksheet = writer.sheets['Hasil Prediksi']
                    
                    # Format header dengan openpyxl
                    from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
                    
                    # Define styles dengan border hitam yang tebal
                    header_font = Font(bold=True, size=11)
                    header_fill = PatternFill(start_color="D7E4BC", end_color="D7E4BC", fill_type="solid")
                    # Gunakan medium border untuk semua garis tabel
                    black_border = Border(
                        left=Side(style='medium', color='000000'),
                        right=Side(style='medium', color='000000'),
                        top=Side(style='medium', color='000000'),
                        bottom=Side(style='medium', color='000000')
                    )
                    center_alignment = Alignment(horizontal='center', vertical='center')
                    left_alignment = Alignment(horizontal='left', vertical='center')
                    
                    # Apply header formatting - baris pertama
                    for col in worksheet[1]:
                        col.font = header_font
                        col.fill = header_fill
                        col.border = black_border
                        col.alignment = center_alignment
                    
                    # Kolom yang perlu center alignment di data
                    center_columns = ['No', 'Informatika', 'Fisika', 'Kimia', 'Biologi', 'BIG Lanjut', 
                                    'Ekonomi', 'MAT Lanjut', 'SEJ Lanjut', 'Sosiologi', 'Geografi']
                    
                    # Get column indices untuk alignment
                    header_row = worksheet[1]
                    column_indices = {}
                    for idx, cell in enumerate(header_row, 1):
                        if cell.value in center_columns:
                            column_indices[cell.value] = idx
                    
                    # Apply formatting ke semua data cells
                    for row_num in range(2, worksheet.max_row + 1):
                        for col_num in range(1, worksheet.max_column + 1):
                            cell = worksheet.cell(row=row_num, column=col_num)
                            # Apply black border ke semua cell
                            cell.border = black_border
                            
                            # Get header value untuk cell ini
                            header_cell = worksheet.cell(row=1, column=col_num)
                            if header_cell.value in center_columns:
                                cell.alignment = center_alignment
                            else:
                                cell.alignment = left_alignment
                    
                    # Auto-adjust column width
                    for column in worksheet.columns:
                        max_length = 0
                        column_letter = column[0].column_letter
                        
                        for cell in column:
                            try:
                                if len(str(cell.value)) > max_length:
                                    max_length = len(str(cell.value))
                            except:
                                pass
                        
                        adjusted_width = min(max_length + 2, 50)
                        worksheet.column_dimensions[column_letter].width = adjusted_width
                
                CustomNotificationDialog.show_success(
                    self, 
                    'Sukses!', 
                    f"Data berhasil diekspor ke:\n{file_path}\n\nTotal {len(df)} data diekspor.",
                    'OK'
                )
                self.statusBar().showMessage(f'Data berhasil diekspor ke Excel: {len(df)} records')
                
        except Exception as e:
            CustomNotificationDialog.show_error(self, 'Error!', f'Gagal mengekspor data:\n{str(e)}', 'OK')
            self.statusBar().showMessage(f'Export gagal: {str(e)}')

class PerformanceDialog(QDialog):
    def __init__(self, classifier, parent=None):
        super().__init__(parent)
        self.classifier = classifier
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle('Model Performance Report')
        self.setGeometry(100, 100, 1200, 900)  # Increased height for vertical layout
        
        # Main vertical layout
        main_layout = QVBoxLayout()
        main_layout.setSpacing(15)
        main_layout.setContentsMargins(20, 20, 20, 20)
        
        # Title
        title_label = QLabel("📊 Model Performance Report")
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet("""
            font-weight: bold; 
            font-size: 18px; 
            margin-bottom: 15px;
            color: #2c3e50;
            background-color: #ecf0f1;
            padding: 10px;
            border-radius: 5px;
        """)
        main_layout.addWidget(title_label)
        
        # Content layout (vertical with 3 sections)
        content_layout = QVBoxLayout()
        content_layout.setSpacing(20)
        
        # Section 1: Confusion Matrix + Explanation
        confusion_section = QHBoxLayout()
        confusion_section.setSpacing(15)
        
        # Left - Confusion Matrix
        confusion_left = QWidget()
        confusion_left.setFixedWidth(500)
        confusion_left_layout = QVBoxLayout(confusion_left)
        confusion_left_layout.setContentsMargins(10, 10, 10, 10)
        
        confusion_label = QLabel("🔄 Confusion Matrix")
        confusion_label.setAlignment(Qt.AlignCenter)
        confusion_label.setStyleSheet("""
            font-weight: bold; 
            font-size: 14px; 
            margin-bottom: 10px;
            color: #34495e;
            background-color: #e8f4fd;
            padding: 8px;
            border-radius: 3px;
        """)
        confusion_left_layout.addWidget(confusion_label)
        
        # Create matplotlib figure for confusion matrix
        self.figure = Figure(figsize=(5, 4), dpi=80)
        self.figure.patch.set_facecolor('white')
        self.canvas = FigureCanvas(self.figure)
        self.canvas.setMinimumHeight(320)
        confusion_left_layout.addWidget(self.canvas)
        
        # Right - Confusion Matrix Explanation
        confusion_right = QWidget()
        confusion_right_layout = QVBoxLayout(confusion_right)
        confusion_right_layout.setContentsMargins(10, 10, 10, 10)
        
        confusion_exp_label = QLabel("📖 Penjelasan Confusion Matrix")
        confusion_exp_label.setAlignment(Qt.AlignCenter)
        confusion_exp_label.setStyleSheet("""
            font-weight: bold; 
            font-size: 14px; 
            margin-bottom: 10px;
            color: #34495e;
            background-color: #f0f8ff;
            padding: 8px;
            border-radius: 3px;
        """)
        confusion_right_layout.addWidget(confusion_exp_label)
        
        self.confusion_explanation = QTextEdit()
        self.confusion_explanation.setReadOnly(True)
        self.confusion_explanation.setFont(QFont("Segoe UI", 10))
        self.confusion_explanation.setMaximumHeight(350)
        self.confusion_explanation.setStyleSheet("""
            QTextEdit {
                background-color: #f8f9fa;
                border: 2px solid #dee2e6;
                border-radius: 5px;
                padding: 12px;
                font-family: 'Segoe UI', Arial, sans-serif;
                line-height: 1.4;
            }
        """)
        confusion_right_layout.addWidget(self.confusion_explanation)
        
        confusion_section.addWidget(confusion_left)
        confusion_section.addWidget(confusion_right)
        content_layout.addLayout(confusion_section)
        
        # Section 2: ROC Curves + Explanation
        roc_section = QHBoxLayout()
        roc_section.setSpacing(15)
        
        # Left - ROC Curves
        roc_left = QWidget()
        roc_left.setFixedWidth(500)
        roc_left_layout = QVBoxLayout(roc_left)
        roc_left_layout.setContentsMargins(10, 10, 10, 10)
        
        roc_label = QLabel("📈 ROC AUC Curves")
        roc_label.setAlignment(Qt.AlignCenter)
        roc_label.setStyleSheet("""
            font-weight: bold; 
            font-size: 14px; 
            margin-bottom: 10px;
            color: #34495e;
            background-color: #fff3e0;
            padding: 8px;
            border-radius: 3px;
        """)
        roc_left_layout.addWidget(roc_label)
        
        # Create matplotlib figure for ROC curve
        self.roc_figure = Figure(figsize=(5, 4), dpi=80)
        self.roc_figure.patch.set_facecolor('white')
        self.roc_canvas = FigureCanvas(self.roc_figure)
        self.roc_canvas.setMinimumHeight(320)
        roc_left_layout.addWidget(self.roc_canvas)
        
        # Right - ROC Explanation
        roc_right = QWidget()
        roc_right_layout = QVBoxLayout(roc_right)
        roc_right_layout.setContentsMargins(10, 10, 10, 10)
        
        roc_exp_label = QLabel("📖 Penjelasan ROC AUC")
        roc_exp_label.setAlignment(Qt.AlignCenter)
        roc_exp_label.setStyleSheet("""
            font-weight: bold; 
            font-size: 14px; 
            margin-bottom: 10px;
            color: #34495e;
            background-color: #fffaf0;
            padding: 8px;
            border-radius: 3px;
        """)
        roc_right_layout.addWidget(roc_exp_label)
        
        self.roc_explanation = QTextEdit()
        self.roc_explanation.setReadOnly(True)
        self.roc_explanation.setFont(QFont("Segoe UI", 10))
        self.roc_explanation.setMaximumHeight(350)
        self.roc_explanation.setStyleSheet("""
            QTextEdit {
                background-color: #f8f9fa;
                border: 2px solid #dee2e6;
                border-radius: 5px;
                padding: 12px;
                font-family: 'Segoe UI', Arial, sans-serif;
                line-height: 1.4;
            }
        """)
        roc_right_layout.addWidget(self.roc_explanation)
        
        roc_section.addWidget(roc_left)
        roc_section.addWidget(roc_right)
        content_layout.addLayout(roc_section)
        
        # Section 3: Classification Report + Explanation
        class_section = QHBoxLayout()
        class_section.setSpacing(15)
        
        # Left - Classification Report
        class_left = QWidget()
        class_left.setFixedWidth(500)
        class_left_layout = QVBoxLayout(class_left)
        class_left_layout.setContentsMargins(10, 10, 10, 10)
        
        class_label = QLabel("📋 Classification Report")
        class_label.setAlignment(Qt.AlignCenter)
        class_label.setStyleSheet("""
            font-weight: bold; 
            font-size: 14px; 
            margin-bottom: 10px;
            color: #34495e;
            background-color: #e8f6f3;
            padding: 8px;
            border-radius: 3px;
        """)
        class_left_layout.addWidget(class_label)
        
        # Text area for classification report
        self.report_text = QTextEdit()
        self.report_text.setReadOnly(True)
        self.report_text.setFont(QFont("Consolas", 10))
        self.report_text.setMaximumHeight(300)
        self.report_text.setStyleSheet("""
            QTextEdit {
                background-color: #f8f9fa;
                border: 2px solid #dee2e6;
                border-radius: 5px;
                padding: 12px;
                font-family: 'Consolas', 'Monaco', monospace;
                line-height: 1.3;
            }
        """)
        class_left_layout.addWidget(self.report_text)
        
        # Right - Classification Report Explanation
        class_right = QWidget()
        class_right_layout = QVBoxLayout(class_right)
        class_right_layout.setContentsMargins(10, 10, 10, 10)
        
        class_exp_label = QLabel("📖 Penjelasan Classification Report")
        class_exp_label.setAlignment(Qt.AlignCenter)
        class_exp_label.setStyleSheet("""
            font-weight: bold; 
            font-size: 14px; 
            margin-bottom: 10px;
            color: #34495e;
            background-color: #f0fff0;
            padding: 8px;
            border-radius: 3px;
        """)
        class_right_layout.addWidget(class_exp_label)
        
        self.class_explanation = QTextEdit()
        self.class_explanation.setReadOnly(True)
        self.class_explanation.setFont(QFont("Segoe UI", 10))
        self.class_explanation.setMaximumHeight(300)
        self.class_explanation.setStyleSheet("""
            QTextEdit {
                background-color: #f8f9fa;
                border: 2px solid #dee2e6;
                border-radius: 5px;
                padding: 12px;
                font-family: 'Segoe UI', Arial, sans-serif;
                line-height: 1.4;
            }
        """)
        class_right_layout.addWidget(self.class_explanation)
        
        class_section.addWidget(class_left)
        class_section.addWidget(class_right)
        content_layout.addLayout(class_section)
        
        # Add content to main layout
        main_layout.addLayout(content_layout)
        
        # Close button at bottom
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        
        btn_close = QPushButton("✖️ Close")
        btn_close.setFixedSize(120, 40)
        btn_close.clicked.connect(self.close)
        btn_close.setStyleSheet("""
            QPushButton {
                background-color: #e74c3c; 
                color: white; 
                padding: 10px 20px; 
                font-weight: bold; 
                border-radius: 8px;
                font-size: 12px;
                border: none;
            }
            QPushButton:hover {
                background-color: #c0392b;
            }
            QPushButton:pressed {
                background-color: #a93226;
            }
        """)
        btn_layout.addWidget(btn_close)
        btn_layout.addStretch()
        
        main_layout.addLayout(btn_layout)
        
        self.setLayout(main_layout)
        
        # Generate performance report
        self.generate_performance_report()
    
    def generate_performance_report(self):
        """Generate confusion matrix and classification report"""
        try:
            from sklearn.metrics import confusion_matrix, classification_report
            import numpy as np
            
            # Get test data predictions

            data_path = config.DATASET_PATH
            if os.path.exists(data_path):
                test_path = getattr(config, 'TEST_DATASET_PATH', None)
                if test_path and os.path.exists(test_path):
                    X_test, y_test = self.classifier.prepare_data(test_path)
                else:
                    X, y = self.classifier.prepare_data(data_path)
                    from sklearn.model_selection import train_test_split
                    _, X_test, _, y_test = train_test_split(
                        X, y, test_size=config.TEST_SIZE, random_state=config.RANDOM_STATE, stratify=y
                    )
                
                # Scale test data
                X_test_scaled = self.classifier.scaler.transform(X_test)
                y_pred = self.classifier.knn.predict(X_test_scaled)
                y_pred_proba = self.classifier.knn.predict_proba(X_test_scaled)
                
                # Generate confusion matrix
                cm = confusion_matrix(y_test, y_pred)
                self.plot_confusion_matrix(cm, sorted(set(y_test)))
                self.display_confusion_matrix_explanation()
                
                # Generate ROC curve
                self.plot_roc_curve(y_test, y_pred_proba)
                self.display_roc_explanation()
                
                # Generate classification report
                report = classification_report(y_test, y_pred, output_dict=True, zero_division=0)
                self.display_classification_report(report)
                
            else:
                self.show_error("No training data available")
                
        except Exception as e:
            self.show_error(f"Error generating performance report: {str(e)}")
    
    def plot_confusion_matrix(self, cm, classes):
        """Plot confusion matrix"""
        # Clear previous plot
        self.figure.clear()
        
        ax = self.figure.add_subplot(111)
        im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues, aspect='auto')
        
        # Add colorbar with proper sizing
        cbar = self.figure.colorbar(im, ax=ax, shrink=0.8)
        cbar.ax.tick_params(labelsize=9)
        
        # Set labels with better formatting
        ax.set_xticks(np.arange(cm.shape[1]))
        ax.set_yticks(np.arange(cm.shape[0]))
        ax.set_xticklabels(classes, fontsize=9)
        ax.set_yticklabels(classes, fontsize=9)
        
        # Set axis labels
        ax.set_ylabel('True Labels', fontsize=11, fontweight='bold')
        ax.set_xlabel('Predicted Labels', fontsize=11, fontweight='bold')
        
        # Rotate the tick labels and set their alignment
        plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")
        
        # Add text annotations with better formatting
        thresh = cm.max() / 2.
        for i in range(cm.shape[0]):
            for j in range(cm.shape[1]):
                text_color = "white" if cm[i, j] > thresh else "black"
                ax.text(j, i, format(cm[i, j], 'd'),
                       ha="center", va="center",
                       color=text_color,
                       fontweight='bold',
                       fontsize=10)
        
        # Set title with better spacing
        ax.set_title("Confusion Matrix", fontweight='bold', fontsize=12, pad=15)
        
        # Adjust layout to prevent clipping
        self.figure.subplots_adjust(left=0.15, bottom=0.2, right=0.85, top=0.9)
        self.canvas.draw()
    
    def plot_roc_curve(self, y_test, y_pred_proba):
        """Plot ROC AUC curve for performance dialog"""
        try:
            # Clear previous plot
            self.roc_figure.clear()
            
            # Get unique classes from classifier
            classes = self.classifier.knn.classes_
            n_classes = len(classes)
            
            # Handle single class case
            if n_classes == 2:
                # For binary classification, use probability of positive class
                y_score = y_pred_proba[:, 1]
                y_true = (y_test == classes[1]).astype(int)
                
                # Compute ROC curve and ROC area
                fpr, tpr, _ = roc_curve(y_true, y_score)
                roc_auc = auc(fpr, tpr)
                
                # Plot ROC curve
                ax = self.roc_figure.add_subplot(111)
                ax.plot(fpr, tpr, color='darkorange', lw=2, 
                       label=f'ROC curve (AUC = {roc_auc:.3f})')
                ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', 
                       label='Random Classifier')
                ax.set_xlim([0.0, 1.0])
                ax.set_ylim([0.0, 1.05])
                ax.set_xlabel('False Positive Rate', fontsize=9)
                ax.set_ylabel('True Positive Rate', fontsize=9)
                ax.set_title('ROC Curve (Binary)', fontweight='bold', fontsize=10)
                ax.legend(loc="lower right", fontsize=8)
                ax.grid(True, alpha=0.3)
                
            else:
                # Multi-class ROC
                ax = self.roc_figure.add_subplot(111)
                
                # Compute ROC curve and ROC area for each class
                fpr = dict()
                tpr = dict()
                roc_auc = dict()
                
                for i in range(n_classes):
                    # Get true binary labels for this class
                    y_true_class = (y_test == classes[i]).astype(int)
                    # Get predicted probabilities for this class
                    y_score_class = y_pred_proba[:, i]
                    
                    fpr[i], tpr[i], _ = roc_curve(y_true_class, y_score_class)
                    roc_auc[i] = auc(fpr[i], tpr[i])
                
                # Plot ROC curve for each class
                colors = ['blue', 'red', 'green', 'orange', 'purple', 'brown']
                for i in range(n_classes):
                    color = colors[i % len(colors)]
                    ax.plot(fpr[i], tpr[i], color=color, lw=1.5,
                           label=f'Paket {classes[i]} (AUC = {roc_auc[i]:.3f})')
                
                # Plot random classifier line
                ax.plot([0, 1], [0, 1], 'k--', lw=1.5, label='Random', alpha=0.8)
                
                ax.set_xlim([0.0, 1.0])
                ax.set_ylim([0.0, 1.05])
                ax.set_xlabel('False Positive Rate', fontsize=9)
                ax.set_ylabel('True Positive Rate', fontsize=9)
                ax.set_title('Multi-class ROC Curves', fontweight='bold', fontsize=10)
                
                # Position legend to avoid overlap
                ax.legend(loc="center left", bbox_to_anchor=(1, 0.5), fontsize=7)
                ax.grid(True, alpha=0.3)
            
            # Adjust layout
            self.roc_figure.subplots_adjust(left=0.15, bottom=0.15, right=0.7, top=0.9)
            self.roc_canvas.draw()
            
        except Exception as e:
            print(f"Error plotting ROC curve: {e}")
            # Show error in ROC canvas
            ax = self.roc_figure.add_subplot(111)
            ax.text(0.5, 0.5, f'Error plotting ROC curve:\\n{str(e)}', 
                   ha='center', va='center', transform=ax.transAxes,
                   fontsize=9, color='red')
            ax.set_title("ROC Plot Error", fontweight='bold')
            self.roc_canvas.draw()
    
    def display_confusion_matrix_explanation(self):
        """Display explanation for confusion matrix"""
        explanation_text = """
🔍 CONFUSION MATRIX - Analisis Prediksi vs Aktual

Confusion Matrix adalah tabel yang menunjukkan performa klasifikasi dengan membandingkan prediksi model terhadap label aktual.

📊 INTERPRETASI:
• Baris (True Labels): Kelas sebenarnya dari data
• Kolom (Predicted Labels): Kelas yang diprediksi oleh model
• Diagonal utama: Prediksi yang BENAR
• Di luar diagonal: Prediksi yang SALAH

🎯 METRIK PENTING:
• True Positive (TP): Prediksi benar untuk kelas positif
• True Negative (TN): Prediksi benar untuk kelas negatif  
• False Positive (FP): Salah prediksi sebagai positif
• False Negative (FN): Salah prediksi sebagai negatif

💡 CARA MEMBACA:
• Nilai tinggi di diagonal = performa bagus
• Nilai tinggi di luar diagonal = banyak kesalahan
• Semakin gelap warna biru = semakin banyak prediksi

✅ TARGET IDEAL:
Confusion matrix yang baik memiliki nilai tinggi di diagonal utama dan nilai rendah di tempat lain, menunjukkan model dapat membedakan kelas dengan akurat.
        """
        
        self.confusion_explanation.setPlainText(explanation_text.strip())
    
    def display_roc_explanation(self):
        """Display explanation for ROC curves"""
        explanation_text = """
📈 ROC AUC CURVES - Evaluasi Performa Klasifikasi

ROC (Receiver Operating Characteristic) Curve menunjukkan performa model klasifikasi pada berbagai threshold dengan plot TPR vs FPR.

📊 KOMPONEN KURVA:
• True Positive Rate (TPR/Recall): TP/(TP+FN)
• False Positive Rate (FPR): FP/(FP+TN)
• AUC (Area Under Curve): Luas di bawah kurva ROC

🎯 INTERPRETASI AUC:
• AUC = 1.0: Klasifikasi sempurna
• AUC = 0.9-1.0: Performa sangat baik
• AUC = 0.8-0.9: Performa baik
• AUC = 0.7-0.8: Performa sedang
• AUC = 0.6-0.7: Performa buruk
• AUC = 0.5: Tidak lebih baik dari tebakan acak

📍 ANALISIS KURVA:
• Kurva mendekati sudut kiri atas = baik
• Kurva mendekati garis diagonal = buruk
• Garis putus-putus = Random Classifier (baseline)

🏆 MULTI-CLASS:
Untuk klasifikasi multi-class, setiap paket memiliki kurva ROC tersendiri dengan strategi One-vs-All.

✅ TARGET IDEAL:
AUC > 0.8 untuk setiap kelas menunjukkan model dapat membedakan kelas dengan baik.
        """
        
        self.roc_explanation.setPlainText(explanation_text.strip())
    
    def display_classification_report(self, report_dict):
        """Display classification report in table format similar to sklearn output"""
        # Create formatted text report with better alignment
        header = f"{'':>15} {'precision':>12} {'recall':>12} {'f1-score':>12} {'support':>12}\n"
        separator = "=" * 75 + "\n"
        
        report_text = header + separator
        
        # Add metrics for each class (sorted by class number)
        classes = [key for key in report_dict.keys() if key not in ['accuracy', 'macro avg', 'weighted avg']]
        
        # Sort classes numerically if they are numbers, otherwise alphabetically
        try:
            sorted_classes = sorted(classes, key=lambda x: int(x))
        except (ValueError, TypeError):
            sorted_classes = sorted(classes)
        
        for class_name in sorted_classes:
            metrics = report_dict[class_name]
            class_label = f"Paket {class_name}" if str(class_name).isdigit() else str(class_name)
            report_text += f"{class_label:>15} {metrics['precision']:>12.2f} {metrics['recall']:>12.2f} {metrics['f1-score']:>12.2f} {int(metrics['support']):>12}\n"
        
        # Add separator line
        report_text += "\n" + "-" * 75 + "\n"
        
        # Add accuracy
        accuracy = report_dict['accuracy']
        total_support = sum(report_dict[key]['support'] for key in classes)
        report_text += f"{'accuracy':>15} {'':>12} {'':>12} {accuracy:>12.2f} {int(total_support):>12}\n"
        
        # Add macro avg
        macro_avg = report_dict['macro avg']
        report_text += f"{'macro avg':>15} {macro_avg['precision']:>12.2f} {macro_avg['recall']:>12.2f} {macro_avg['f1-score']:>12.2f} {int(macro_avg['support']):>12}\n"
        
        # Add weighted avg
        weighted_avg = report_dict['weighted avg']
        report_text += f"{'weighted avg':>15} {weighted_avg['precision']:>12.2f} {weighted_avg['recall']:>12.2f} {weighted_avg['f1-score']:>12.2f} {int(weighted_avg['support']):>12}\n"
        
        # Add some explanation at the bottom
        report_text += "\n" + "=" * 75 + "\n"
        report_text += "\nPenjelasan:\n"
        report_text += "• Precision: Proporsi prediksi positif yang benar\n"
        report_text += "• Recall: Proporsi kelas aktual yang diprediksi dengan benar\n" 
        report_text += "• F1-score: Rata-rata harmonik dari precision dan recall\n"
        report_text += "• Support: Jumlah sampel untuk setiap kelas\n"
        report_text += "• Macro avg: Rata-rata tanpa mempertimbangkan ketidakseimbangan kelas\n"
        report_text += "• Weighted avg: Rata-rata dengan mempertimbangkan support setiap kelas"
        
        self.report_text.setPlainText(report_text)
        
        # Also display detailed explanation
        self.display_classification_explanation()
    
    def display_classification_explanation(self):
        """Display detailed explanation for classification report"""
        explanation_text = """
📋 CLASSIFICATION REPORT - Analisis Detail Performa

Classification Report memberikan metrik evaluasi yang komprehensif untuk setiap kelas dalam klasifikasi multi-class.

🎯 METRIK UTAMA:

1️⃣ PRECISION (Ketepatan)
• Formula: TP / (TP + FP)
• Arti: Dari semua yang diprediksi positif, berapa yang benar-benar positif?
• Tinggi = sedikit False Positive

2️⃣ RECALL (Sensitivitas/TPR)
• Formula: TP / (TP + FN)  
• Arti: Dari semua yang sebenarnya positif, berapa yang berhasil diprediksi?
• Tinggi = sedikit False Negative

3️⃣ F1-SCORE (Keseimbangan)
• Formula: 2 × (Precision × Recall) / (Precision + Recall)
• Arti: Rata-rata harmonik precision dan recall
• Baik ketika precision dan recall seimbang tinggi

📊 RATA-RATA:

▶️ MACRO AVERAGE:
• Rata-rata sederhana dari semua kelas
• Tidak mempertimbangkan ketidakseimbangan data
• Memberikan bobot sama untuk setiap kelas

▶️ WEIGHTED AVERAGE:
• Rata-rata berbobot berdasarkan support
• Mempertimbangkan jumlah sampel per kelas
• Lebih representatif untuk data tidak seimbang

✅ INTERPRETASI SKOR:
• 0.9-1.0: Sangat Baik
• 0.8-0.9: Baik  
• 0.7-0.8: Cukup Baik
• 0.6-0.7: Perlu Perbaikan
• <0.6: Kurang Baik
        """
        
        self.class_explanation.setPlainText(explanation_text.strip())
    
    def show_error(self, message):
        """Show error message"""
        ax = self.figure.add_subplot(111)
        ax.text(0.5, 0.5, message, 
               ha='center', va='center', transform=ax.transAxes)
        self.canvas.draw()
        
        ax2 = self.roc_figure.add_subplot(111)
        ax2.text(0.5, 0.5, message, 
               ha='center', va='center', transform=ax2.transAxes)
        self.roc_canvas.draw()

        self.report_text.setPlainText(f"Error: {message}")
        self.confusion_explanation.setPlainText(f"Error: {message}")
        self.roc_explanation.setPlainText(f"Error: {message}")
        self.class_explanation.setPlainText(f"Error: {message}")

