
import os
import re
from pathlib import Path


from qgis.PyQt import uic, QtWidgets
from qgis.PyQt.QtWidgets import QApplication, QDialog, QMessageBox, QWidget


import numpy as np
import pandas as pd
import geopandas as gpd
from shapely.geometry.base import BaseGeometry


import common
from urbanq.logging.logging_config import logger
from urbanq.function.qss import gradient_style, default_style


from urbanq.function.file import (
    export_gdf,
    keep_columns_gdf,
    load_geojson_gdf,
    load_txt_or_csv_df,
    load_json_df_or_gdf,
    load_layer_or_shp_gdf,
    update_shapefile_layer,
    df_to_empty_geometry_gdf,
)

from urbanq.function.widgetutils import (
    show_progress,
    update_progress,
)

from urbanq.function.geo import (
    normalize_null_values,
)


from urbanq.menu.autoUI.fileRread_dockwidget import fileRreadDockWidget
from urbanq.menu.autoUI.fileSave_dockwidget import fileSaveDockWidget
from urbanq.menu.autoUI.fileSetting_dockwidget import fileSettingDockWidget
from urbanq.menu.autoUI.ImageDescription_dockwidget import ImageDescriptionDockWidget



FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'NumericDataProcessing_dockwidget_base.ui'))


class NumericDataProcessingDockWidget(QDialog, FORM_CLASS):  
    def __init__(self, parent=None):
        
        super(NumericDataProcessingDockWidget, self).__init__(parent)  
        
        
        
        
        
        self.setupUi(self)

        
        show_progress(self.progressBar, False)

        
        self.menuPushButton.setProperty("class", "boldText")
        self.nextStepPushButton.setProperty("class", "boldText")
        self.previousStepPushButton.setProperty("class", "boldText")

        
        self.menuPushButton.clicked.connect(self.go_back_to_data_conversion)

        
        self.nextStepPushButton.clicked.connect(lambda: self.next_previous_clicked(1))
        self.nextStepPushButton.clicked.connect(lambda: self.update_current_progress(self.stackedWidget.currentIndex()))
        self.nextStepPushButton.clicked.connect(lambda: self.load_menu_ui(self.stackedWidget.currentIndex()))

        
        self.previousStepPushButton.clicked.connect(lambda: self.next_previous_clicked(-1))
        self.previousStepPushButton.clicked.connect(lambda: self.update_current_progress(self.stackedWidget.currentIndex()))
        self.previousStepPushButton.clicked.connect(lambda: self.load_menu_ui(self.stackedWidget.currentIndex()))

        
        self.job_index = common.job_info.get("job_index") if common.job_info else None
        self.job_title = common.job_info.get("job_title") if common.job_info else None

        
        self.option = self.get_widget_option(self.job_index, self.job_title)

        
        self.pages_and_files = self.configure_pages_and_files()

        
        self.update_current_progress(0)

        
        self.stackedWidget.setCurrentIndex(0)

        
        self.load_menu_ui(0)

    
    
    

    def configure_pages_and_files(self):
        
        try:
            pages = []

            
            pages.append((True, self.current_step_1, ImageDescriptionDockWidget, None, None))

            
            pages.append((True, self.current_step_2, fileRreadDockWidget, self.option, None))

            
            read_required = any([
                self.option["setting_by_text"],
                self.option["setting_by_array"],
                self.option["setting_by_expression"],
                self.option["setting_by_section"]["enabled"],
                self.option["setting_by_numeric"]["enabled"],
                self.option["setting_by_combo"]["enabled"],
            ])
            pages.append((read_required, self.current_step_3, fileSettingDockWidget, self.option, None))

            
            save_required = any([
                self.option["output_by_file"],
                self.option["output_by_field"],
                self.option["output_by_table"]
            ])
            pages.append((save_required, self.current_step_4, fileSaveDockWidget, self.option, None))

            return pages

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    def go_back_to_data_conversion(self):
        
        try:
            from urbanq.menu.dataConversion.dataConversion_dockwidget import dataConversionDockWidget  
            parent_ui = dataConversionDockWidget(self)  
            main_page_layout = self.parent().parent().findChild(QWidget, "page_dataConversion").layout()
            if main_page_layout:
                
                for i in reversed(range(main_page_layout.count())):
                    main_page_layout.itemAt(i).widget().deleteLater()
                main_page_layout.addWidget(parent_ui)

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    def load_menu_ui(self, index):
        
        try:
            widget_enabled, widget_process, widget_class, widget_option, widget_instance = self.pages_and_files[index]
            page = self.stackedWidget.widget(index)

            
            if widget_instance is None:

                
                widget_instance = widget_class(self, self.option)
                page.layout().addWidget(widget_instance)
                self.pages_and_files[index] = (
                    self.pages_and_files[index][0],
                    self.pages_and_files[index][1],
                    self.pages_and_files[index][2],
                    self.pages_and_files[index][3],
                    widget_instance
                )

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    def update_current_progress(self, index):
        
        try:
            step = 1
            for i, (widget_enabled, widget_process, _, _, _) in enumerate(self.pages_and_files):
                if not widget_enabled:
                    widget_process.hide()
                    continue
                else:
                    updated_text = re.sub(r"\[\d+단계\]", f"[{step}단계]", widget_process.text())
                    widget_process.setText(updated_text)
                    step += 1

                
                widget_process.show()

                if i == index:
                    widget_process.setStyleSheet(gradient_style)
                else:
                    widget_process.setStyleSheet(default_style)

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    def get_safe_page_index(self, current_index: int, direction: int) -> int:
        
        try:
            new_index = current_index

            while True:
                
                new_index += direction

                
                new_index = max(0, min(new_index, len(self.pages_and_files) - 1))

                
                if self.pages_and_files[new_index][0]:
                    return new_index

                
                if new_index == 0 and direction == -1:
                    return current_index

                
                if new_index == len(self.pages_and_files) - 1 and direction == 1:
                    return current_index

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    def next_previous_clicked(self, direction):
        
        def get_last_valid_page_index(pages_and_files) -> int:
            
            for i in reversed(range(len(pages_and_files))):
                if pages_and_files[i][0]:
                    return i
            return -1  

        try:
            
            current_index = self.stackedWidget.currentIndex()

            
            if self.pages_and_files[current_index][0]:
                instance = self.pages_and_files[current_index][4]
                if direction > 0 and not instance.set_fileResults():
                    return

            
            new_index = self.get_safe_page_index(current_index, direction)

            
            last_page_index = get_last_valid_page_index(self.pages_and_files)

            
            self.nextStepPushButton.setText("실행하기 " if new_index == last_page_index else "다음 단계 ▶")

            
            self.stackedWidget.setCurrentIndex(new_index)

            
            if current_index == last_page_index and direction > 0:
                self.run_job_process()

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    
    
    

    def get_file_data_frame(self, source_file_type, source_file_path, file_path, file_encoding, file_delimiter, file_has_header):
        
        try:
            
            gdf = None

            
            if source_file_type == "shp":
                gdf = load_layer_or_shp_gdf(shp_path=file_path, file_encoding=file_encoding)

            
            elif source_file_type == "layer":
                qgs_project_layer = source_file_path
                gdf = load_layer_or_shp_gdf(layer=qgs_project_layer, file_encoding=file_encoding)

            
            elif source_file_type == "json":
                df, _ = load_json_df_or_gdf(file_path=file_path, file_encoding=file_encoding)
                gdf = df_to_empty_geometry_gdf(df)

            
            elif source_file_type == "geojson":
                gdf = load_geojson_gdf(file_path=file_path, file_encoding=file_encoding)

            
            elif source_file_type == "txt":
                df = load_txt_or_csv_df(file_path, file_encoding, file_delimiter, file_has_header)
                gdf = df_to_empty_geometry_gdf(df)

            
            elif source_file_type == "csv":
                df = load_txt_or_csv_df(file_path, file_encoding, file_delimiter, file_has_header)
                gdf = df_to_empty_geometry_gdf(df)

            
            elif source_file_type == "folder":
                df = load_txt_or_csv_df(file_path, file_encoding, file_delimiter, file_has_header)
                gdf = df_to_empty_geometry_gdf(df)

            if gdf is None:
                return

            return gdf

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    def run_job_process(self):
        
        try:
            
            show_progress(self.progressBar)

            
            total_files = len(common.fileInfo_1.file_preview)  
            steps_per_file = 4  
            total_steps = total_files * steps_per_file  
            base_progress = 20  
            step_weight = (100 - base_progress) / total_steps  
            current_step = 0  

            
            source_file_type, source_file_path, _ = common.fileInfo_1.file_record.get_record()
            result_file_type, result_file_path, _ = common.fileInfo_1.result_record.get_record()

            
            status_flags = []  
            for index, file_preview in enumerate(common.fileInfo_1.file_preview):

                
                file_path, file_encoding, file_delimiter, file_has_header = file_preview.get_info()
                current_step += 1
                update_progress(self.progressBar, int(base_progress + current_step * step_weight))

                
                if source_file_type == "folder":
                    
                    file_name_with_ext = os.path.basename(file_path)
                    new_file_path = os.path.join(result_file_path, file_name_with_ext)
                elif result_file_type == "layer":
                    new_file_path = file_path
                else:
                    new_file_path = result_file_path

                
                gdf = self.get_file_data_frame(source_file_type, source_file_path, file_path, file_encoding, file_delimiter, file_has_header)
                current_step += 1
                update_progress(self.progressBar, int(base_progress + current_step * step_weight))

                
                result = self.run_job_by_index(gdf, index)
                current_step += 1
                update_progress(self.progressBar, int(base_progress + current_step * step_weight))

                
                if result is None:
                    status_flags.append(False)
                    break
                elif result is True:
                    
                    
                    status_flags.append(True)

                try:
                    
                    if result_file_type == 'layer':

                        
                        layer_widget = self.pages_and_files[1][4].get_qgs_layer_widget()

                        
                        layer_widget_index = layer_widget.currentIndex()

                        
                        layer = source_file_path

                        
                        new_layer = update_shapefile_layer(layer, result)

                        
                        if 0 <= layer_widget_index < layer_widget.count():
                            layer_widget.setCurrentIndex(layer_widget_index)

                        
                        common.fileInfo_1.file_record.file_path[result_file_type] = new_layer

                        
                        status_flags.append(True)

                    else:
                        
                        if new_file_path:

                            
                            if isinstance(result, gpd.GeoDataFrame):
                                export_success = export_gdf(result, new_file_path)

                                
                                status_flags.append(export_success)

                            elif isinstance(result, list) and result:
                                
                                file_type, _, file_name = common.fileInfo_1.file_record.get_record()
                                base_dir = Path(new_file_path)
                                base_name = Path(file_name).stem
                                ext = f".{file_type}"

                                
                                export_success = []
                                for i, part in enumerate(result, start=1):
                                    output_path = base_dir / f"{base_name}_{i:03d}{ext}"
                                    export_success.append(export_gdf(part, output_path))

                                
                                status_flags.append(all(export_success))

                            else:
                                
                                QMessageBox.information(self, "파일 오류", "파일 저장 중 오류가 발생했습니다.", QMessageBox.Ok)
                                status_flags.append(False)

                except Exception as e:
                    
                    QMessageBox.information(self, "파일 오류", f"GeoDataFrame export 실패: {e}", QMessageBox.Ok)
                    status_flags.append(False)

                
                current_step += 1
                update_progress(self.progressBar, int(base_progress + current_step * step_weight))

            
            if status_flags and all(status_flags):
                update_progress(self.progressBar, 100)  
                QMessageBox.information(self, "알림", "축하합니다. 작업이 완료했습니다!", QMessageBox.Ok)

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

        finally:
            show_progress(self.progressBar, False)

    
    
    

    
    def calculate_numeric_statistics(self, gdf, field_name):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            try:
                
                
                
                
                gdf_copy_target = pd.to_numeric(gdf_copy[field_name], errors="coerce").fillna(0).astype(float)

            except Exception as e:
                QMessageBox.information(self, "작업 오류", "해당 필드를 숫자 형식으로 변환할 수 없습니다.\n문자 또는 특수문자가 포함되어 있는지 확인해 주세요.", QMessageBox.Ok)
                logger.error("에러 발생: %s", e, exc_info=True)
                return None

            
            col_data = gdf_copy_target
            stats = {
                "전체 레코드 수": len(col_data),
                "유효한 값 개수": col_data.count(),
                "평균": round(col_data.mean(), 4),
                "합계": round(col_data.sum(), 4),
                "최대값": round(col_data.max(), 4),
                "최소값": round(col_data.min(), 4)
            }

            
            header = ["통계 항목", "값"]
            rows = [[k, v] for k, v in stats.items()]

            
            common.fileInfo_1.result_table = {
                "header": header,
                "rows": rows,
                "msg": f"'{field_name}' 컬럼 통계 분석 완료"
            }
            common.signals.file_preview_updated.emit()

            return True

        except ValueError as ve:
            QMessageBox.warning(self, "입력 오류", str(ve), QMessageBox.Ok)
            logger.error(f"숫자 통계 계산 오류: {ve}", exc_info=True)
            return False

        except Exception as e:
            QMessageBox.critical(self, "처리 오류", f"통계 계산 중 오류 발생: {str(e)}", QMessageBox.Ok)
            logger.error(f"숫자 통계 계산 오류: {e}", exc_info=True)
            return False

    
    def calculate_group_statistics(self, gdf, group_field, target_field):
        
        try:
            
            geometry_col = gdf.geometry.name

            
            gdf_copy = gdf.copy()

            
            gdf_copy_no_geom = gdf_copy.drop(columns=geometry_col, errors='ignore')

            
            gdf_copy_cleaned = normalize_null_values(gdf_copy_no_geom)

            
            try:
                
                
                
                
                gdf_copy_cleaned[target_field] = pd.to_numeric(gdf_copy_cleaned[target_field], errors="coerce").fillna(0).astype(float)

            except Exception as e:
                QMessageBox.information(self, "작업 오류", f"'{target_field}' 필드를 숫자 형식으로 변환할 수 없습니다.\n문자 또는 특수문자가 포함되어 있는지 확인해 주세요.", QMessageBox.Ok)
                logger.error("에러 발생: %s", e, exc_info=True)
                return None

            
            grouped = gdf_copy_cleaned.groupby(group_field)[target_field].agg(['count', 'sum', 'mean', 'max', 'min']).reset_index()

            
            grouped['sum'] = grouped['sum'].round(3)
            grouped['mean'] = grouped['mean'].round(3)
            grouped['max'] = grouped['max'].round(3)
            grouped['min'] = grouped['min'].round(3)

            
            valid_group_field_count = gdf_copy_cleaned[group_field].dropna().shape[0]

            
            grouped.insert(0, '기준 필드명', group_field)
            grouped.insert(1, '기준 필드 개수', valid_group_field_count)
            grouped.rename(columns={
                group_field: '그룹명',
                'count': '그룹 개수 (count)',
                'sum': '그룹 합계 (sum)',
                'mean': '그룹 평균 (mean)',
                'max': '그룹 최대값',
                'min': '그룹 최소값'
            }, inplace=True)

            
            header = [
                '기준 필드명',
                '기준 필드 개수',
                '그룹명',
                '그룹 개수 (count)',
                '그룹 합계 (sum)',
                '그룹 평균 (mean)',
                '그룹 최대값',
                '그룹 최소값'
            ]

            
            rows = grouped[header].values.tolist()
            common.fileInfo_1.result_table["header"] = header
            common.fileInfo_1.result_table["rows"] = rows
            common.fileInfo_1.result_table["msg"] = (
                f"총 그룹 수: {len(grouped)},  기준 필드: '{group_field}',  통계 필드: '{target_field}'"
            )

            
            common.signals.file_preview_updated.emit()

            return True

        except Exception as e:
            QMessageBox.information(self, "작업 오류", "파일 작업 중 오류가 발생하였습니다.", QMessageBox.Ok)
            logger.error("에러 발생: %s", e, exc_info=True)
            return False

    
    def calculate_field_ranking(self, gdf, field_name, new_field_name='순위'):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            try:
                
                
                
                
                gdf_copy[field_name] = pd.to_numeric(gdf_copy[field_name], errors="coerce").fillna(0).astype(float)

            except Exception as e:
                QMessageBox.information(self, "작업 오류", "해당 필드를 숫자 형식으로 변환할 수 없습니다.\n문자 또는 특수문자가 포함되어 있는지 확인해 주세요.", QMessageBox.Ok)
                logger.error("에러 발생: %s", e, exc_info=True)
                return None

            
            
            df_sorted = gdf_copy.sort_values(by=[field_name], ascending=False, kind="mergesort")
            df_sorted[new_field_name] = range(1, len(df_sorted) + 1)

            
            
            if new_field_name in gdf.columns:
                gdf = gdf.drop(columns=[new_field_name])

            
            geometry_col = gdf.geometry.name
            gdf = gdf.merge(
                df_sorted[[geometry_col, new_field_name]],
                on=geometry_col,
                how='left'
            )

            return gdf

        except Exception as e:
            QMessageBox.information(self, "작업 오류", "파일 작업 중 오류가 발생하였습니다.", QMessageBox.Ok)
            logger.error("필드 랭킹 계산 에러: %s", e, exc_info=True)
            return False

    
    def calculate_field_ratio(self, gdf, field_A, field_B, new_field_name='비율', percentage=True, round_decimal=2):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            try:
                gdf_copy[field_A] = pd.to_numeric(gdf_copy[field_A], errors="coerce").fillna(0).astype(float)
                gdf_copy[field_B] = pd.to_numeric(gdf_copy[field_B], errors="coerce").fillna(0).astype(float)

            except Exception as e:
                QMessageBox.information(self, "작업 오류", "해당 필드를 숫자 형식으로 변환할 수 없습니다.\n문자 또는 특수문자가 포함되어 있는지 확인해 주세요.", QMessageBox.Ok)
                logger.error("에러 발생: %s", e, exc_info=True)
                return None

            
            gdf_copy[new_field_name] = gdf_copy.apply(
                lambda row: row[field_A] / row[field_B] if row[field_B] != 0 else float('nan'),
                axis=1
            )

            
            if percentage:
                gdf_copy[new_field_name] = gdf_copy[new_field_name]
            gdf_copy[new_field_name] = gdf_copy[new_field_name].round(round_decimal)

            
            
            if new_field_name in gdf.columns:
                gdf = gdf.drop(columns=[new_field_name])

            
            geometry_col = gdf.geometry.name
            result_gdf = gdf.merge(
                gdf_copy[[geometry_col, new_field_name]],
                on=geometry_col,
                how='left'
            )

            return result_gdf

        except Exception as e:
            QMessageBox.information(self, "작업 오류", "비율 계 중 오류가 발생하였습니다.", QMessageBox.Ok)
            logger.error("비율 계산 에러: %s", e, exc_info=True)
            return False

    
    def extract_records_by_range(self, gdf, field_name, min_value, max_value):
        
        try:
            
            if min_value > max_value:
                QMessageBox.information(self, "작업 오류", "최소값은 최대값보다 작거나 같아야 합니다.", QMessageBox.Ok)
                return False

            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            try:
                
                
                
                
                series = pd.to_numeric(gdf_copy[field_name], errors="coerce").fillna(0).astype(float)

            except Exception as e:
                QMessageBox.information(self, "작업 오류", "해당 필드를 숫자 형식으로 변환할 수 없습니다.\n문자 또는 특수문자가 포함되어 있는지 확인해 주세요.", QMessageBox.Ok)
                logger.error("에러 발생: %s", e, exc_info=True)
                return None

            
            if pd.api.types.is_float_dtype(series) and (series.dropna() % 1 == 0).all():
                gdf_copy[field_name] = series.astype("Int64")
            else:
                gdf_copy[field_name] = series

            
            mask = (gdf_copy[field_name] >= min_value) & (gdf_copy[field_name] <= max_value)
            df_filtered = gdf_copy.loc[mask]

            
            geometry_col = gdf.geometry.name
            result_gdf = gpd.GeoDataFrame(df_filtered, geometry=geometry_col, crs=gdf.crs)

            return result_gdf

        except ValueError as ve:
            QMessageBox.information(self, "작업 오류", "입력값 오류가 발생하였습니다.", QMessageBox.Ok)
            logger.error("입력값 오류: %s", ve, exc_info=True)
            return False

        except Exception as e:
            QMessageBox.information(self, "작업 오류", "레코드 추출 에러가 발생하였습니다.", QMessageBox.Ok)
            logger.error("레코드 추출 에러: %s", e, exc_info=True)
            return False

    
    def normalize_field_to_range(self, gdf, field_name, section_min, section_max, result_field):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            try:
                
                
                
                
                gdf_copy_target = pd.to_numeric(gdf_copy[field_name], errors="coerce").fillna(0).astype(float)

            except Exception as e:
                QMessageBox.information(self, "작업 오류", "해당 필드를 숫자 형식으로 변환할 수 없습니다.\n문자 또는 특수문자가 포함되어 있는지 확인해 주세요.",
                                        QMessageBox.Ok)
                logger.error("에러 발생: %s", e, exc_info=True)
                return None

            
            valid_values = gdf_copy_target.dropna()
            if valid_values.empty:
                QMessageBox.information(self, "정규화 불가", "숫자값이 존재하지 않아 정규화를 수행할 수 없습니다.", QMessageBox.Ok)
                return None

            
            min_val = valid_values.min()
            max_val = valid_values.max()

            if min_val == max_val:
                QMessageBox.information(self, "정규화 불가", "모든 값이 동일하여 정규화를 수행할 수 없습니다.", QMessageBox.Ok)
                return None

            
            series = ((gdf_copy_target - min_val) / (max_val - min_val)) * (section_max - section_min) + section_min

            
            if pd.api.types.is_float_dtype(series) and (series.dropna() % 1 == 0).all():
                gdf_copy[result_field] = series.astype("Int64")
            else:
                gdf_copy[result_field] = series

            return gdf_copy

        except Exception as e:
            QMessageBox.information(self, "작업 오류", "파일 작업 중 오류가 발생하였습니다.", QMessageBox.Ok)
            logger.error("에러 발생: %s", e, exc_info=True)
            return None

    
    def clip_field_to_fixed_range(self, gdf, field_name, clip_min, clip_max, result_field):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            try:
                
                
                
                
                gdf_copy_target = pd.to_numeric(gdf_copy[field_name], errors="coerce").fillna(0).astype(float)

            except Exception as e:
                QMessageBox.information(self, "작업 오류", "해당 필드를 숫자 형식으로 변환할 수 없습니다.\n문자 또는 특수문자가 포함되어 있는지 확인해 주세요.", QMessageBox.Ok)
                logger.error("에러 발생: %s", e, exc_info=True)
                return None

            
            gdf_copy_target = gdf_copy_target.fillna(clip_min)

            
            series = gdf_copy_target.clip(lower=clip_min, upper=clip_max)

            
            if pd.api.types.is_float_dtype(series) and (series.dropna() % 1 == 0).all():
                gdf_copy[result_field] = series.astype("Int64")
            else:
                gdf_copy[result_field] = series

            return gdf_copy

        except Exception as e:
            QMessageBox.information(self, "작업 오류", "파일 작업 중 오류가 발생하였습니다.", QMessageBox.Ok)
            logger.error("에러 발생: %s", e, exc_info=True)
            return None

    
    
    

    @staticmethod
    def get_widget_option(job_index, job_title):
        
        try:
            option = None  
            job_title = job_title[2:]

            if job_index == 0:
                option = {
                    "apply_basic_qss": True,

                    "disable_file_type_layer": True,
                    "disable_file_type_shp": True,
                    "disable_file_type_json": True,
                    "disable_file_type_txtcsv": True,
                    "disable_file_type_fold": False,

                    "show_uid_in_file": False,
                    "show_tuid_in_file": True,
                    "show_field_in_file": False,

                    "setting_by_text": False,
                    "setting_by_array": False,
                    "setting_by_expression": False,
                    "setting_by_section": {"enabled": False, "value_type": "int"},
                    "setting_by_numeric": {"enabled": False, "value_type": "int"},
                    "setting_by_combo": {"enabled": False, "items": []},

                    "output_by_file": False,
                    "output_by_field": False,
                    "output_by_table": True,

                    "FILE_TUID": [
                        '통계 계산 기준 필드 선택',
                        '필드 선택: ',
                        '통계 계산(개수, 평균, 합계 등)을 위해 숫자 유형이 아닌 필드는 자동으로 숫자형으로 변환됩니다.'
                    ],

                    "RESULT_TABLE": [
                        f'{job_title} 결과 테이블',
                        ''
                    ]
                }
            if job_index == 1:
                option = {
                    "apply_basic_qss": True,

                    "disable_file_type_layer": True,
                    "disable_file_type_shp": True,
                    "disable_file_type_json": True,
                    "disable_file_type_txtcsv": True,
                    "disable_file_type_fold": False,

                    "show_uid_in_file": True,
                    "show_tuid_in_file": True,
                    "show_field_in_file": False,

                    "setting_by_text": False,
                    "setting_by_array": False,
                    "setting_by_expression": False,
                    "setting_by_section": {"enabled": False, "value_type": "int"},
                    "setting_by_numeric": {"enabled": False, "value_type": "int"},
                    "setting_by_combo": {"enabled": False, "items": []},

                    "output_by_file": False,
                    "output_by_field": False,
                    "output_by_table": True,

                    "FILE_UID": [
                        '그룹화 기준 필드 선택',
                        '필드 선택: ',
                        '선택한 필드를 기준으로 레코드를 그룹화하고, 각 그룹별 개수(count), 평균(mean), 합계(sum), 최대값 및 최소값 등의 통계를 계산합니다.'
                    ],

                    "FILE_TUID": [
                        '통계 계산 대상 필드 선택',
                        '필드 선택: ',
                        '통계 계산을 위해 숫자 유형이 아닌 필드는 자동으로 숫자형으로 변환됩니다.'
                    ],

                    "RESULT_TABLE": [f'{job_title} 결과 테이블', '']
                }
            if job_index == 2:
                option = {
                    "apply_basic_qss": True,

                    "disable_file_type_layer": True,
                    "disable_file_type_shp": True,
                    "disable_file_type_json": True,
                    "disable_file_type_txtcsv": True,
                    "disable_file_type_fold": False,

                    "show_uid_in_file": False,
                    "show_tuid_in_file": True,
                    "show_field_in_file": False,

                    "setting_by_text": False,
                    "setting_by_array": False,
                    "setting_by_expression": False,
                    "setting_by_section": {"enabled": False, "value_type": "int"},
                    "setting_by_numeric": {"enabled": False, "value_type": "int"},
                    "setting_by_combo": {"enabled": False, "items": []},

                    "output_by_file": True,
                    "output_by_field": True,
                    "output_by_table": False,

                    "FILE_TUID": [
                        '순위 계산 기준 필드 선택',
                        '필드 선택: ',
                        '선택한 필드 값을 기준으로 레코드를 내림차순으로 정렬하여 순위를 계산합니다.'
                    ],

                    "RESULT_FIELD": [
                        '순위 결과 필드 생성',
                        '필드명 입력: ',
                        ''
                    ],
                }
            if job_index == 3:
                option = {
                    "apply_basic_qss": True,

                    "disable_file_type_layer": True,
                    "disable_file_type_shp": True,
                    "disable_file_type_json": True,
                    "disable_file_type_txtcsv": True,
                    "disable_file_type_fold": False,

                    "show_uid_in_file": True,
                    "show_tuid_in_file": True,
                    "show_field_in_file": False,

                    "setting_by_text": False,
                    "setting_by_array": False,
                    "setting_by_expression": False,
                    "setting_by_section": {"enabled": False, "value_type": "int"},
                    "setting_by_numeric": {"enabled": False, "value_type": "int"},
                    "setting_by_combo": {"enabled": False, "items": []},

                    "output_by_file": True,
                    "output_by_field": True,
                    "output_by_table": False,

                    "FILE_UID": [
                        'A/B 계산 기준 A 필드 선택',
                        '필드 선택: ',
                        'A/B 계산을 위해 숫자 유형이 아닌 필드는 자동으로 숫자형으로 변환됩니다.'
                    ],

                    "FILE_TUID": [
                        'A/B 계산 기준 B 필드 선택',
                        '필드 선택: ',
                        'A/B 계산을 위해 숫자 유형이 아닌 필드는 자동으로 숫자형으로 변환됩니다.'
                    ],

                    "RESULT_FIELD": [
                        'A/B 계산 결과 필드 생성',
                        '필드명 입력: ',
                        ''
                    ],
                }
            if job_index == 4:
                option = {
                    "apply_basic_qss": True,

                    "disable_file_type_layer": True,
                    "disable_file_type_shp": True,
                    "disable_file_type_json": True,
                    "disable_file_type_txtcsv": True,
                    "disable_file_type_fold": False,

                    "show_uid_in_file": False,
                    "show_tuid_in_file": True,
                    "show_field_in_file": False,

                    "setting_by_text": False,
                    "setting_by_array": False,
                    "setting_by_expression": False,
                    "setting_by_section": {"enabled": True, "value_type": "float"},
                    "setting_by_numeric": {"enabled": False, "value_type": "int"},
                    "setting_by_combo": {"enabled": False, "items": []},

                    "output_by_file": True,
                    "output_by_field": False,
                    "output_by_table": False,

                    "FILE_TUID": [
                        '구간 추출 기준 필드 선택',
                        '필드 선택: ',
                        '선택한 필드를 기준으로 지정한 값의 구간에 해당하는 레코드를 추출합니다.'
                    ],

                    "SETTING_SECTION": [
                        '추출 구간 설정 (최소값 ~ 최대값)',
                        '최소값 입력: ',
                        '최대값 입력: ',
                        '선택한 필드의 값이 지정한 범위(최소값~최대값) 내에 있는 레코드만 추출합니다.'
                    ],
                }
            if job_index == 5:
                option = {
                    "apply_basic_qss": True,

                    "disable_file_type_layer": True,
                    "disable_file_type_shp": True,
                    "disable_file_type_json": True,
                    "disable_file_type_txtcsv": True,
                    "disable_file_type_fold": True,

                    "show_uid_in_file": False,
                    "show_tuid_in_file": True,
                    "show_field_in_file": False,

                    "setting_by_text": False,
                    "setting_by_array": False,
                    "setting_by_expression": False,
                    "setting_by_section": {"enabled": True, "value_type": "float"},
                    "setting_by_numeric": {"enabled": False, "value_type": "int"},
                    "setting_by_combo": {"enabled": False, "items": []},

                    "output_by_file": True,
                    "output_by_field": True,
                    "output_by_table": False,

                    "FILE_TUID": [
                        '정규화 기준 필드 선택',
                        '필드 선택: ',
                        '정규화 계산을 위해 숫자 유형이 아닌 필드는 자동으로 숫자형으로 변환됩니다.'
                    ],

                    "SETTING_SECTION": [
                        '정규화 범위 설정 (최소값 ~ 최대값)',
                        '최소값 입력: ',
                        '최대값 입력: ',
                        '입력한 최소값과 최대값 범위를 기준으로 값을 정규화합니다.'
                    ],

                    "RESULT_FIELD": [
                        '정규화 결과 필드 생성',
                        '필드명 입력: ',
                        ''
                    ],
                }
            if job_index == 6:
                option = {
                    "apply_basic_qss": True,

                    "disable_file_type_layer": True,
                    "disable_file_type_shp": True,
                    "disable_file_type_json": True,
                    "disable_file_type_txtcsv": True,
                    "disable_file_type_fold": True,

                    "show_uid_in_file": False,
                    "show_tuid_in_file": True,
                    "show_field_in_file": False,

                    "setting_by_text": False,
                    "setting_by_array": False,
                    "setting_by_expression": False,
                    "setting_by_section": {"enabled": True, "value_type": "float"},
                    "setting_by_numeric": {"enabled": False, "value_type": "int"},
                    "setting_by_combo": {"enabled": False, "items": []},

                    "output_by_file": True,
                    "output_by_field": True,
                    "output_by_table": False,

                    "FILE_TUID": [
                        '범위 재조정 기준 필드 선택',
                        '필드 선택: ',
                        '범위 재조정 계산을 위해 숫자 유형이 아닌 필드는 자동으로 숫자형으로 변환됩니다.'
                    ],

                    "SETTING_SECTION": [
                        '범위 재조정 설정 (최소값 ~ 최대값)',
                        '최소값 입력: ',
                        '최대값 입력: ',
                        '입력한 최소값과 최대값 범위를 기준으로 값을 재조정합니다.'
                    ],

                    "RESULT_FIELD": [
                        '범위 재조정 결과 필드 생성',
                        '필드명 입력: ',
                        ''
                    ],
                }
            return option

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)

    def run_job_by_index(self, gdf, file_preview_index):
        
        try:
            
            file_info = common.fileInfo_1

            
            setting_text = file_info.file_setting.get_text()
            setting_numeric = file_info.file_setting.get_numeric()
            setting_section_min, setting_section_max = file_info.file_setting.get_section()
            setting_combo = file_info.file_setting.get_combo()
            setting_array_string, setting_array_integer, setting_array_float = file_info.file_setting.get_array()

            
            source_file_type, source_file_path, source_file_name = file_info.file_record.get_record()

            
            file_preview = file_info.file_preview[file_preview_index]
            file_field_selection = file_preview.get_selection_field()
            file_uid = file_preview.get_file_uid()
            file_tuid = file_preview.get_file_tuid()
            file_is_field_check = file_preview.get_field_check()
            result_field = file_info.result_field

            
            gdf.columns = gdf.columns.astype(str)

            
            result = None
            if self.job_index == 0:
                result = self.calculate_numeric_statistics(gdf, file_tuid)

            elif self.job_index == 1:
                result = self.calculate_group_statistics(gdf, file_uid, file_tuid)

            elif self.job_index == 2:
                result = self.calculate_field_ranking(gdf, file_tuid, result_field)

            elif self.job_index == 3:
                result = self.calculate_field_ratio(gdf, file_uid, file_tuid, result_field)

            elif self.job_index == 4:
                result = self.extract_records_by_range(gdf, file_tuid, setting_section_min, setting_section_max)

            elif self.job_index == 5:
                gdf = keep_columns_gdf(gdf, file_field_selection + [file_tuid]) if file_is_field_check else gdf
                result = self.normalize_field_to_range(gdf, file_tuid, setting_section_min, setting_section_max, result_field)

            elif self.job_index == 6:
                gdf = keep_columns_gdf(gdf, file_field_selection + [file_tuid]) if file_is_field_check else gdf
                result = self.clip_field_to_fixed_range(gdf, file_tuid, setting_section_min, setting_section_max, result_field)

            
            if result is None or result is False:
                return None

            return result

        except Exception as e:
            logger.error("에러 발생: %s", e, exc_info=True)
            return None



