# Notebook Title: Evaluation Functions for AA Prepflow
# Author: Advanced Analytics Team
# Date: 2025-11-13
# Purpose: Orkestrasi profiling, formatting data, dan pipeline MV/Outlier

from __future__ import annotations

# Standard Libraries
# import sys
import base64
from typing import List

# Data
import numpy as np
import pandas as pd

# UI / Third-Party
import gradio as gr
from ydata_profiling import ProfileReport

# # Project Modules (modular code dari src/)
# sys.path.append("../aa-prepflow/")
from ..services.visualization import AA_Rapid_Eval

from ..models.outlier_value import * # noqa: F401,F403
from ..models.missing_value import * # noqa: F401,F403


class AALibrary:
    """Koleksi fungsi utilitas untuk AA Prepflow."""

    def __init__(self):
        """Inisialisasi instance AALibrary tanpa state khusus."""
        pass

    def generate_profile_report(self, file_obj, data, upload):
        """Buat ydata-profiling report dan kembalikan iframe HTML + DataFrame."""
        if upload and file_obj is None:
            return (
                "<h1>Please upload a CSV file to generate the profile report.</h1>",
                gr.DataFrame(visible=False),
            )

        try:
            if not data["data"] and upload:
                df = pd.read_csv(file_obj.name)
            else:
                df = pd.DataFrame(data["data"])

            profile = ProfileReport(
                df,
                title="Pandas Profiling Report (ydata-profiling)",
                html={"style": {"full_width": True}},
                sort=None,
                pool_size=1,
                minimal=True,
                progress_bar=False,
            )

            html_report = profile.to_html()
            html_bytes = html_report.encode("utf-8")
            encoded_html = base64.b64encode(html_bytes).decode("utf-8")

            iframe_src = f"data:text/html;base64,{encoded_html}"
            iframe_html = f"""
            <iframe 
                src="{iframe_src}" 
                width="100%" 
                height="800px" 
                style="border: none;zoom: 0.85; transform-origin: top left;">
            </iframe>
            """

            return iframe_html, df

        except Exception as exc:  # pragma: no cover (fail-safe)
            return (
                f"<h1>An error occurred: {exc}</h1>",
                gr.DataFrame(visible=False),
            )

    def rearrange_dataframe_first(self, df: pd.DataFrame, list_kolom: List[str]):
        """Pindahkan list_kolom ke posisi paling kiri DataFrame."""
        kolom_kiri = list_kolom
        kolom_lain = [col for col in df.columns if col not in kolom_kiri]
        urutan_kolom_baru = kolom_kiri + kolom_lain
        df = df[urutan_kolom_baru]
        return df

    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:
            if pd.api.types.is_numeric_dtype(df_rounded[col]):
                df_rounded[col] = df_rounded[col].round(decimals)
            elif pd.api.types.is_object_dtype(df_rounded[col]):
                try:
                    numeric_series = pd.to_numeric(
                        df_rounded[col],
                        errors="raise",
                    )
                    df_rounded[col] = numeric_series.round(decimals).astype(str)
                except (ValueError, TypeError):
                    pass
        return df_rounded

    def combine_into_a_dataframe(
        self,
        df_raw: pd.DataFrame,
        version_dfs: List[pd.DataFrame],
        keep_raw_strings: bool = True,  # kept for compatibility
    ) -> pd.DataFrame:
        """Gabungkan df_raw dengan beberapa versi numerik df lain dalam satu tabel."""

        def is_numeric_or_numeric_str(series: pd.Series) -> bool:
            """Cek apakah series numerik atau string yang numeric-like."""
            if pd.api.types.is_numeric_dtype(series):
                return True

            if not (
                pd.api.types.is_string_dtype(series)
                or pd.api.types.is_object_dtype(series)
            ):
                return False

            try:
                series_no_na = series.dropna()
                if series_no_na.empty:
                    return True
                numeric_series = pd.to_numeric(series_no_na, errors="coerce")
                if numeric_series.isnull().any():
                    return False
                return True
            except Exception:  # pragma: no cover
                return False

        def filter_version_df(df: pd.DataFrame) -> pd.DataFrame:
            """Ambil hanya kolom numerik atau numeric-like string dari df."""
            return df.loc[
                :,
                [
                    col
                    for col in df.columns
                    if is_numeric_or_numeric_str(df[col])
                ],
            ]

        def combine_dataframes(
            df_raw_local: pd.DataFrame,
            version_dfs_local: List[pd.DataFrame],
        ) -> pd.DataFrame:
            """Gabungkan df_raw dengan versi-versi numerik, plus suffix _raw/_1/_2."""
            filtered_versions = [filter_version_df(df) for df in version_dfs_local]

            common_cols = set(filtered_versions[0].columns)
            for df in filtered_versions[1:]:
                common_cols &= set(df.columns)
            sorted_cols = sorted(common_cols)

            renamed_versions = []
            for i, df in enumerate(filtered_versions, start=1):
                df_selected = df[sorted_cols].copy()
                df_renamed = df_selected.rename(
                    columns={col: f"{col}_{i}" for col in df_selected.columns},
                )
                renamed_versions.append(df_renamed)

            df_raw_copy = df_raw_local.copy()
            raw_renamed_cols = {
                col: f"{col}_raw" if col in sorted_cols else col
                for col in df_raw_copy.columns
            }
            df_raw_renamed = df_raw_copy.rename(columns=raw_renamed_cols)

            df_raw_renamed = df_raw_renamed.reset_index(drop=True)
            renamed_versions = [
                df.reset_index(drop=True) for df in renamed_versions
            ]

            combined_df = pd.concat([df_raw_renamed] + renamed_versions, axis=1)

            def base(col_name: str) -> str:
                """Ambil base name dari kolom (tanpa suffix _raw/_1/_2)."""
                for suffix in ["_raw"] + [
                    f"_{i}" for i in range(1, len(version_dfs_local) + 1)
                ]:
                    if col_name.endswith(suffix):
                        return col_name.replace(suffix, "")
                return col_name

            combined_df = combined_df[
                sorted(combined_df.columns, key=lambda x: (base(x), x))
            ]
            return combined_df

        combined_df = combine_dataframes(df_raw, version_dfs)
        return combined_df

    def run_data_formatting_process(
        self,
        data_temp,
        file_input,
        upload,
        col_to_eval,
        col_filter,
        task_type,
        input_type_data,
        category_filter,
        col_time: str = "",
        treatement_amount: str = "1",
        argument_1=None,
        argument_2=None,
        argument_3=None,
        argument_4=None,
        argument_5=None,
        argument_6=None,
        argument_7=None,
        argument_8=None,
        argument_9=None,
        argument_10=None,
        argument_11=None,
        argument_12=None,
        argument_13=None,
        argument_14=None,
        argument_15=None,
        argument_16=None,
        argument_17=None,
        argument_18=None,
    ):
        """Pipeline utama formatting data: missing value / outlier + kombinasi hasil."""
        if upload:
            df_raw_ori = pd.read_csv(file_input)
        else:
            df_raw_ori = pd.DataFrame(
                data_temp["data"],
                columns=data_temp["headers"],
            )

        aa_rapid_eval = AA_Rapid_Eval()

        if input_type_data in ["Panel", "Timeseries"] and col_time:
            df_raw_ori[col_time] = aa_rapid_eval.normalize_by_parts(df_raw_ori[col_time])

        df_raw = df_raw_ori[
            [col for col in df_raw_ori.columns if col not in col_filter]
        ]

        df_clean = df_raw.copy()
        df_clean2 = df_raw.copy()
        df_clean3 = df_raw.copy()

        # ==========================================================
        # MISSING VALUE HANDLING
        # ==========================================================
        if task_type == "missing_value":
            handle_filter_cs1 = argument_1
            df_handle_hyperparameter_cs1 = argument_2
            handle_filter_ts1 = argument_3
            df_handle_hyperparameter_ts1 = argument_4
            handle_filter_panel1 = argument_5
            df_handle_hyperparameter_panel1 = argument_6
            handle_filter_cs2 = argument_7
            df_handle_hyperparameter_cs2 = argument_8
            handle_filter_ts2 = argument_9
            df_handle_hyperparameter_ts2 = argument_10
            handle_filter_panel2 = argument_11
            df_handle_hyperparameter_panel2 = argument_12
            handle_filter_cs3 = argument_13
            df_handle_hyperparameter_cs3 = argument_14
            handle_filter_ts3 = argument_15
            df_handle_hyperparameter_ts3 = argument_16
            handle_filter_panel3 = argument_17
            df_handle_hyperparameter_panel3 = argument_18

            # ---- CROSS SECTIONAL ----
            if input_type_data == "Cross Sectional Data":
                if int(treatement_amount) == 3:
                    if handle_filter_cs3 == "Likewise Deletion Strategy":
                        handle_strategy_3 = ListwiseDeletionStrategy()
                    elif handle_filter_cs3 == "Mean Median Mode Imputer":
                        handle_strategy_3 = MeanMedianModeImputer(
                            strategy=df_handle_hyperparameter_cs3.iloc[0, 1],
                        )
                    elif handle_filter_cs3 == "Hot Deck Imputer":
                        handle_strategy_3 = HotDeckImputer(
                            group_by=None,
                            random_state=int(
                                df_handle_hyperparameter_cs3.iloc[0, 1],
                            ),
                        )
                    elif handle_filter_cs3 == "Regression Imputer":
                        handle_strategy_3 = RegressionImputer()
                    elif handle_filter_cs3 == "KNN Imputer Strategy":
                        handle_strategy_3 = KNNImputerStrategy(
                            n_neighbors=int(
                                df_handle_hyperparameter_cs3.iloc[0, 1],
                            ),
                            weights=df_handle_hyperparameter_cs3.iloc[1, 1],
                        )
                    processor_cs3 = CrossSectionMVProcessor(
                        impute_strategy=handle_strategy_3,
                    )
                    df_clean3 = processor_cs3.fit_transform(df_raw)

                if int(treatement_amount) >= 2:
                    if handle_filter_cs2 == "Likewise Deletion Strategy":
                        handle_strategy_2 = ListwiseDeletionStrategy()
                    elif handle_filter_cs2 == "Mean Median Mode Imputer":
                        handle_strategy_2 = MeanMedianModeImputer(
                            strategy=df_handle_hyperparameter_cs2.iloc[0, 1],
                        )
                    elif handle_filter_cs2 == "Hot Deck Imputer":
                        handle_strategy_2 = HotDeckImputer(
                            group_by=None,
                            random_state=int(
                                df_handle_hyperparameter_cs2.iloc[0, 1],
                            ),
                        )
                    elif handle_filter_cs2 == "Regression Imputer":
                        handle_strategy_2 = RegressionImputer()
                    elif handle_filter_cs2 == "KNN Imputer Strategy":
                        handle_strategy_2 = KNNImputerStrategy(
                            n_neighbors=int(
                                df_handle_hyperparameter_cs2.iloc[0, 1],
                            ),
                            weights=df_handle_hyperparameter_cs2.iloc[1, 1],
                        )
                    processor_cs2 = CrossSectionMVProcessor(
                        impute_strategy=handle_strategy_2,
                    )
                    df_clean2 = processor_cs2.fit_transform(df_raw)

                if int(treatement_amount) >= 1:
                    if handle_filter_cs1 == "Likewise Deletion Strategy":
                        handle_strategy_1 = ListwiseDeletionStrategy()
                    elif handle_filter_cs1 == "Mean Median Mode Imputer":
                        handle_strategy_1 = MeanMedianModeImputer(
                            strategy=df_handle_hyperparameter_cs1.iloc[0, 1],
                        )
                    elif handle_filter_cs1 == "Hot Deck Imputer":
                        handle_strategy_1 = HotDeckImputer(
                            group_by=None,
                            random_state=int(
                                df_handle_hyperparameter_cs1.iloc[0, 1],
                            ),
                        )
                    elif handle_filter_cs1 == "Regression Imputer":
                        handle_strategy_1 = RegressionImputer()
                    elif handle_filter_cs1 == "KNN Imputer Strategy":
                        handle_strategy_1 = KNNImputerStrategy(
                            n_neighbors=int(
                                df_handle_hyperparameter_cs1.iloc[0, 1],
                            ),
                            weights=df_handle_hyperparameter_cs1.iloc[1, 1],
                        )
                    processor_cs1 = CrossSectionMVProcessor(
                        impute_strategy=handle_strategy_1,
                    )
                    df_clean = processor_cs1.fit_transform(df_raw)

            # ---- TIMESERIES ----
            elif input_type_data == "Timeseries":
                if int(treatement_amount) == 3:
                    if handle_filter_ts3 == "LOCF Strategy":
                        handle_strategy_3 = LOCFStrategy()
                    elif handle_filter_ts3 == "NOCB Strategy":
                        handle_strategy_3 = NOCBStrategy()
                    elif handle_filter_ts3 == "Interpolation Strategy":
                        handle_strategy_3 = InterpolationStrategy(
                            method=df_handle_hyperparameter_ts3.iloc[0, 1],
                            order=int(df_handle_hyperparameter_ts3.iloc[1, 1]),
                            window_ma=int(
                                df_handle_hyperparameter_ts3.iloc[2, 1],
                            ),
                        )
                    elif handle_filter_ts3 == "Seasonal Imputer Strategy":
                        handle_strategy_3 = SeasonalImputerStrategy(
                            by=df_handle_hyperparameter_ts3.iloc[0, 1],
                        )
                    elif handle_filter_ts3 == "ARIMA Imputer Strategy":
                        handle_strategy_3 = ARIMAImputerStrategy(
                            order=(
                                int(df_handle_hyperparameter_ts3.iloc[0, 1]),
                                int(df_handle_hyperparameter_ts3.iloc[1, 1]),
                                int(df_handle_hyperparameter_ts3.iloc[2, 1]),
                            ),
                            seasonal_order=(
                                int(df_handle_hyperparameter_ts3.iloc[3, 1]),
                                int(df_handle_hyperparameter_ts3.iloc[4, 1]),
                                int(df_handle_hyperparameter_ts3.iloc[5, 1]),
                                int(df_handle_hyperparameter_ts3.iloc[6, 1]),
                            ),
                        )
                    elif handle_filter_ts3 == "Kalman Imputer Strategy":
                        handle_strategy_3 = KalmanImputerStrategy(
                            level=df_handle_hyperparameter_ts3.iloc[0, 1],
                            seasonal=int(
                                df_handle_hyperparameter_ts3.iloc[1, 1],
                            ),
                        )
                    elif handle_filter_ts3 == "Moving Average Imputer Strategy":
                        handle_strategy_3 = MovingAverageImputerStrategy(
                            k=int(df_handle_hyperparameter_ts3.iloc[0, 1]),
                            side=df_handle_hyperparameter_ts3.iloc[1, 1],
                        )
                    elif handle_filter_ts3 == "CAGR Imputer Strategy":
                        handle_strategy_3 = CAGRImputerStrategy(
                            min_positive=float(
                                df_handle_hyperparameter_ts3.iloc[0, 1],
                            ),
                        )
                    processor_ts3 = TimeSeriesMVProcessor(
                        impute_strategy=handle_strategy_3,
                        date_col=col_time,
                    )
                    df_clean3 = processor_ts3.fit_transform(df_raw)

                if int(treatement_amount) >= 2:
                    if handle_filter_ts2 == "LOCF Strategy":
                        handle_strategy_2 = LOCFStrategy()
                    elif handle_filter_ts2 == "NOCB Strategy":
                        handle_strategy_2 = NOCBStrategy()
                    elif handle_filter_ts2 == "Interpolation Strategy":
                        handle_strategy_2 = InterpolationStrategy(
                            method=df_handle_hyperparameter_ts2.iloc[0, 1],
                            order=int(df_handle_hyperparameter_ts2.iloc[1, 1]),
                            window_ma=int(
                                df_handle_hyperparameter_ts2.iloc[2, 1],
                            ),
                        )
                    elif handle_filter_ts2 == "Seasonal Imputer Strategy":
                        handle_strategy_2 = SeasonalImputerStrategy(
                            by=df_handle_hyperparameter_ts2.iloc[0, 1],
                        )
                    elif handle_filter_ts2 == "ARIMA Imputer Strategy":
                        handle_strategy_2 = ARIMAImputerStrategy(
                            order=(
                                int(df_handle_hyperparameter_ts2.iloc[0, 1]),
                                int(df_handle_hyperparameter_ts2.iloc[1, 1]),
                                int(df_handle_hyperparameter_ts2.iloc[2, 1]),
                            ),
                            seasonal_order=(
                                int(df_handle_hyperparameter_ts2.iloc[3, 1]),
                                int(df_handle_hyperparameter_ts2.iloc[4, 1]),
                                int(df_handle_hyperparameter_ts2.iloc[5, 1]),
                                int(df_handle_hyperparameter_ts2.iloc[6, 1]),
                            ),
                        )
                    elif handle_filter_ts2 == "Kalman Imputer Strategy":
                        handle_strategy_2 = KalmanImputerStrategy(
                            level=df_handle_hyperparameter_ts2.iloc[0, 1],
                            seasonal=int(
                                df_handle_hyperparameter_ts2.iloc[1, 1],
                            ),
                        )
                    elif handle_filter_ts2 == "Moving Average Imputer Strategy":
                        handle_strategy_2 = MovingAverageImputerStrategy(
                            k=int(df_handle_hyperparameter_ts2.iloc[0, 1]),
                            side=df_handle_hyperparameter_ts2.iloc[1, 1],
                        )
                    elif handle_filter_ts2 == "CAGR Imputer Strategy":
                        handle_strategy_2 = CAGRImputerStrategy(
                            min_positive=float(
                                df_handle_hyperparameter_ts2.iloc[0, 1],
                            ),
                        )
                    processor_ts2 = TimeSeriesMVProcessor(
                        impute_strategy=handle_strategy_2,
                        date_col=col_time,
                    )
                    df_clean2 = processor_ts2.fit_transform(df_raw)

                if int(treatement_amount) >= 1:
                    if handle_filter_ts1 == "LOCF Strategy":
                        handle_strategy_1 = LOCFStrategy()
                    elif handle_filter_ts1 == "NOCB Strategy":
                        handle_strategy_1 = NOCBStrategy()
                    elif handle_filter_ts1 == "Interpolation Strategy":
                        handle_strategy_1 = InterpolationStrategy(
                            method=df_handle_hyperparameter_ts1.iloc[0, 1],
                            order=int(df_handle_hyperparameter_ts1.iloc[1, 1]),
                            window_ma=int(
                                df_handle_hyperparameter_ts1.iloc[2, 1],
                            ),
                        )
                    elif handle_filter_ts1 == "Seasonal Imputer Strategy":
                        handle_strategy_1 = SeasonalImputerStrategy(
                            by=df_handle_hyperparameter_ts1.iloc[0, 1],
                        )
                    elif handle_filter_ts1 == "ARIMA Imputer Strategy":
                        handle_strategy_1 = ARIMAImputerStrategy(
                            order=(
                                int(df_handle_hyperparameter_ts1.iloc[0, 1]),
                                int(df_handle_hyperparameter_ts1.iloc[1, 1]),
                                int(df_handle_hyperparameter_ts1.iloc[2, 1]),
                            ),
                            seasonal_order=(
                                int(df_handle_hyperparameter_ts1.iloc[3, 1]),
                                int(df_handle_hyperparameter_ts1.iloc[4, 1]),
                                int(df_handle_hyperparameter_ts1.iloc[5, 1]),
                                int(df_handle_hyperparameter_ts1.iloc[6, 1]),
                            ),
                        )
                    elif handle_filter_ts1 == "Kalman Imputer Strategy":
                        handle_strategy_1 = KalmanImputerStrategy(
                            level=df_handle_hyperparameter_ts1.iloc[0, 1],
                            seasonal=int(
                                df_handle_hyperparameter_ts1.iloc[1, 1],
                            ),
                        )
                    elif handle_filter_ts1 == "Moving Average Imputer Strategy":
                        handle_strategy_1 = MovingAverageImputerStrategy(
                            k=int(df_handle_hyperparameter_ts1.iloc[0, 1]),
                            side=df_handle_hyperparameter_ts1.iloc[1, 1],
                        )
                    elif handle_filter_ts1 == "CAGR Imputer Strategy":
                        handle_strategy_1 = CAGRImputerStrategy(
                            min_positive=float(
                                df_handle_hyperparameter_ts1.iloc[0, 1],
                            ),
                        )
                    processor_ts1 = TimeSeriesMVProcessor(
                        impute_strategy=handle_strategy_1,
                        date_col=col_time,
                    )
                    df_clean = processor_ts1.fit_transform(df_raw)

            # ---- PANEL ----
            elif input_type_data == "Panel":
                if int(treatement_amount) == 3:
                    if handle_filter_panel3 == "Linear Strategy":
                        handle_strategy_3 = InterpolationStrategy("linear")
                    elif handle_filter_panel3 == "LOCF Panel Strategy":
                        handle_strategy_3 = LOCFStrategy()
                    elif handle_filter_panel3 == "MA Strategy":
                        handle_strategy_3 = MovingAverageImputerStrategy(
                            k=int(
                                df_handle_hyperparameter_panel3.iloc[0, 1],
                            ),
                            side=df_handle_hyperparameter_panel3.iloc[1, 1],
                        )
                    elif handle_filter_panel3 == "CAGR Strategy":
                        handle_strategy_3 = CAGRImputerStrategy(
                            min_positive=float(
                                df_handle_hyperparameter_panel3.iloc[0, 1],
                            ),
                        )
                    elif handle_filter_panel3 == "Kalman Strategy":
                        handle_strategy_3 = KalmanImputerStrategy(
                            level=df_handle_hyperparameter_panel3.iloc[0, 1],
                            seasonal=int(
                                df_handle_hyperparameter_panel3.iloc[1, 1],
                            ),
                        )
                    elif handle_filter_panel3 == "ARIMA Strategy":
                        handle_strategy_3 = ARIMAImputerStrategy(
                            order=(
                                int(df_handle_hyperparameter_panel3.iloc[0, 1]),
                                int(df_handle_hyperparameter_panel3.iloc[1, 1]),
                                int(df_handle_hyperparameter_panel3.iloc[2, 1]),
                            ),
                            seasonal_order=(
                                int(df_handle_hyperparameter_panel3.iloc[3, 1]),
                                int(df_handle_hyperparameter_panel3.iloc[4, 1]),
                                int(df_handle_hyperparameter_panel3.iloc[5, 1]),
                                int(df_handle_hyperparameter_panel3.iloc[6, 1]),
                            ),
                        )
                    processor_panel3 = PanelMVProcessor(
                        base_impute_strategy=handle_strategy_3,
                        group_by=category_filter,
                        date_col=col_time,
                        prefer_dayfirst=True,
                        sort_output="group_date",
                    )
                    df_clean3 = processor_panel3.fit_transform(df_raw)

                if int(treatement_amount) >= 2:
                    if handle_filter_panel2 == "Linear Strategy":
                        handle_strategy_2 = InterpolationStrategy("linear")
                    elif handle_filter_panel2 == "LOCF Panel Strategy":
                        handle_strategy_2 = LOCFStrategy()
                    elif handle_filter_panel2 == "MA Strategy":
                        handle_strategy_2 = MovingAverageImputerStrategy(
                            k=int(
                                df_handle_hyperparameter_panel2.iloc[0, 1],
                            ),
                            side=df_handle_hyperparameter_panel2.iloc[1, 1],
                        )
                    elif handle_filter_panel2 == "CAGR Strategy":
                        handle_strategy_2 = CAGRImputerStrategy(
                            min_positive=float(
                                df_handle_hyperparameter_panel2.iloc[0, 1],
                            ),
                        )
                    elif handle_filter_panel2 == "Kalman Strategy":
                        handle_strategy_2 = KalmanImputerStrategy(
                            level=df_handle_hyperparameter_panel2.iloc[0, 1],
                            seasonal=int(
                                df_handle_hyperparameter_panel2.iloc[1, 1],
                            ),
                        )
                    elif handle_filter_panel2 == "ARIMA Strategy":
                        handle_strategy_2 = ARIMAImputerStrategy(
                            order=(
                                int(df_handle_hyperparameter_panel2.iloc[0, 1]),
                                int(df_handle_hyperparameter_panel2.iloc[1, 1]),
                                int(df_handle_hyperparameter_panel2.iloc[2, 1]),
                            ),
                            seasonal_order=(
                                int(df_handle_hyperparameter_panel2.iloc[3, 1]),
                                int(df_handle_hyperparameter_panel2.iloc[4, 1]),
                                int(df_handle_hyperparameter_panel2.iloc[5, 1]),
                                int(df_handle_hyperparameter_panel2.iloc[6, 1]),
                            ),
                        )
                    processor_panel2 = PanelMVProcessor(
                        base_impute_strategy=handle_strategy_2,
                        group_by=category_filter,
                        date_col=col_time,
                        prefer_dayfirst=True,
                        sort_output="group_date",
                    )
                    df_clean2 = processor_panel2.fit_transform(df_raw)

                if int(treatement_amount) >= 1:
                    if handle_filter_panel1 == "Linear Strategy":
                        handle_strategy_1 = InterpolationStrategy("linear")
                    elif handle_filter_panel1 == "LOCF Panel Strategy":
                        handle_strategy_1 = LOCFStrategy()
                    elif handle_filter_panel1 == "MA Strategy":
                        handle_strategy_1 = MovingAverageImputerStrategy(
                            k=int(
                                df_handle_hyperparameter_panel1.iloc[0, 1],
                            ),
                            side=df_handle_hyperparameter_panel1.iloc[1, 1],
                        )
                    elif handle_filter_panel1 == "CAGR Strategy":
                        handle_strategy_1 = CAGRImputerStrategy(
                            min_positive=float(
                                df_handle_hyperparameter_panel1.iloc[0, 1],
                            ),
                        )
                    elif handle_filter_panel1 == "Kalman Strategy":
                        handle_strategy_1 = KalmanImputerStrategy(
                            level=df_handle_hyperparameter_panel1.iloc[0, 1],
                            seasonal=int(
                                df_handle_hyperparameter_panel1.iloc[1, 1],
                            ),
                        )
                    elif handle_filter_panel1 == "ARIMA Strategy":
                        handle_strategy_1 = ARIMAImputerStrategy(
                            order=(
                                int(df_handle_hyperparameter_panel1.iloc[0, 1]),
                                int(df_handle_hyperparameter_panel1.iloc[1, 1]),
                                int(df_handle_hyperparameter_panel1.iloc[2, 1]),
                            ),
                            seasonal_order=(
                                int(df_handle_hyperparameter_panel1.iloc[3, 1]),
                                int(df_handle_hyperparameter_panel1.iloc[4, 1]),
                                int(df_handle_hyperparameter_panel1.iloc[5, 1]),
                                int(df_handle_hyperparameter_panel1.iloc[6, 1]),
                            ),
                        )
                    processor_panel1 = PanelMVProcessor(
                        base_impute_strategy=handle_strategy_1,
                        group_by=category_filter,
                        date_col=col_time,
                        prefer_dayfirst=True,
                        sort_output="group_date",
                    )
                    df_clean = processor_panel1.fit_transform(df_raw)

        # ==========================================================
        # OUTLIER HANDLING
        # ==========================================================
        elif task_type == "outlier":
            detection_filter = argument_1
            df_detection_hyperparameter = argument_2
            detection_filter2 = argument_3
            df_detection_hyperparameter2 = argument_4
            detection_filter3 = argument_5
            df_detection_hyperparameter3 = argument_6
            handle_filter = argument_7
            df_handle_hyperparameter = argument_8
            handle_filter2 = argument_9
            df_handle_hyperparameter2 = argument_10
            handle_filter3 = argument_11
            df_handle_hyperparameter3 = argument_12

            # ---- DETECTION STRATEGIES ----
            if detection_filter == "Interquartile Range":
                detection_strategy = IQRStrategy(
                    factor=float(df_detection_hyperparameter.iloc[0, 1]),
                )
            elif detection_filter == "Z Score":
                detection_strategy = ZScoreStrategy(
                    factor=float(df_detection_hyperparameter.iloc[0, 1]),
                )
            elif detection_filter == "Rolling IQR":
                detection_strategy = RollingIQRStrategy(
                    factor=float(df_detection_hyperparameter.iloc[0, 1]),
                    window=int(df_detection_hyperparameter.iloc[1, 1]),
                )

            if detection_filter2 == "Interquartile Range":
                detection_strategy2 = IQRStrategy(
                    factor=float(df_detection_hyperparameter2.iloc[0, 1]),
                )
            elif detection_filter2 == "Z Score":
                detection_strategy2 = ZScoreStrategy(
                    factor=float(df_detection_hyperparameter2.iloc[0, 1]),
                )
            elif detection_filter2 == "Rolling IQR":
                detection_strategy2 = RollingIQRStrategy(
                    factor=float(df_detection_hyperparameter2.iloc[0, 1]),
                    window=int(df_detection_hyperparameter2.iloc[1, 1]),
                )

            if detection_filter3 == "Interquartile Range":
                detection_strategy3 = IQRStrategy(
                    factor=float(df_detection_hyperparameter3.iloc[0, 1]),
                )
            elif detection_filter3 == "Z Score":
                detection_strategy3 = ZScoreStrategy(
                    factor=float(df_detection_hyperparameter3.iloc[0, 1]),
                )
            elif detection_filter3 == "Rolling IQR":
                detection_strategy3 = RollingIQRStrategy(
                    factor=float(df_detection_hyperparameter3.iloc[0, 1]),
                    window=int(df_detection_hyperparameter3.iloc[1, 1]),
                )

            # ---- HANDLING STRATEGIES ----
            if handle_filter == "Capping Strategy":
                handling_strategy = CappingStrategy()
            elif handle_filter == "Imputation Strategy":
                handling_strategy = ImputationStrategy(
                    strategy=str(df_handle_hyperparameter.iloc[0, 1]),
                )
            elif handle_filter == "LOCF Strategy":
                if input_type_data == "Panel":
                    handling_strategy = LOCFStrategy_CS(groupby=category_filter)
                else:
                    handling_strategy = LOCFStrategy_CS()

            if handle_filter2 == "Capping Strategy":
                handling_strategy2 = CappingStrategy()
            elif handle_filter2 == "Imputation Strategy":
                handling_strategy2 = ImputationStrategy(
                    strategy=str(df_handle_hyperparameter2.iloc[0, 1]),
                )
            elif handle_filter2 == "LOCF Strategy":
                if input_type_data == "Panel":
                    handling_strategy2 = LOCFStrategy_CS(groupby=category_filter)
                else:
                    handling_strategy2 = LOCFStrategy_CS()

            if handle_filter3 == "Capping Strategy":
                handling_strategy3 = CappingStrategy()
            elif handle_filter3 == "Imputation Strategy":
                handling_strategy3 = ImputationStrategy(
                    strategy=str(df_handle_hyperparameter3.iloc[0, 1]),
                )
            elif handle_filter3 == "LOCF Strategy":
                if input_type_data == "Panel":
                    handling_strategy3 = LOCFStrategy_CS(groupby=category_filter)
                else:
                    handling_strategy3 = LOCFStrategy_CS()

            # ---- APPLY PROCESSORS ----
            if input_type_data == "Cross Sectional Data":
                processor_cs = CrossSectionalProcessor(
                    detection_strategy,
                    handling_strategy,
                )
                processor_cs2 = CrossSectionalProcessor(
                    detection_strategy2,
                    handling_strategy2,
                )
                processor_cs3 = CrossSectionalProcessor(
                    detection_strategy3,
                    handling_strategy3,
                )
                df_clean = processor_cs.fit_transform(df_clean)
                df_clean2 = processor_cs2.fit_transform(df_clean2)
                df_clean3 = processor_cs3.fit_transform(df_clean3)

            elif input_type_data == "Panel":
                processor_panel = PanelProcessor(
                    detection_strategy,
                    handling_strategy,
                    group_by_col=category_filter,
                    date_col=col_time,
                )
                processor_panel2 = PanelProcessor(
                    detection_strategy2,
                    handling_strategy2,
                    group_by_col=category_filter,
                    date_col=col_time,
                )
                processor_panel3 = PanelProcessor(
                    detection_strategy3,
                    handling_strategy3,
                    group_by_col=category_filter,
                    date_col=col_time,
                )
                df_clean = processor_panel.fit_transform(df_clean)
                df_clean2 = processor_panel2.fit_transform(df_clean2)
                df_clean3 = processor_panel3.fit_transform(df_clean3)

            elif input_type_data == "Timeseries":
                processor_ts = TimeSeriesProcessor(
                    detection_strategy,
                    handling_strategy,
                    date_col=col_time,
                )
                processor_ts2 = TimeSeriesProcessor(
                    detection_strategy2,
                    handling_strategy2,
                    date_col=col_time,
                )
                processor_ts3 = TimeSeriesProcessor(
                    detection_strategy3,
                    handling_strategy3,
                    date_col=col_time,
                )
                df_clean = processor_ts.fit_transform(df_clean)
                df_clean2 = processor_ts2.fit_transform(df_clean2)
                df_clean3 = processor_ts3.fit_transform(df_clean3)

        # ==========================================================
        # POST-PROCESSING & OUTPUT PACKAGING
        # ==========================================================
        df_raw_ori = self.round_float_columns(df_raw_ori)
        df_raw = self.round_float_columns(df_raw)
        df_clean = self.round_float_columns(df_clean)
        df_clean2 = self.round_float_columns(df_clean2)
        df_clean3 = self.round_float_columns(df_clean3)

        if input_type_data == "Panel":
            df_raw = df_raw.sort_values(by=[category_filter, col_time])
            df_clean = df_clean.sort_values(by=[category_filter, col_time])
            try:
                df_clean2 = df_clean2.sort_values(
                    by=[category_filter, col_time],
                )
            except Exception:
                print("Pembanding cuma 1")
            try:
                df_clean3 = df_clean3.sort_values(
                    by=[category_filter, col_time],
                )
            except Exception:
                print("Pembanding cuma 2")

        elif input_type_data == "Timeseries":
            df_raw = df_raw.sort_values(by=[col_time])
            df_clean = df_clean.sort_values(by=[col_time])
            try:
                df_clean2 = df_clean2.sort_values(by=[col_time])
            except Exception:
                print("Pembanding cuma 1")
            try:
                df_clean3 = df_clean3.sort_values(by=[col_time])
            except Exception:
                print("Pembanding cuma 2")

        strategy_plot_output = None
        viz = None
        stats = None
        interpretation = None
        summary_stats_before = pd.DataFrame()
        df_signifikansi_test = pd.DataFrame()

        if treatement_amount == "1":
            df_raw = self.combine_into_a_dataframe(df_raw, [df_clean])
        elif treatement_amount == "2":
            df_raw = self.combine_into_a_dataframe(
                df_raw,
                [df_clean, df_clean2],
            )
        elif treatement_amount == "3":
            df_raw = self.combine_into_a_dataframe(
                df_raw,
                [df_clean, df_clean2, df_clean3],
            )

        try:
            df_raw[col_time] = (
                df_raw[col_time]
                .dt.strftime("%m/%d/%Y")
                .str.replace(r"^0|(?<=/)0", "", regex=True)
            )
        except Exception:
            pass

        if input_type_data == "Panel":
            df_raw = self.rearrange_dataframe_first(
                df_raw,
                [category_filter, col_time],
            )
            return (
                gr.Dataframe(value=df_raw, pinned_columns=2),
                viz,
                stats,
                interpretation,
                gr.update(
                    choices=df_clean.select_dtypes(include=np.number)
                    .columns.tolist(),
                    value=None,
                ),
                gr.update(
                    visible=True,
                    choices=df_raw_ori[category_filter].unique().tolist(),
                    value=None,
                ),
                strategy_plot_output,
                summary_stats_before,
                df_signifikansi_test,
                df_clean,
                df_clean2,
                df_clean3,
                df_raw_ori,
            )

        if input_type_data == "Timeseries":
            df_raw = self.rearrange_dataframe_first(df_raw, [col_time])
            return (
                gr.Dataframe(value=df_raw, pinned_columns=1),
                viz,
                stats,
                interpretation,
                gr.update(
                    choices=df_clean.select_dtypes(include=np.number)
                    .columns.tolist(),
                    value=None,
                ),
                gr.update(visible=False, choices=[], value=None),
                strategy_plot_output,
                summary_stats_before,
                df_signifikansi_test,
                df_clean,
                df_clean2,
                df_clean3,
                df_raw_ori,
            )

        return (
            df_raw,
            viz,
            stats,
            interpretation,
            gr.update(
                choices=df_clean.select_dtypes(include=np.number)
                .columns.tolist(),
                value=None,
            ),
            gr.update(visible=False, choices=[], value=None),
            strategy_plot_output,
            summary_stats_before,
            df_signifikansi_test,
            df_clean,
            df_clean2,
            df_clean3,
            df_raw_ori,
        )


# ===================================================================
# Ringkasan Hasil / Summary Update (evaluation_function)
# ===================================================================
# - Menambahkan metadata notebook-style di bagian atas modul.
# - Menstandarkan blok import (Standard / Data / Visualization / Project).
# - Menambah docstring singkat untuk semua method & fungsi helper.
# - Merapikan struktur run_data_formatting_process tanpa mengubah logika,
#   termasuk grouping missing value vs outlier dan post-processing output.