# Notebook Title: AA Prepflow - Missing Value Lab UI
# Author: Advanced Analytics Team
# Date: 2025-11-13
# Purpose: UI Gradio untuk eksperimen dan evaluasi penanganan missing value

from __future__ import annotations

# import sys

# sys.path.append('../aa-prepflow/')  # Add the src directory to sys.path

import gradio as gr
import pandas as pd
import numpy as np

from ..ui.data_evaluation import call_data_evaluation
from ..services.visualization import AA_Rapid_Eval
from ..services.evaluation_function import AALibrary


class MissingValueLab:
    """Kelas UI untuk lab eksperimen missing value di Gradio."""

    def __init__(self) -> None:
        """Inisialisasi MissingValueLab tanpa state tambahan."""
        pass

    def round_float_columns(self, df: pd.DataFrame, decimals: int = 2) -> pd.DataFrame:
        """Membulatkan kolom numerik (atau numeric-string) ke sejumlah desimal."""
        df_rounded = df.copy()
        for col in df_rounded.columns:
            # Check if the column is already numeric (float, int)
            if pd.api.types.is_numeric_dtype(df_rounded[col]):
                df_rounded[col] = df_rounded[col].round(decimals)
            # Check if the column contains objects (strings)
            elif pd.api.types.is_object_dtype(df_rounded[col]):
                try:
                    # Attempt to convert the series to a numeric type
                    numeric_series = pd.to_numeric(df_rounded[col], errors='raise')
                    # If successful, round the numeric series and convert it back to a string
                    df_rounded[col] = numeric_series.round(decimals).astype(str)
                except (ValueError, TypeError):
                    # If conversion fails, the column likely contains mixed
                    # or non-numeric strings, so we leave it as is.
                    pass
        return df_rounded

    # --- Fungsi Interaksi Gradio ---
    def load_and_update_options(
        self,
        data: dict,
        file_input,
        upload: bool,
    ):
        """Memuat data source dan meng-update pilihan dropdown/komponen UI."""
        if not data['data'] and file_input is None:
            # Jika tidak ada file, kosongkan semuanya
            empty_df = pd.DataFrame()
            return (
                empty_df,
                gr.update(choices=[], value=None), # Kosongkan eval_col_dd
                gr.update(choices=[], value=[]),   # Kosongkan col_filter
                gr.update(choices=[], value=None), # Kosongkan col_time
                gr.update(choices=[], value=None)  # Kosongkan category_filter
            )
     
        try:
            if upload:
                df = pd.read_csv(file_input)
            else:
                df = pd.DataFrame(data['data'], columns=data['headers'])
            df = self.round_float_columns(df)
            column_names = df.columns.tolist()
            num_cols = df.select_dtypes(include=np.number).columns.tolist()
     
            # Nilai default yang aman (None jika daftar kosong)
            default_num_val = num_cols[0] if num_cols else None
            default_col_val = column_names[0] if column_names else None
            return (
                df,
                # Reset eval_col_dd dengan choices dan value yang baru & valid
                gr.update(choices=num_cols, value=None),
                # Reset col_filter (multiselect, value=[] sudah benar untuk mengosongkan)
                gr.update(choices=column_names, value=[]),
                # Reset col_time
                gr.update(choices=column_names, value=default_col_val),
                # Reset category_filter
                gr.update(choices=column_names, value=default_col_val)
            )
        except Exception as e:
            # Penanganan error jika file CSV tidak valid
            # print(f"Error reading CSV file: {e}")
            empty_df = pd.DataFrame() # Menggunakan pd.DataFrame kosong
            gr.Warning("Gagal membaca file CSV. Pastikan formatnya benar.")
            return (
                empty_df,
                gr.update(choices=[], value=None),
                gr.update(choices=[], value=[]),
                gr.update(choices=[], value=None),
                gr.update(choices=[], value=None)
            )

    def part_missing_value(
        self,
        visibility_cs: bool = True,
        visibility_ts: bool = False,
        visilbility_panel: bool = False,
    ):
        """Membangun blok UI pemilihan handler missing value per tipe data."""
        #CROSS SECTION
        handle_filter_cs = gr.Dropdown(
            choices=['Likewise Deletion Strategy', 'Mean Median Mode Imputer', 'Hot Deck Imputer', 'Regression Imputer', 'KNN Imputer Strategy'],
            label="Pilih Missing Value Handler",
            info="Handle Method",
            value="Likewise Deletion Strategy",
            visible=visibility_cs
        )
        data_handle_hyperparameter_cs = [["Metode", "mean", "Nilai hanya antara 'mean', 'median', atau 'mode'. Mengisi nilai yang kosong dengan metode yang dipilih."]]
        df_handle_hyperparameter_cs = gr.DataFrame(headers=["Handle Hyperparameter","Value","Description"], value=data_handle_hyperparameter_cs, label="Custom Hyperparameter", interactive=True, visible=False)
        
        #TIMESERIES
        handle_filter_ts = gr.Dropdown(
            choices=['LOCF Strategy', 'NOCB Strategy', 'Interpolation Strategy', 'Seasonal Imputer Strategy', 'ARIMA Imputer Strategy', 'Kalman Imputer Strategy', 'Moving Average Imputer Strategy', 'CAGR Imputer Strategy'],
            label="Pilih Missing Value Handler",
            info="Handle Method",
            value="LOCF Strategy",
            visible=visibility_ts
        )
        data_handle_hyperparameter_ts = [["Metode", "linear", "Nilai hanya antara 'linear' atau 'polynomial'. Mengisi nilai yang kosong dengan metode yang dipilih."]]
        df_handle_hyperparameter_ts = gr.DataFrame(headers=["Handle Hyperparameter","Value","Description"], value=data_handle_hyperparameter_ts, label="Custom Hyperparameter", interactive=True, visible=False)
        
        #PANEL
        handle_filter_panel = gr.Dropdown(
            choices=['Linear Strategy', 'LOCF Panel Strategy', 'MA Strategy', 'CAGR Strategy', 'Kalman Strategy', 'ARIMA Strategy'],
            label="Pilih Missing Value Handler",
            info="Handle Method",
            value="Linear Strategy",
            visible=visilbility_panel
        )
        data_handle_hyperparameter_panel = [["Random State", 42, "Parameter yang digunakan untuk mengontrol proses pengacakan (randomness) dalam algoritma atau fungsi, agar hasilnya bisa direproduksi secara konsisten"]]
        df_handle_hyperparameter_panel = gr.DataFrame(headers=["Handle Hyperparameter","Value","Description"], value=data_handle_hyperparameter_panel, label="Custom Hyperparameter", interactive=True, visible=False) # Menggunakan data_handle_hyperparameter_panel, bukan ts
        
        return handle_filter_cs, df_handle_hyperparameter_cs, handle_filter_ts, df_handle_hyperparameter_ts, handle_filter_panel,df_handle_hyperparameter_panel
        
    def change_in_handle_filter(self, handle_filter: str):
        """Mengatur isi & visibilitas tabel hyperparameter berdasar handler terpilih."""
        if handle_filter == "Likewise Deletion Strategy":
            return (gr.update(visible=False))
        
        elif handle_filter == "Mean Median Mode Imputer":
            data = [["Metode", "mean", "Nilai hanya antara 'mean', 'median', atau 'mode'. Mengisi nilai yang kosong dengan metode yang dipilih."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "Hot Deck Imputer":
            data = [["Random State", 42, "Parameter yang digunakan untuk mengontrol proses pengacakan (randomness) dalam algoritma atau fungsi, agar hasilnya bisa direproduksi secara konsisten"]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "Regression Imputer":
            return (gr.update(visible=False))
        
        elif handle_filter == "KNN Imputer Strategy":
            data = [["Jumlah Neighbor", 5, "Jumlah neighbor terdekat yang digunakan utnuk mengisi nilai yang hilang. Nilai rekomendasi 3-10. Jika nilai kecil maka akan lebih sensitif terhadap outlier, sedangkan nilai besar akan terlalu umum dan kurang spesifik."],["Nilai Bobot", "uniform", "Nilai antara 'uniform' atau 'distance'. Uniform berarti semua tetangga memiliki bobot yang sama. Kalau distance berarti tetangga yang lebih dekat memiliki bobo yang lebih besar."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "LOCF Strategy":
            return (gr.update(visible=False))
        
        elif handle_filter == "NOCB Strategy":
            return (gr.update(visible=False))
        
        elif handle_filter == "Interpolation Strategy":
            data = [["Metode", "linear", "Pilihan metode ada 'linear','polynomial','spline', dan lain sebagainya. Kalau polynomial harus memiliki order."],["Order", 2, "Rekomendasi antara 2 atau 3. Order digunakan untuk mengetahui seberapa kompleks kurva interpolasi. Terlalu tinggi bisa overfitting."],["Window", 2, "Rekomendasi window antara 2 - 10. Fitur ini untuk rolling value yang dimasukkan. Semakin tinggi, maka hasilnya akan smooth. Kalau semakin rendah maka akan mengikuti data yang lebih dekat."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "Seasonal Imputer Strategy":
            data = [["Periode Season", "month", "Pilihan 'month', 'weekofyear', dan 'quarter'. Nilai didasarkan rata - rata pada periode musim yang ditentukan."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "ARIMA Imputer Strategy":
            data = [["Order p non seasonal", 1 ,"Autoregressive (AR). Banyaknya lag dari nilai masa lalu yang digunakan"], ["Order d non seasonal", 1 ,"Differencing (I). Banyaknya differencing (pengurangan) untuk stasioner"], ["Order q non seasonal", 0 ,"Moving Average (MA). Banyaknya lag dari error masa lalu yang digunakan"], ["Order P Seasonal", 0 , "Seasonal AR. Autoregressive untuk data musiman."], ["Order D Seasonal", 0 , "Seasonal Differencing. Differencing pada komponen musiman"], ["Order Q Seasonal", 0 , "Seasonal MA. Moving Average untuk data musiman"], ["Order s Seasonal", 0 , "Seasonal Period. Panjang siklus musiman"]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "Kalman Imputer Strategy":
            data = [["Level", "local level", "Pilihan level: 'local level', 'local linear trend', 'random walk', dan 'fixed intercept'. Level menentukan struktur dasar dari model deret waktu yang digunakan untuk estimasi nilai yang hilang."], ["Seasonal", 0, "Berupa integer yang menyatakan panjang siklus musiman berdasarkan tiap record data"]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "Moving Average Imputer Strategy":
            data = [["Nilai K", 2, "Jumlah data yang bergerak dalam Moving Average"], ["Sisi data yang digunakan", "both", "Pilihan sisi yaitu 'both', 'past', dan 'future'. Both menggunakan data sebelum dan sesudah nilai yang hilang, past menggunakan data sebelum, dan future menggunakan data sesudah."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "CAGR Imputer Strategy":
            data = [["Minimal Positive", 0.000000001, "Lowest acceptable value for CAGR calculations."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "Linear Strategy":
            return (gr.update(visible=False))
        
        elif handle_filter == "LOCF Panel Strategy":
            return (gr.update(visible=False))
        
        elif handle_filter == "Hot Deck Strategy":
            data = [["Random State", 42, "Parameter yang digunakan untuk mengontrol proses pengacakan (randomness) dalam algoritma atau fungsi, agar hasilnya bisa direproduksi secara konsisten"]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "MA Strategy":
            data = [["Nilai K", 2, "Jumlah data yang bergerak dalam Moving Average"], ["Sisi data yang digunakan", "both", "Pilihan sisi yaitu 'both', 'past', dan 'future'. Both menggunakan data sebelum dan sesudah nilai yang hilang, past menggunakan data sebelum, dan future menggunakan data sesudah."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "CAGR Strategy":
            data = [["Minimal Positive", 0.000000001, "Lowest acceptable value for CAGR calculations."]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "Kalman Strategy":
            data = [["Level", "local level", "Pilihan level: 'local level', 'local linear trend', 'random walk', dan 'fixed intercept'. Level menentukan struktur dasar dari model deret waktu yang digunakan untuk estimasi nilai yang hilang."], ["Seasonal", 0, "Berupa integer yang menyatakan panjang siklus musiman berdasarkan tiap record data"]]
            return (gr.update(visible=True, value=data))
        
        elif handle_filter == "ARIMA Strategy":
            data = [["Order p non seasonal", 1 ,"Autoregressive (AR). Banyaknya lag dari nilai masa lalu yang digunakan"], ["Order d non seasonal", 1 ,"Differencing (I). Banyaknya differencing (pengurangan) untuk stasioner"], ["Order q non seasonal", 0 ,"Moving Average (MA). Banyaknya lag dari error masa lalu yang digunakan"], ["Order P Seasonal", 0 , "Seasonal AR. Autoregressive untuk data musiman."], ["Order D Seasonal", 0 , "Seasonal Differencing. Differencing pada komponen musiman"], ["Order Q Seasonal", 0 , "Seasonal MA. Moving Average untuk data musiman"], ["Order s Seasonal", 0 , "Seasonal Period. Panjang siklus musiman"]]
            return (gr.update(visible=True, value=data))
        
        return gr.update(visible=False) # Fallback

    def change_in_sidebar(self, treatement_amount: str, input_type_data: str):
        """Mengatur sidebar handler per jumlah treatment & tipe data (CS/TS/Panel)."""
        if input_type_data == "Cross Sectional Data":
            if treatement_amount == "1":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(True, False, False)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(False, False, False)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, False, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=False), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=False), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
            elif treatement_amount == "2":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(True, False, False)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(True, False, False)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, False, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=True), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=False), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
            elif treatement_amount == "3":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(True, False, False)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(True, False, False)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(True, False, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=True), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=True), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
        
        elif input_type_data == "Timeseries":
            if treatement_amount == "1":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(False, True, False)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(False, False, False)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, False, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=False), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=False), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
            elif treatement_amount == "2":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(False, True, False)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(False, True, False)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, False, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=True), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=False), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
            elif treatement_amount == "3":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(False, True, False)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(False, True, False)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, True, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=True), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=True), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
        
        elif input_type_data == "Panel":
            if treatement_amount == "1":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(False, False, True)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(False, False, False)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, False, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=False), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=False), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
            elif treatement_amount == "2":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(False, False, True)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(False, False, True)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, False, False)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=True), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=False), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
            elif treatement_amount == "3":
                handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1 = self.part_missing_value(False, False, True)
                handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(False, False, True)
                handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(False, False, True)
                return  handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, gr.update(visible=True), handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2,  gr.update(visible=True), handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3
        
        # Fallback
        return (
            gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
            gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
            gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(),
        )

    def call_data_missing_value(
        self,
        data,
        file_input,
        col_time,
        category_filter,
        col_filter,
        input_type_data,
        upload,
        demo,
    ):
        """Membangun seluruh layout Missing Value Lab dan wiring callback Gradio."""
        data_state_component = data
        jenis_check = gr.Textbox(value="missing_value", visible=False)

        with gr.Row():
            with gr.Tabs(elem_classes="level-2-tabs"):
                # ====================== TAB: TREATMENT METHOD ======================
                with gr.TabItem("Treatment Method"):
                    # 1. ListwiseDeletionStrategy
                    listwise_deletion_summary = """
                    **ListwiseDeletionStrategy** (Penghapusan Baris) adalah metode untuk **menghapus seluruh baris data** yang memiliki nilai hilang (NaN).
                    * **Cara Kerja:** Baris dengan nilai hilang dihapus sepenuhnya dari dataset.
                    * **Kelebihan:** Sederhana, mudah diterapkan, dan menjaga konsistensi antar variabel.
                    * **Kekurangan:** **Kehilangan informasi besar**, dapat menyebabkan **bias** jika hilangnya data tidak acak.
                    * **Kapan Digunakan:** Data Cross-section (bila proporsi *missing value* kecil <5%); Data Time-series/Panel (jika hilangnya data **homogen** di seluruh entitas/waktu).
                    """

                    # 2. MeanMedianModeImputer
                    mean_median_mode_summary = """
                    **MeanMedianModeImputer** (Pengisian dengan Statistik) adalah metode untuk **mengisi nilai hilang** dengan Mean, Median, atau Mode dari kolom.
                    * **Cara Kerja:** Nilai hilang diganti dengan statistik pusat kolom (Mean untuk numerik, Mode untuk kategorikal, Median untuk robust).
                    * **Kelebihan:** Mudah, cepat, dan efektif bila proporsi hilang kecil.
                    * **Kekurangan:** Mengurangi variabilitas, bias pada distribusi data, tidak mempertahankan hubungan antar variabel.
                    * **Kapan Digunakan:** Data Cross-section (cocok untuk numerik/kategorikal); Data Time-series (**kurang cocok** karena mengabaikan urutan waktu); Data Panel (jika pola hilang tidak tergantung waktu/entitas).
                    """

                    # 3. HotDeckImputer
                    hot_deck_summary = """
                    **HotDeckImputer** (Pengisian dari "Donor" Mirip) adalah metode untuk **mengisi nilai kosong** dengan nilai dari baris lain (*donor*) yang dianggap paling **mirip**.
                    * **Cara Kerja:** Mencari baris yang paling menyerupai baris dengan *missing value* dan menggunakan nilai baris tersebut sebagai pengganti.
                    * **Kelebihan:** Mempertahankan hubungan antar variabel dan distribusi alami data.
                    * **Kekurangan:** Butuh ukuran sampel besar dan perhitungan kemiripan yang akurat.
                    * **Kapan Digunakan:** Data Cross-section (cocok untuk survei atau data demografis); Data Panel (donor dari **entitas serupa** di waktu sama).
                    """

                    # 4. RegressionImputer
                    regression_summary = """
                    **RegressionImputer** (Pengisian Berbasis Regresi) adalah metode untuk **memprediksi nilai yang hilang** menggunakan model regresi antar variabel.
                    * **Cara Kerja:** Membuat model regresi di mana variabel yang hilang adalah variabel dependen, dan variabel lain adalah prediktor.
                    * **Kelebihan:** Preservasi hubungan antar variabel, akurat untuk hubungan **linear**.
                    * **Kekurangan:** Rentan terhadap **multikolinearitas**, *error* prediksi, dan sensitif terhadap *outlier*.
                    * **Kapan Digunakan:** Data Cross-section (ketika terdapat **korelasi kuat** antar variabel); Data Time-series (bisa digunakan dengan regresi **temporal/lag**).
                    """

                    # 5. KNNImputerStrategy
                    knn_summary = """
                    **KNNImputerStrategy** (K-Nearest Neighbors) adalah metode untuk **mengisi *missing value*** dengan rata-rata nilai dari **K tetangga terdekat**.
                    * **Cara Kerja:** Menghitung jarak (misal Euclidean) antar titik data dan menggunakan rata-rata nilai dari K tetangga terdekat yang valid.
                    * **Kelebihan:** Non-parametrik, fleksibel, bisa menyesuaikan dengan pola data non-linear.
                    * **Kekurangan:** Komputasi berat dan sensitif terhadap pemilihan nilai **K** serta *outlier*.
                    * **Kapan Digunakan:** Data Cross-section (baik untuk data **non-linear** dengan hubungan kompleks); Data Time-series (**kurang cocok** karena mengabaikan urutan waktu).
                    """

                    # 6. LOCFStrategy
                    locf_summary = """
                    **LOCFStrategy** (Last Observation Carried Forward) adalah metode untuk **mengganti *outlier* dengan nilai valid terakhir** yang diamati.
                    * **Cara Kerja:** Nilai hilang diisi dengan nilai yang langsung mendahuluinya dalam urutan waktu.
                    * **Kelebihan:** Menjaga urutan waktu, mudah diterapkan, dan tidak merusak pola.
                    * **Kekurangan:** **Overestimation** untuk tren naik dan underestimation untuk tren turun; menciptakan artefak "garis datar".
                    * **Kapan Digunakan:** Data **Time-Series atau Panel** yang memiliki urutan (temporal), dengan asumsi nilai data cenderung tetap sama dari satu waktu ke waktu berikutnya.
                    """

                    # 7. NOCBStrategy
                    nocb_summary = """
                    **NOCBStrategy** (Next Observation Carried Backward) adalah metode untuk **mengisi *missing value* dengan nilai berikutnya** yang tersedia.
                    * **Cara Kerja:** Nilai hilang diisi dengan nilai yang langsung mengikutinya dalam urutan waktu.
                    * **Kelebihan:** Cocok untuk data yang stabil atau *backward-looking*.
                    * **Kekurangan:** Tidak cocok untuk data diskrit atau tren naik/fluktuatif (data dengan perubahan besar antar waktu).
                    * **Kapan Digunakan:** Data **Time-series** (jika arah pergerakan data ke depan lebih representatif); Data Panel (berguna untuk memperkirakan **awal periode** yang hilang).
                    """

                    # 8. InterpolationStrategy
                    interpolation_summary = """
                    **InterpolationStrategy** adalah metode untuk **mengestimasi nilai hilang** menggunakan interpolasi linear atau polinomial.
                    * **Cara Kerja:** Menghitung nilai yang hilang berdasarkan tren antara titik data sebelum dan sesudahnya.
                    * **Kelebihan:** Menjaga tren dan pola waktu, mudah diimplementasikan.
                    * **Kekurangan:** Tidak cocok untuk data diskrit dan non-linear ekstrem (naik-turun tajam).
                    * **Kapan Digunakan:** Ideal untuk data **kontinu** (Time-series/Panel).
                    """

                    # 9. SeasonalImputerStrategy
                    seasonal_summary = """
                    **SeasonalImputerStrategy** adalah metode untuk **mengisi nilai hilang** berdasarkan rata-rata atau **pola musiman** periode serupa.
                    * **Cara Kerja:** Mengisi nilai hilang dengan nilai yang merupakan rata-rata dari periode yang sama di tahun-tahun sebelumnya.
                    * **Kelebihan:** Mempertahankan pola musiman, cocok untuk data periodik.
                    * **Kekurangan:** Membutuhkan **cukup banyak observasi** per musim.
                    * **Kapan Digunakan:** Data **Time-series** (cocok untuk data dengan **pola musiman kuat**); Data Panel (jika setiap entitas memiliki pola musiman konsisten).
                    """

                    # 10. ARIMAImputerStrategy
                    arima_summary = """
                    **ARIMAImputerStrategy** adalah metode untuk **memperkirakan nilai hilang** menggunakan model ARIMA (Autoregressive Integrated Moving Average).
                    * **Cara Kerja:** Membangun model time-series untuk memprediksi nilai hilang berdasarkan pola autoregresif dan *moving average*.
                    * **Kelebihan:** Akurat untuk data stasioner atau yang dapat distasionerkan.
                    * **Kekurangan:** **Kompleks**, sensitif terhadap parameter p, d, q.
                    * **Kapan Digunakan:** Data **Time-series univariat** dengan pola **tren/musiman**; Data Panel (dapat diterapkan per entitas).
                    """

                    # 11. KalmanFilterStrategy
                    kalman_summary = """
                    **KalmanFilterStrategy** adalah metode untuk **memperkirakan nilai hilang** menggunakan filter Kalman berdasarkan model *state-space*.
                    * **Cara Kerja:** Menggunakan serangkaian pengukuran yang diamati seiring waktu untuk memprediksi nilai hilang.
                    * **Kelebihan:** Optimal untuk data **stokastik** (data kompleks), mampu menangkap dinamika sistem.
                    * **Kekurangan:** Membutuhkan model matematis yang lebih rumit.
                    * **Kapan Digunakan:** Data **Time-series** (sangat baik untuk data dengan **noise** atau **berubah-ubah**); Data Panel (dapat diterapkan secara individual per entitas).
                    """

                    # 12. MovingAverageImputerStrategy
                    moving_average_summary = """
                    **MovingAverageImputerStrategy** adalah metode untuk **mengisi nilai kosong** dengan rata-rata dari **beberapa periode terakhir**.
                    * **Cara Kerja:** Mengganti nilai hilang dengan rata-rata *window* tertentu yang mendahului titik data yang hilang.
                    * **Kelebihan:** Sederhana, menjaga tren umum dan mengurangi fluktuasi ekstrem.
                    * **Kekurangan:** Menghaluskan data terlalu banyak, bisa menghilangkan lonjakan penting.
                    * **Kapan Digunakan:** Data **Time-series** (ideal untuk data dengan **noise rendah** dan **fluktuasi besar**); Data Panel (cocok bila tiap entitas menunjukkan pola tren stabil).
                    """

                    # 13. CAGRImputerStrategy
                    cagr_summary = """
                    **CAGRImputerStrategy** (Compound Annual Growth Rate) adalah metode untuk **mengisi nilai kosong** antar dua periode menggunakan pertumbuhan rata-rata tahunan.
                    * **Cara Kerja:** Menggunakan tingkat pertumbuhan tahunan gabungan untuk mengestimasi nilai-nilai yang hilang di antaranya.
                    * **Kelebihan:** Cocok untuk data **pertumbuhan stabil** dan berskala besar tiap tahun.
                    * **Kekurangan:** Tidak cocok untuk data **fluktuatif** jangka pendek.
                    * **Kapan Digunakan:** Data **Time-series ekonomi/keuangan** antar periode tahunan; Data Panel (dapat diterapkan per entitas).
                    """
                    gr.Markdown("## 📋 Informasi Metode Penanganan Missing Value (Imputasi)")
    
                    # Kelompok 1: Penghapusan, Sederhana, dan Model Dasar
                    with gr.Accordion("Grup 1: Based on Observasi", open=True):
                        with gr.Accordion("1. ListwiseDeletionStrategy", open=False):
                            gr.Markdown(listwise_deletion_summary)
                        with gr.Accordion("2. MeanMedianModeImputer", open=False):
                            gr.Markdown(mean_median_mode_summary)
                        with gr.Accordion("3. HotDeckImputer", open=False):
                            gr.Markdown(hot_deck_summary)
                        with gr.Accordion("4. RegressionImputer", open=False):
                            gr.Markdown(regression_summary)
                        with gr.Accordion("5. KNNImputerStrategy", open=False):
                            gr.Markdown(knn_summary)
                        
                    # Kelompok 2: Metode Time-Series Sederhana hingga Lanjutan
                    with gr.Accordion("Grup 2: Based on Timeseries", open=False):
                        with gr.Accordion("6. LOCFStrategy (Last Observation Carried Forward)", open=False):
                            gr.Markdown(locf_summary)
                        with gr.Accordion("7. NOCBStrategy (Next Observation Carried Backward)", open=False):
                            gr.Markdown(nocb_summary)
                        with gr.Accordion("8. InterpolationStrategy", open=False):
                            gr.Markdown(interpolation_summary)
                        with gr.Accordion("9. SeasonalImputerStrategy", open=False):
                            gr.Markdown(seasonal_summary)
                        with gr.Accordion("10. ARIMAImputerStrategy", open=False):
                            gr.Markdown(arima_summary)
                        with gr.Accordion("11. KalmanFilterStrategy", open=False):
                            gr.Markdown(kalman_summary)
                        with gr.Accordion("12. MovingAverageImputerStrategy", open=False):
                            gr.Markdown(moving_average_summary)
                        with gr.Accordion("13. CAGRImputerStrategy", open=False):
                            gr.Markdown(cagr_summary)

                    gr.Markdown("## Missing Value Lab")
                    treatement_amount = gr.Dropdown(
                        choices=['1','2','3'], 
                        label="Amount of Treatment",
                        info="Method comparison across [X] treatments."
                    )
                    treatement_markdown1 = gr.Markdown("### Treatment 1")
                    handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1  = self.part_missing_value(visibility_cs=True)#VISIBILITAS DISINI UNTUK INITIAL STATE
                    treatement_markdown2 = gr.Markdown("### Treatment 2", visible=False)
                    handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2 = self.part_missing_value(visibility_cs=False)#VISIBILITAS DISINI UNTUK INITIAL STATE
                    treatement_markdown3 = gr.Markdown("### Treatment 3", visible=False)
                    handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3 = self.part_missing_value(visibility_cs=False)#VISIBILITAS DISINI UNTUK INITIAL STATE
                    apply_btn = gr.Button("✨ Terapkan & Evaluasi", variant="primary", elem_classes="apply-button")
                
                # ====================== TAB: RESULT EVALUATION ======================
                with gr.TabItem("Result Evaluation"):
                    with gr.Row():
                        with gr.Column():
                            gr.Markdown("### Hasil & Evaluasi")
                            with gr.Tabs(elem_classes="level-3-tabs"):
                                with gr.TabItem("Pratinjau Data"):
                                    with gr.Row():
                                        df_raw_output = gr.DataFrame(scale=1, label="Perbandingan Data", elem_classes="result-dataframe")
                                        df_raw_ori = gr.DataFrame(scale=1, label="Perbandingan Data", visible=False, elem_classes="result-dataframe")
                                        df_clean_output = gr.DataFrame(scale=1, label="Data Bersih 1", visible=False, elem_classes="result-dataframe")
                                        df_clean_output2 = gr.DataFrame(scale=1, label="Data Bersih 2", visible=False, elem_classes="result-dataframe")
                                        df_clean_output3 = gr.DataFrame(scale=1, label="Data Bersih 3", visible=False, elem_classes="result-dataframe")
                                
                                (
                                    eval_col_dd,
                                    eval_cat_dd,
                                    plot_output,
                                    stats_output,
                                    interpretation_output,
                                    strategy_plot_output,
                                    summary_stats_before,
                                    df_signifikansi_test,
                                    plot_tab,
                                    uji_statistik_tab
                                ) = call_data_evaluation()
            
            # ====================== WIRING EVENT HANDLERS ======================
            
            if upload.value:
                file_input.upload(fn=self.load_and_update_options, inputs=[gr.State(data_state_component.value), file_input, upload], outputs=[df_raw_output, eval_col_dd, col_filter, col_time, category_filter])
            else:
                demo.load(fn=self.load_and_update_options, inputs=[gr.State(data_state_component.value), file_input, upload], outputs=[df_raw_output, eval_col_dd, col_filter, col_time, category_filter])
            
            treatement_amount.change(fn=self.change_in_sidebar, inputs=[treatement_amount, input_type_data], outputs=[handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, treatement_markdown2, handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2, treatement_markdown3, handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3])
            
            input_type_data.change(fn=self.change_in_sidebar, inputs=[treatement_amount, input_type_data], outputs=[handle_filter_cs1, df_handle_hyperparameter_cs1, handle_filter_ts1, df_handle_hyperparameter_ts1, handle_filter_panel1, df_handle_hyperparameter_panel1, treatement_markdown2, handle_filter_cs2, df_handle_hyperparameter_cs2, handle_filter_ts2, df_handle_hyperparameter_ts2, handle_filter_panel2, df_handle_hyperparameter_panel2, treatement_markdown3, handle_filter_cs3, df_handle_hyperparameter_cs3, handle_filter_ts3, df_handle_hyperparameter_ts3, handle_filter_panel3, df_handle_hyperparameter_panel3])
            
            handle_filter_cs1.change(fn=self.change_in_handle_filter, inputs=[handle_filter_cs1], outputs=[df_handle_hyperparameter_cs1])
            handle_filter_cs2.change(fn=self.change_in_handle_filter, inputs=[handle_filter_cs2], outputs=[df_handle_hyperparameter_cs2])
            handle_filter_cs3.change(fn=self.change_in_handle_filter, inputs=[handle_filter_cs3], outputs=[df_handle_hyperparameter_cs3])
            handle_filter_ts1.change(fn=self.change_in_handle_filter, inputs=[handle_filter_ts1], outputs=[df_handle_hyperparameter_ts1])
            handle_filter_ts2.change(fn=self.change_in_handle_filter, inputs=[handle_filter_ts2], outputs=[df_handle_hyperparameter_ts2])
            handle_filter_ts3.change(fn=self.change_in_handle_filter, inputs=[handle_filter_ts3], outputs=[df_handle_hyperparameter_ts3])
            handle_filter_panel1.change(fn=self.change_in_handle_filter, inputs=[handle_filter_panel1], outputs=[df_handle_hyperparameter_panel1])
            handle_filter_panel2.change(fn=self.change_in_handle_filter, inputs=[handle_filter_panel2], outputs=[df_handle_hyperparameter_panel2])
            handle_filter_panel3.change(fn=self.change_in_handle_filter, inputs=[handle_filter_panel3], outputs=[df_handle_hyperparameter_panel3])

            aa_library = AALibrary()
            apply_btn.click(fn=aa_library.run_data_formatting_process, 
                            inputs=[gr.State(data_state_component.value), file_input, upload, eval_col_dd, col_filter, jenis_check, input_type_data, category_filter, col_time, treatement_amount, handle_filter_cs1,df_handle_hyperparameter_cs1,handle_filter_ts1
                                    ,df_handle_hyperparameter_ts1,handle_filter_panel1,df_handle_hyperparameter_panel1,handle_filter_cs2,df_handle_hyperparameter_cs2,handle_filter_ts2,df_handle_hyperparameter_ts2
                                    ,handle_filter_panel2,df_handle_hyperparameter_panel2,handle_filter_cs3,df_handle_hyperparameter_cs3,handle_filter_ts3,df_handle_hyperparameter_ts3,handle_filter_panel3,df_handle_hyperparameter_panel3], 
                            outputs=[df_raw_output, plot_output, stats_output, interpretation_output, eval_col_dd, eval_cat_dd, strategy_plot_output, summary_stats_before, df_signifikansi_test, df_clean_output, df_clean_output2, df_clean_output3, df_raw_ori])

            aa_rapid_eval = AA_Rapid_Eval()

            eval_col_dd.change(fn=aa_rapid_eval.change_in_plot_strategy, inputs=[df_raw_ori, df_clean_output, df_clean_output2, df_clean_output3, treatement_amount, eval_col_dd, category_filter, eval_cat_dd, col_time, input_type_data], outputs=[strategy_plot_output, plot_tab])
            eval_col_dd.change(fn=aa_rapid_eval.change_in_distribution_strategies, inputs=[df_raw_ori, df_clean_output, df_clean_output2, df_clean_output3, eval_col_dd, category_filter, eval_cat_dd, col_time, treatement_amount, input_type_data], outputs=[plot_output])
            eval_col_dd.change(fn=aa_rapid_eval.change_in_descriptive_and_signifikansi, inputs=[jenis_check, col_filter, input_type_data, category_filter, eval_cat_dd, treatement_amount, df_raw_ori, df_clean_output, df_clean_output2, df_clean_output3, eval_col_dd, col_time], outputs=[summary_stats_before, df_signifikansi_test])
            eval_col_dd.change(fn=aa_rapid_eval.create_comparison_viz_and_stats, inputs=[df_raw_ori, df_clean_output, eval_col_dd, input_type_data, col_time, category_filter, df_clean_output2, df_clean_output3, treatement_amount, eval_cat_dd], outputs=[stats_output, interpretation_output, uji_statistik_tab])

            eval_cat_dd.change(fn=aa_rapid_eval.change_in_plot_strategy, inputs=[df_raw_ori, df_clean_output, df_clean_output2, df_clean_output3, treatement_amount, eval_col_dd, category_filter, eval_cat_dd, col_time, input_type_data], outputs=[strategy_plot_output, plot_tab])
            eval_cat_dd.change(fn=aa_rapid_eval.change_in_distribution_strategies, inputs=[df_raw_ori, df_clean_output, df_clean_output2, df_clean_output3, eval_col_dd, category_filter, eval_cat_dd, col_time, treatement_amount, input_type_data], outputs=[plot_output])
            eval_cat_dd.change(fn=aa_rapid_eval.change_in_descriptive_and_signifikansi, inputs=[jenis_check, col_filter, input_type_data, category_filter, eval_cat_dd, treatement_amount, df_raw_ori, df_clean_output, df_clean_output2, df_clean_output3, eval_col_dd, col_time], outputs=[summary_stats_before, df_signifikansi_test])
            eval_col_dd.change(fn=aa_rapid_eval.create_comparison_viz_and_stats, inputs=[df_raw_ori, df_clean_output, eval_col_dd, input_type_data, col_time, category_filter, df_clean_output2, df_clean_output3, treatement_amount, eval_cat_dd], outputs=[stats_output, interpretation_output, uji_statistik_tab])

# ===================================================================
# Ringkasan Hasil / Summary Update (Missing Value Lab UI)
# ===================================================================
# - Menambahkan metadata header (Notebook Title, Author, Date, Purpose).
# - Menambahkan docstring singkat (<3 baris) di semua method utama kelas.
# - Merapikan import, snake_case, dan penataan event handler tanpa mengubah logic.
# - Memperbaiki `load_and_update_options` agar mengembalikan pd.DataFrame kosong saat error.
# - Memperbaiki deskripsi hyperparameter di `change_in_handle_filter` agar lebih jelas.
# - Logika `change_in_sidebar` (fungsi paling kompleks) dipertahankan 100%
#   sesuai file asli untuk menjaga kompatibilitas wiring Gradio.
# - Struktur UI, nama komponen, dan wiring Gradio tetap kompatibel dengan versi sebelumnya.