
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__), 'StringDataProcessing_dockwidget_base.ui'))


class StringDataProcessingDockWidget(QDialog, FORM_CLASS):  
    def __init__(self, parent=None):
        
        super(StringDataProcessingDockWidget, 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 strip_whitespace_from_field(self, gdf, target_field, result_field=None):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            gdf_copy[target_field] = gdf_copy[target_field].astype(str)

            
            stripped_values = gdf_copy[target_field].str.strip()

            
            if result_field and result_field != target_field:
                gdf_copy[result_field] = stripped_values
            else:
                gdf_copy[target_field] = stripped_values

            return gdf_copy

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

    
    def apply_text_replacement(self, gdf, target_field, replacement, result_field=None):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            gdf_copy[target_field] = gdf_copy[target_field].astype(str)
            modified_series = gdf_copy[target_field]

            
            if '=' in replacement:
                old, new = replacement.split('=', 1)
                modified_series = modified_series.str.replace(old, new, regex=False)
            else:
                QMessageBox.information(self, "치환 규칙 오류",
                                        f"치환 규칙 형식이 잘못되었습니다: '{replacement}'\n\n'A=B' 또는 'A=' 형식으로 입력해 주세요.",
                                        QMessageBox.Ok)
                return None

            
            if result_field and result_field != target_field:
                gdf_copy[result_field] = modified_series
            else:
                gdf_copy[target_field] = modified_series

            return gdf_copy

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

    
    def split_text_by_delimiter_auto_fields(self, gdf, target_field, delimiter):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            gdf_copy[target_field] = gdf_copy[target_field].astype(str)

            
            split_df = gdf_copy[target_field].str.split(delimiter.replace("'", ""), expand=True)

            
            for i in range(split_df.shape[1]):
                new_field = f"{target_field}_{i + 1}"
                gdf_copy[new_field] = split_df[i].str.strip()

            return gdf_copy

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

    
    def convert_case_variations(self, gdf, target_field):
        
        try:
            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            gdf_copy[target_field] = gdf_copy[target_field].astype(str)

            
            gdf_copy["Upper_"] = gdf_copy[target_field].str.upper()  
            gdf_copy["Lower_"] = gdf_copy[target_field].str.lower()  
            gdf_copy["Capitalized_"] = gdf_copy[target_field].str.capitalize()  

            return gdf_copy

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

    
    def extract_text_between_delimiters(self, gdf, target_field, delim, result_field):
        
        try:
            import re

            
            if ',' not in delim:
                QMessageBox.information(self, "입력 오류", "구분자는 반드시 쉼표(,)로 앞·뒤 구분자를 구분해야 합니다.\n예: /,/ 또는 구,동", QMessageBox.Ok)
                return None

            
            start_delim, end_delim = delim.split(',', 1)

            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            gdf_copy[target_field] = gdf_copy[target_field].astype(str)

            
            if start_delim and end_delim:
                pattern = rf"{re.escape(start_delim)}(.*?){re.escape(end_delim)}"
            elif start_delim and not end_delim:
                pattern = rf"{re.escape(start_delim)}(.*)"
            elif not start_delim and end_delim:
                pattern = rf"^(.*?){re.escape(end_delim)}"
            else:
                gdf_copy[result_field] = gdf_copy[target_field]
                return gdf_copy

            
            gdf_copy[result_field] = gdf_copy[target_field].str.extract(pattern)[0].str.strip()

            return gdf_copy

        except Exception as e:
            QMessageBox.information(self, "작업 오류", "구분자 사이 문자열 추출 중 오류가 발생하였습니다.", QMessageBox.Ok)
            logger.error("구분자 추출 에러: %s", e, exc_info=True)
            return None

    
    def extract_text_by_position_range(self, gdf, target_field, start_no, end_no, result_field):
        
        try:

            
            try:
                s = int(start_no)
                e = int(end_no)
            except Exception:
                QMessageBox.information(self, "입력 오류", "시작 번호와 끝 번호는 정수로 입력해 주세요.\n예: 시작 2, 끝 5", QMessageBox.Ok)
                return None

            if s <= 0 or e <= 0:
                QMessageBox.information(self, "입력 오류", "시작 번호와 끝 번호는 1 이상의 정수여야 합니다.", QMessageBox.Ok)
                return None

            if s > e:
                QMessageBox.information(self, "입력 오류", "시작 번호는 끝 번호보다 클 수 없습니다.", QMessageBox.Ok)
                return None

            
            gdf_copy = gdf.copy()

            
            gdf_copy = normalize_null_values(gdf_copy)

            
            gdf_copy[target_field] = gdf_copy[target_field].astype(str)

            
            
            
            start_idx = s - 1
            end_idx_exclusive = e  

            def _slice_text(val: str) -> str:
                if val is None:
                    return ""
                text = str(val)

                
                if text.lower() in ("nan", "none"):
                    return ""

                
                return text[start_idx:end_idx_exclusive]

            
            gdf_copy[result_field] = gdf_copy[target_field].apply(_slice_text)

            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": 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": 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 == 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": True,

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

                    "setting_by_text": True,
                    "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": [
                        '문자 삭제·치환 기준 필드 선택',
                        '필드 선택: ',
                        '선택한 필드에서 쉼표, 괄호, 따옴표 등 특정 문자나 부호를 삭제하거나 다른 값으로 치환할 수 있습니다.'
                    ],

                    "SETTING_TEXT": [
                        '문자 삭제·치환 값 설정',
                        '삭제·치환 값 입력: ',
                        '• 필드 값의 A를 B로 변경하려면 A=B 형식으로 입력해 주세요.\n'
                        '• 필드 값의 A를 삭제하려면 A= 형식으로 입력해 주세요.'
                    ],

                    "RESULT_FIELD": [
                        '문자 삭제·치환 결과 필드 생성',
                        '필드명 입력: ',
                        ''
                    ],
                }
            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": True,
                    "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": False,
                    "output_by_table": False,

                    "FILE_TUID": [
                        '텍스트 분리 기준 필드 선택',
                        '필드 선택: ',
                        '쉼표(,), 슬래시(/), 하이픈(-) 등 지정한 구분자를 기준으로 문자열을 분리하고, 분리된 값을 새 필드로 자동 생성하여 저장합니다.'
                    ],

                    "SETTING_TEXT": [
                        '구분자 값 설정',
                        '구분자 입력: ',
                        '• 입력한 구분자를 기준으로 문자열을 분리하며, 분리된 값의 개수만큼 새 필드가 자동 생성됩니다.\n'
                        "• 공백(띄어쓰기)을 구분자로 사용할 경우, ' '(따옴표 안 공백)으로 입력해 주세요."
                    ],
                }
            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": 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": False,
                    "output_by_table": False,

                    "FILE_TUID": [
                        '대소문자 변환 기준 필드 선택',
                        '필드 선택: ',
                        '선택한 문자열을 ① 모두 대문자, ② 모두 소문자, ③ 첫 글자만 대문자 형태로 변환하여 세 개의 새 필드를 자동 생성합니다.'
                    ],
                }
            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": True,
                    "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": [
                        '구분자 사이 문자열 추출 기준 필드 선택',
                        '필드 선택: ',
                        '선택한 필드에서 앞쪽과 뒤쪽 구분자 사이에 위치한 문자열을 자동으로 추출합니다.'
                    ],

                    "SETTING_TEXT": [
                        '앞뒤 구분자 값 설정',
                        '앞뒤 구분자 입력: ',
                        '• 앞쪽과 뒤쪽 구분자는 쉼표(,)로 구분하여 입력해 주세요.\n'
                        '• 예: /,/ 또는 시,동 형식으로 입력합니다.\n'
                        '• 앞쪽 구분자가 없으면 문자열의 처음부터, 뒤쪽 구분자가 없으면 문자열의 끝까지 포함됩니다.\n'
                        '• 예: ,/ 또는 ,동 형식으로 입력합니다.'
                    ],

                    "RESULT_FIELD": [
                        '구분자 사이 문자열 결과 필드 생성',
                        '필드명 입력: ',
                        ''
                    ],
                }
            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": 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": "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": [
                        '텍스트 범위 추출 기준 필드 선택',
                        '필드 선택: ',
                        '선택한 필드를 대상으로 텍스트 범위 추출을 적용합니다.'
                    ],

                    "SETTING_SECTION": [
                        '텍스트 범위 설정',
                        '시작 번호: ',
                        '끝 번호: ',
                        '지정한 시작 번호부터 끝 번호까지의 텍스트만 추출하여 저장합니다.\n'
                        '예: abcdefg에서 2~5를 지정하면 bcde가 추출됩니다.'
                    ],

                }
            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.strip_whitespace_from_field(gdf, file_tuid, result_field)

            elif self.job_index == 1:
                result = self.apply_text_replacement(gdf, file_tuid, setting_text, result_field)

            elif self.job_index == 2:
                result = self.split_text_by_delimiter_auto_fields(gdf, file_tuid, setting_text)

            elif self.job_index == 3:
                result = self.convert_case_variations(gdf, file_tuid)

            elif self.job_index == 4:
                result = self.extract_text_between_delimiters(gdf, file_tuid, setting_text, result_field)

            elif self.job_index == 5:
                result = self.extract_text_by_position_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



