from pathlib import Path

from nicegui import ui

from nemo_library_etl.adapter.migman.enums import MigManExtractAdapter

from ..folder_actions import render_folder_button, resolve_extract_output_dir
from ..render_context import UIRenderContext


def render_extract(ctx: UIRenderContext) -> None:
    runtime = ctx.runtime
    global_config = runtime.global_config
    local_config = runtime.local_config
    merged_config = runtime.merged_config
    form_state = runtime.form_state
    state = runtime.state
    has_changes = runtime.has_changes
    active = runtime.active
    nav_buttons = runtime.nav_buttons
    controller = ctx.controller
    content_container = ctx.content_container
    transform_tab_state = ctx.transform_tab_state
    convenience_hooks = ctx.convenience_hooks
    migmanprojects = ctx.migmanprojects
    get_tip = ctx.get_tip
    mark_changed = ctx.mark_changed
    mark_saved = ctx.mark_saved
    set_active = ctx.set_active
    save_config = ctx.save_config
    reset_config = ctx.reset_config
    renderers = ctx.renderers
    self = ctx.ui_app

    def _coerce_int(value, default: int, *, min_value: int = 1) -> int:
        try:
            v = int(value)
            return v if v >= min_value else default
        except (TypeError, ValueError):
            return default

    def _get_migman_duckdb_path() -> Path:
        etl_dir = form_state["data"].get("etl_directory", "./etl/migman")
        return (Path(etl_dir) / "migman_etl.duckdb").resolve()

    def _render_tables_picker(cfg: dict, adapter_key: str, *, placeholder: str) -> None:
        current_tables = cfg.get("tables", []) or []
        default_tables = (
            global_config.get("extract", {})
            .get(adapter_key, {})
            .get("tables", [])
            or []
        )

        all_available_tables = sorted(set(default_tables) | set(current_tables))

        def on_tables_change(e):
            cfg["tables"] = e.value or []
            mark_changed()
            convenience_hooks.get("refresh_quick_setup", lambda: None)()

        ui.label("Tables").classes("text-base font-medium mt-2")

        tables_select = (
            ui.select(
                options=all_available_tables,
                value=current_tables,
                multiple=True,
                label="Select tables to extract",
                on_change=on_tables_change,
            )
            .props("use-chips clearable use-input input-debounce=0")
            .classes("w-full")
            .tooltip(get_tip("extract.tables_select"))
        )

        with ui.row().classes("gap-2 items-end mt-3"):
            new_table_input = (
                ui.input(label="Add custom table", placeholder=placeholder)
                .props("dense input-debounce=0")
                .classes("flex-1")
                .tooltip(get_tip("extract.add_custom_table"))
            )

            # wichtig: sorgt dafür, dass "tippen + klicken" ohne Fokuswechsel serverseitig ankommt
            new_table_input.on("input", lambda e: None)

            def add_custom_table():
                table_name = (new_table_input.value or "").strip()
                if not table_name:
                    ui.notify("Please enter a table name", type="warning")
                    return
                if table_name in all_available_tables:
                    ui.notify(f"Table '{table_name}' already exists", type="warning")
                    return

                all_available_tables.append(table_name)
                all_available_tables.sort()
                tables_select.options = all_available_tables

                selection = cfg.get("tables", []) or []
                if table_name not in selection:
                    selection = selection + [table_name]
                    cfg["tables"] = selection
                    tables_select.value = selection
                    mark_changed()
                    convenience_hooks.get("refresh_quick_setup", lambda: None)()

                new_table_input.value = ""
                ui.notify(f"Added table: {table_name}", type="positive")

            ui.button("Add Table", icon="add", on_click=add_custom_table).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

        ui.label("You can add custom tables not in the default list above.").classes("text-sm text-gray-600 mt-2")

    def render_extract():
        if not content_container:
            return

        content_container.clear()
        with content_container:
            ui.label("Extract").classes("text-2xl font-semibold")
            ui.separator()
            ui.label("Configure data sources and extraction parameters.")

            # Extract configuration
            extract_config = form_state["data"].setdefault("extract", {})

            # General Extract Settings Card
            with ui.card().classes("mt-4"):
                ui.label("General Extract Settings").classes(
                    "text-lg font-semibold mb-2"
                )

                def on_extract_active_change(e):
                    extract_config["active"] = e.value
                    mark_changed()

                extract_active = extract_config.get("active", True)

                with ui.row().classes("items-center gap-2"):
                    extract_active_switch = ui.switch(
                        "Extract active",
                        value=extract_active,
                        on_change=on_extract_active_change,
                    )
                    render_folder_button(
                        resolve_extract_output_dir(form_state),
                        get_tip("open_extract_directory"),
                    )
                extract_active_switch.tooltip(get_tip("extract.general.active"))
                def on_load_to_nemo_change(e):
                    extract_config["load_to_nemo"] = e.value
                    mark_changed()

                load_to_nemo_switch = ui.switch(
                    "Load to NEMO",
                    value=extract_config.get("load_to_nemo", True),
                    on_change=on_load_to_nemo_change,
                )

                load_to_nemo_switch.tooltip(get_tip("extract.general.load_to_nemo"))
                def on_delete_temp_files_change(e):
                    extract_config["delete_temp_files"] = e.value
                    mark_changed()

                delete_temp_files_switch = ui.switch(
                    "Delete temp files",
                    value=extract_config.get("delete_temp_files", True),
                    on_change=on_delete_temp_files_change,
                )

                delete_temp_files_switch.tooltip(get_tip("extract.general.delete_temp_files"))
                def on_nemo_project_prefix_change(e):
                    extract_config["nemo_project_prefix"] = e.value
                    mark_changed()

                nemo_project_prefix_input = (
                    ui.input(
                        label="NEMO project prefix",
                        value=extract_config.get("nemo_project_prefix", "mme"),
                        placeholder="migman_extract_",
                        on_change=on_nemo_project_prefix_change,
                    )
                    .props("dense")
                    .classes("w-[36rem] mt-3")
                    .tooltip(get_tip("extract.general.nemo_project_prefix"))
                )

                def on_extract_method_change(e):
                    extract_config["extract_method"] = e.value
                    mark_changed()
                    # Update convenience button visibility
                    update_convenience_button_visibility()

                extract_method_select = (
                    ui.select(
                        label="Extract method",
                        options=["database", "file"],
                        value=extract_config.get("extract_method", "database"),
                        on_change=on_extract_method_change,
                    )
                    .props("dense")
                    .classes("w-48 mt-3")
                )

                extract_method_select.tooltip(get_tip("extract.general.method"))
                ui.label(
                    "Choose extraction method: 'database' for direct database extraction, 'file' for file-based extraction only."
                ).classes("text-sm text-gray-600 mt-1")

                # Convenience button container for creating file configs from adapter tables
                convenience_button_container = ui.column().classes("mt-3")

                def get_adapter_tables():
                    """Get the tables list for the currently selected adapter."""
                    setup_config = form_state["data"].setdefault("setup", {})
                    current_adapter = setup_config.get(
                        "source_adapter", MigManExtractAdapter.GENERICODBC.value
                    )

                    # Get the tables from the adapter configuration in the form state
                    adapter_tables = []
                    if current_adapter in extract_config:
                        adapter_tables = extract_config[current_adapter].get(
                            "tables", []
                        )

                    return current_adapter, adapter_tables

                def create_file_configs_from_adapter():
                    """Create file configurations for all tables in the selected adapter."""
                    current_adapter, adapter_tables = get_adapter_tables()

                    if not adapter_tables:
                        ui.notify(
                            f"No tables found for adapter '{current_adapter}'",
                            type="warning",
                        )
                        return

                    file_config = extract_config.setdefault("file", [])
                    added_count = 0

                    # Get the extract_from_folder value for file paths
                    extract_from_folder = extract_config.get(
                        "extract_from_folder", ""
                    )
                    if extract_from_folder and extract_from_folder.strip():
                        base_folder = extract_from_folder.strip()
                        # Ensure folder ends with separator
                        if not base_folder.endswith(
                            "/"
                        ) and not base_folder.endswith("\\"):
                            base_folder += "/"
                    else:
                        base_folder = "./data/"

                    for table in adapter_tables:
                        # Check if a file config for this table already exists
                        table_exists = any(
                            file_item.get("project", "").lower()
                            == table.lower()
                            for file_item in file_config
                        )

                        if not table_exists:
                            # Create new file configuration for this table
                            new_file_config = {
                                "active": True,
                                "project": table,
                                "file_path": f"{base_folder}{table.lower()}.csv",
                                "separator": ";",
                                "quote": '"',
                                "dateformat": "%d-%m-%Y",
                                "encoding": "utf-8",
                                "header": True,
                                "columns": [],
                                "all_varchar": True,
                            }
                            file_config.append(new_file_config)
                            added_count += 1

                    if added_count > 0:
                        mark_changed()
                        ui.notify(
                            f"Created {added_count} file configurations for {current_adapter} tables",
                            type="positive",
                        )
                        # Refresh only the file configuration panel to show the new files
                        extract_file_config_panel.refresh()
                    else:
                        ui.notify(
                            "All table file configurations already exist",
                            type="info",
                        )

                def update_convenience_button_visibility():
                    """Show/hide the convenience button based on extract method and available tables."""
                    convenience_button_container.clear()

                    current_extract_method = extract_config.get(
                        "extract_method", "database"
                    )
                    if current_extract_method == "file":
                        current_adapter, adapter_tables = get_adapter_tables()

                        if adapter_tables and len(adapter_tables) > 0:
                            with convenience_button_container:
                                with ui.card().classes(
                                    "p-3 bg-blue-50 border border-blue-200"
                                ):
                                    ui.label("Quick Setup").classes(
                                        "text-md font-semibold mb-2 text-blue-800"
                                    )
                                    ui.label(
                                        f"Create file configurations for all {len(adapter_tables)} tables from the {current_adapter.upper()} adapter?"
                                    ).classes("text-sm mb-3 text-blue-700")

                                    with ui.row().classes("gap-2"):
                                        ui.button(
                                            f"Create {len(adapter_tables)} File Configs",
                                            icon="add_box",
                                            on_click=create_file_configs_from_adapter,
                                        ).props("color=primary").tooltip(get_tip("extract.quick_setup.create_file_configs"))

                                        # Show table names on hover/click
                                        with ui.button(
                                            "Show Tables", icon="visibility"
                                        ).props(
                                            "outline color=primary"
                                        ) as show_tables_btn:
                                            with ui.tooltip():
                                                ui.label(
                                                    f"Tables: {', '.join(adapter_tables[:5])}"
                                                )
                                                if len(adapter_tables) > 5:
                                                    ui.label(
                                                        f"... and {len(adapter_tables) - 5} more"
                                                    )
                convenience_hooks["refresh_quick_setup"] = update_convenience_button_visibility
                # Initial visibility update
                update_convenience_button_visibility()

                def on_extract_from_folder_change(e):
                    extract_config["extract_from_folder"] = (
                        e.value if e.value.strip() else None
                    )
                    mark_changed()
                    update_folder_status()

                extract_from_folder_input = (
                    ui.input(
                        label="Extract from folder",
                        value=extract_config.get("extract_from_folder", "")
                        or "",
                        placeholder="e.g. /path/to/data/folder",
                        on_change=on_extract_from_folder_change,
                    )
                    .props("dense")
                    .classes("w-[36rem] mt-3")
                    .tooltip(get_tip("extract.extract_from_folder"))
                )

                # Status label for folder validation
                folder_status_label = ui.label("").classes("text-sm mt-1")

                def update_folder_status():
                    """Update the folder status label with validation information."""
                    folder_path = extract_config.get("extract_from_folder", "")

                    if not folder_path or not folder_path.strip():
                        folder_status_label.text = "Specify the folder path to extract files from (primarily used for file-based extraction)."
                        folder_status_label.classes(
                            "text-sm text-gray-600 mt-1"
                        )
                        return

                    try:
                        path_obj = Path(folder_path)
                        if not path_obj.exists():
                            folder_status_label.text = "⚠️ Path does not exist"
                            folder_status_label.classes(
                                "text-sm text-orange-600 mt-1"
                            )
                        elif not path_obj.is_dir():
                            folder_status_label.text = (
                                "⚠️ Path exists but is not a directory"
                            )
                            folder_status_label.classes(
                                "text-sm text-orange-600 mt-1"
                            )
                        else:
                            # Count CSV files in the directory
                            csv_files = list(path_obj.glob("*.csv"))
                            csv_count = len(csv_files)

                            if csv_count == 0:
                                folder_status_label.text = (
                                    "✅ Directory exists but no CSV files found"
                                )
                                folder_status_label.classes(
                                    "text-sm text-yellow-600 mt-1"
                                )
                            elif csv_count == 1:
                                folder_status_label.text = f"✅ Directory exists with {csv_count} CSV file"
                                folder_status_label.classes(
                                    "text-sm text-green-600 mt-1"
                                )
                            else:
                                folder_status_label.text = f"✅ Directory exists with {csv_count} CSV files"
                                folder_status_label.classes(
                                    "text-sm text-green-600 mt-1"
                                )

                    except (OSError, PermissionError) as e:
                        folder_status_label.text = (
                            f"❌ Error accessing path: {str(e)}"
                        )
                        folder_status_label.classes("text-sm text-red-600 mt-1")

                # Initial status update
                update_folder_status()

            # Create adapter configuration container
            adapter_config_container = ui.column().classes("mt-4")
            ADAPTER_CONFIG_RENDERERS = {
                MigManExtractAdapter.INFORCOM.value: render_inforcom_config,
                MigManExtractAdapter.INFORIGF.value: render_inforigf_config,
                MigManExtractAdapter.INFORAS.value: render_inforas_config,
                MigManExtractAdapter.SAPECC.value: render_sapecc_config,
                MigManExtractAdapter.SAPTENANT.value: render_saptenant_config,
                MigManExtractAdapter.SAPSYSTEMDB.value: render_sapsystemdb_config,
                MigManExtractAdapter.PROALPHA.value: render_proalpha_config,
                MigManExtractAdapter.SAGEKHK.value: render_sagekhk_config,
                MigManExtractAdapter.GENERICODBC.value: render_generic_odbc_config,
            }                   


            def render_adapter_config():
                """Render the adapter configuration based on current selection."""
                adapter_config_container.clear()
                with adapter_config_container:
                    with ui.card().classes(""):
                        # Get adapter from setup config instead of extract config
                        setup_config = form_state["data"].setdefault(
                            "setup", {}
                        )
                        current_adapter = setup_config.get(
                            "source_adapter",
                            MigManExtractAdapter.GENERICODBC.value,
                        )
                        renderer = ADAPTER_CONFIG_RENDERERS.get(current_adapter)

                        if renderer:
                            renderer(extract_config)
                        else:
                            ui.label("Adapter Configuration").classes("text-lg font-semibold mb-3")
                            ui.label(
                                f"No configuration available for adapter '{current_adapter}'."
                            ).classes("text-sm text-gray-600 italic")

                # Update convenience button visibility when adapter config is rendered
                update_convenience_button_visibility()

            # Initial render of adapter configuration
            render_adapter_config()

            # File Configuration
            extract_file_config_panel()

    def render_inforcom_config(extract_config):
        """Render INFORCOM adapter configuration."""
        inforcom_config = extract_config.setdefault("inforcom", {})

        ui.label("INFORCOM Configuration").classes("text-lg font-semibold mb-3")
        ui.label(
            "ODBC-based extraction from INFORCOM (INFOR.* tables)"
        ).classes("text-sm mb-4")

        with ui.column().classes("gap-4"):
            # ODBC Connection String
            def on_odbc_connstr_change(e):
                inforcom_config["odbc_connstr"] = e.value
                mark_changed()

            ui.textarea(
                label="ODBC Connection String",
                value=inforcom_config.get(
                    "odbc_connstr",
                    "Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                ),
                placeholder="Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                on_change=on_odbc_connstr_change,
            ).props("outlined").classes("w-full").style("min-height: 100px").tooltip(get_tip("extract.adapter.odbc_connstr"))

            # Options row
            with ui.row().classes("gap-4"):
                # Chunk Size
                def on_chunk_size_change(e):
                    inforcom_config["chunk_size"] = _coerce_int(e.value, 10000, min_value=1)
                    mark_changed()

                ui.number(
                    label="Chunk Size",
                    value=inforcom_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

                # Timeout
                def on_timeout_change(e):
                    inforcom_config["timeout"] = _coerce_int(e.value, 300, min_value=1)
                    mark_changed()

                ui.number(
                    label="Timeout (seconds)",
                    value=inforcom_config.get("timeout", 300),
                    min=1,
                    on_change=on_timeout_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.timeout"))

                # Table Prefix
                def on_table_prefix_change(e):
                    inforcom_config["table_prefix"] = e.value
                    mark_changed()
            
                ui.input(
                    label="Table Prefix",
                    value=inforcom_config.get("table_prefix", "INFOR."),
                    placeholder="INFOR.",
                    on_change=on_table_prefix_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.table_prefix"))
            

            # Table Selector
            def on_table_selector_change(e):
                inforcom_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=inforcom_config.get("table_selector", "join_parser"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
        
            with ui.column().classes("w-full"):
                _render_tables_picker(
                    inforcom_config,
                    "inforcom",
                    placeholder="Enter table name (e.g. INFOR.CUSTOM_TABLE)",
                )

    def render_inforigf_config(extract_config):
        """Render INFORIGF adapter configuration."""
        inforigf_config = extract_config.setdefault("inforigf", {})

        ui.label("INFOR IGF Configuration").classes(
            "text-lg font-semibold mb-3"
        )
        ui.label(
            "ODBC-based extraction from INFOR IGF (INFOR.* tables)"
        ).classes("text-sm mb-4")

        with ui.column().classes("gap-4"):
            # ODBC Connection String
            def on_odbc_connstr_change(e):
                inforigf_config["odbc_connstr"] = e.value
                mark_changed()

            ui.textarea(
                label="ODBC Connection String",
                value=inforigf_config.get(
                    "odbc_connstr",
                    "Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                ),
                placeholder="Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                on_change=on_odbc_connstr_change,
            ).props("outlined").classes("w-full").style("min-height: 100px").tooltip(get_tip("extract.adapter.odbc_connstr"))

            # Options row
            with ui.row().classes("gap-4"):
                # Chunk Size
                def on_chunk_size_change(e):
                    if e.value is not None:
                        inforigf_config["chunk_size"] = (
                            int(e.value) if e.value else 10000
                        )
                        mark_changed()

                ui.number(
                    label="Chunk Size",
                    value=inforigf_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

                # Timeout
                def on_timeout_change(e):
                    if e.value is not None:
                        inforigf_config["timeout"] = (
                            int(e.value) if e.value else 300
                        )
                        mark_changed()

                ui.number(
                    label="Timeout (seconds)",
                    value=inforigf_config.get("timeout", 300),
                    min=1,
                    on_change=on_timeout_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.timeout"))

                # Table Prefix
                def on_table_prefix_change(e):
                    inforigf_config["table_prefix"] = e.value
                    mark_changed()
            
                ui.input(
                    label="Table Prefix",
                    value=inforigf_config.get("table_prefix", "INFOR."),
                    placeholder="INFOR.",
                    on_change=on_table_prefix_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.table_prefix"))
        

            # Table Selector
            def on_table_selector_change(e):
                inforigf_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=inforigf_config.get("table_selector", "join_parser"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
            current_tables = inforigf_config.get("tables", [])

            with ui.column().classes("w-full") as tables_container:
                ui.label("Tables").classes("text-base font-medium mt-2")

                def on_tables_change(e):
                    inforigf_config["tables"] = e.value if e.value else []
                    mark_changed()

                default_inforigf_tables = (
                    global_config.get("extract", {})
                    .get("inforigf", {})
                    .get("tables", [])
                )

                # Combine default tables with any custom tables that might be in current config
                all_available_tables = list(
                    set(default_inforigf_tables + current_tables)
                )
                all_available_tables.sort()

                tables_select = (
                    ui.select(
                        options=all_available_tables,
                        value=current_tables,
                        multiple=True,
                        label="Select tables to extract",
                        on_change=on_tables_change,
                    )
                    .props("use-chips clearable use-input input-debounce=0")
                    .classes("w-full")
                    .tooltip(get_tip("extract.tables_select"))
                )

                # Add custom table functionality
                with ui.row().classes("gap-2 items-end mt-3"):
                    custom_table_input = (
                        ui.input(
                            label="Custom table name",
                            placeholder="Enter table name",
                        )
                        .props("dense")
                        .classes("flex-grow")
                        .tooltip(get_tip("extract.add_custom_table"))
                    )

                    def add_custom_table():
                        table_name = custom_table_input.value
                        if table_name and table_name.strip():
                            table_name = table_name.strip()
                            if table_name not in current_tables:
                                current_tables.append(table_name)
                                inforigf_config["tables"] = current_tables
                                mark_changed()

                                # Update the available options and current selection
                                all_available_tables.append(table_name)
                                all_available_tables.sort()
                                tables_select.set_options(all_available_tables)
                                tables_select.set_value(current_tables)

                                # Clear input
                                custom_table_input.value = ""
                                ui.notify(
                                    f"Added table: {table_name}",
                                    type="positive",
                                )
                            else:
                                ui.notify(
                                    f"Table {table_name} already exists",
                                    type="warning",
                                )
                        else:
                            ui.notify(
                                "Please enter a table name", type="warning"
                            )

                    ui.button(
                        "Add Table", icon="add", on_click=add_custom_table
                    ).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

                ui.label(
                    "You can add custom tables not in the default list above."
                ).classes("text-sm text-gray-600 mt-2")

    def render_inforas_config(extract_config):
        """Render INFORAS adapter configuration with schema resolver."""
        inforas_config = extract_config.setdefault("inforas", {})

        ui.label("INFORAS Configuration").classes("text-lg font-semibold mb-3")
        ui.label(
            "ODBC-based extraction from INFORAS with schema resolver."
        ).classes("text-sm mb-4")

        with ui.column().classes("gap-4"):
            # ODBC Connection String
            def on_odbc_connstr_change(e):
                inforas_config["odbc_connstr"] = e.value
                mark_changed()

            ui.textarea(
                label="ODBC Connection String",
                value=inforas_config.get(
                    "odbc_connstr",
                    "Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                ),
                placeholder="Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                on_change=on_odbc_connstr_change,
            ).props("outlined").classes("w-full").style("min-height: 100px").tooltip(get_tip("extract.adapter.odbc_connstr"))

            with ui.row().classes("gap-4"):
                # Chunk Size
                def on_chunk_size_change(e):
                    inforas_config["chunk_size"] = _coerce_int(e.value, 10000, min_value=1)
                    mark_changed()

                ui.number(
                    label="Chunk Size",
                    value=inforas_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

                # Timeout
                def on_timeout_change(e):
                    inforas_config["timeout"] = _coerce_int(e.value, 300, min_value=1)
                    mark_changed()

                ui.number(
                    label="Timeout (seconds)",
                    value=inforas_config.get("timeout", 300),
                    min=1,
                    on_change=on_timeout_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.timeout"))

            # Schema Prefixes
            default_prefixes = (
                global_config.get("extract", {})
                .get("inforas", {})
                .get(
                    "table_prefixes",
                    ["WWBD000.", "CCMPQRD.", "CCMPDTAE.", "CASPDTAE."],
                )
            )

            current_prefixes = inforas_config.get("table_prefixes", default_prefixes)

            def normalize_prefixes(raw_text: str) -> list[str]:
                prefixes = []
                for line in (raw_text or "").splitlines():
                    prefix = line.strip().rstrip(".")
                    if prefix:
                        prefixes.append(f"{prefix}.")
                # Reihenfolge beibehalten, Dubletten entfernen
                return list(dict.fromkeys(prefixes))

            def on_table_prefixes_change(e):
                inforas_config["table_prefixes"] = normalize_prefixes(e.value)
                mark_changed()

            prefix_input = ui.textarea(
                label="Schema Prefixes",
                value="\n".join(current_prefixes),
                placeholder="WWBD000.\nCCMPQRD.\nCCMPDTAE.\nCASPDTAE.",
                on_change=on_table_prefixes_change,
            ).props("outlined").classes("w-[28rem]").style("min-height: 140px")

            prefix_input.tooltip(
                "Enter one schema prefix per line. A trailing dot is added automatically if missing."
            )

            ui.label(
                "Each prefix is checked in order for every table. Store only table names below, without schema."
            ).classes("text-sm text-gray-600 -mt-2").tooltip(get_tip("extract.table_prefixes"))

            # Table Selector
            def on_table_selector_change(e):
                inforas_config["table_selector"] = e.value
                mark_changed()
                convenience_hooks.get("refresh_quick_setup", lambda: None)()
            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=inforas_config.get("table_selector", "all"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            with ui.column().classes("w-full"):
                _render_tables_picker(
                    inforas_config,
                    "inforas",
                    placeholder="Enter table name (e.g. UPKADP)",
                )
    def render_sapecc_config(extract_config):
        """Render SAP ECC adapter configuration."""
        sapecc_config = extract_config.setdefault("sapecc", {})

        ui.label("SAP ECC Configuration").classes("text-lg font-semibold mb-3")
        ui.label("HANA DB-based extraction from SAP ECC").classes(
            "text-sm mb-4"
        )

        with ui.column().classes("gap-4"):
            # Connection parameters row
            with ui.row().classes("gap-4 w-full"):
                # Address
                def on_address_change(e):
                    sapecc_config["address"] = e.value
                    mark_changed()

                ui.input(
                    label="Server Address",
                    value=sapecc_config.get("address", "your_sap_hana_server"),
                    placeholder="your_sap_hana_server",
                    on_change=on_address_change,
                ).props("dense").classes("flex-grow").tooltip(get_tip("extract.sap.server_address"))

                # Port
                def on_port_change(e):
                    try:
                        port = int(e.value) if e.value else 30015
                        if 1 <= port <= 65535:
                            sapecc_config["port"] = port
                            mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Port",
                    value=sapecc_config.get("port", 30015),
                    min=1,
                    max=65535,
                    on_change=on_port_change,
                ).props("dense").classes("w-32").tooltip(get_tip("extract.sap.port"))

            # Credentials row
            with ui.row().classes("gap-4 w-full"):
                # Username
                def on_user_change(e):
                    sapecc_config["user"] = e.value
                    mark_changed()

                ui.input(
                    label="Username",
                    value=sapecc_config.get("user", "your_username"),
                    placeholder="your_username",
                    on_change=on_user_change,
                ).props("dense").classes("flex-1").tooltip(get_tip("extract.sap.username"))

                # Password
                def on_password_change(e):
                    sapecc_config["password"] = e.value
                    mark_changed()

                ui.input(
                    label="Password",
                    value=sapecc_config.get("password", "your_password"),
                    placeholder="your_password",
                    password=True,
                    password_toggle_button=True,
                    on_change=on_password_change,
                ).props("dense").classes("flex-1").tooltip(get_tip("extract.sap.password"))

            # Settings row
            with ui.row().classes("gap-4 items-center"):
                # Autocommit
                def on_autocommit_change(e):
                    sapecc_config["autocommit"] = e.value
                    mark_changed()

                ui.switch(
                    "Autocommit",
                    value=sapecc_config.get("autocommit", True),
                    on_change=on_autocommit_change,
                ).tooltip(get_tip("extract.sap.autocommit"))

                # Chunk Size
                def on_chunk_size_change(e):
                    try:
                        sapecc_config["chunk_size"] = (
                            int(e.value) if e.value else 10000
                        )
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Chunk Size",
                    value=sapecc_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

            # Table Selector
            def on_table_selector_change(e):
                sapecc_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=sapecc_config.get("table_selector", "join_parser"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
            current_tables = sapecc_config.get("tables", [])

            with ui.column().classes("w-full") as tables_container:
                ui.label("Tables").classes("text-base font-medium mt-2")

                def on_tables_change(e):
                    sapecc_config["tables"] = e.value if e.value else []
                    mark_changed()

                default_sapecc_tables = (
                    global_config.get("extract", {})
                    .get("sapecc", {})
                    .get("tables", [])
                )

                # Combine default tables with any custom tables that might be in current config
                all_available_tables = list(
                    set(default_sapecc_tables + current_tables)
                )
                all_available_tables.sort()

                tables_select = (
                    ui.select(
                        options=all_available_tables,
                        value=current_tables,
                        multiple=True,
                        label="Select tables to extract",
                        on_change=on_tables_change,
                    )
                    .props("use-chips clearable use-input input-debounce=0")
                    .classes("w-full")
                    .tooltip(get_tip("extract.tables_select"))
                )

                # Add custom table functionality
                with ui.row().classes("gap-2 items-end mt-3"):
                    new_table_input = (
                        ui.input(
                            label="Add custom table",
                            placeholder="Enter table name (e.g. SAPSR3.CUSTOM_TABLE)",
                        )
                        .props("dense")
                        .classes("flex-1")
                        .tooltip(get_tip("extract.add_custom_table"))
                    )

                    def add_custom_table():
                        table_name = new_table_input.value.strip()
                        if (
                            table_name
                            and table_name not in all_available_tables
                        ):
                            # Add to available options
                            all_available_tables.append(table_name)
                            all_available_tables.sort()
                            tables_select.options = all_available_tables

                            # Add to current selection
                            current_selection = sapecc_config.get("tables", [])
                            if table_name not in current_selection:
                                current_selection.append(table_name)
                                sapecc_config["tables"] = current_selection
                                tables_select.value = current_selection
                                mark_changed()

                            # Clear input
                            new_table_input.value = ""
                            ui.notify(
                                f"Added table: {table_name}", type="positive"
                            )
                        elif table_name in all_available_tables:
                            ui.notify(
                                f"Table '{table_name}' already exists",
                                type="warning",
                            )
                        else:
                            ui.notify(
                                "Please enter a table name", type="warning"
                            )

                    ui.button(
                        "Add Table", icon="add", on_click=add_custom_table
                    ).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

                ui.label(
                    "You can add custom tables not in the default list above."
                ).classes("text-sm text-gray-600 mt-2")

    def render_generic_odbc_config(extract_config):
        """Render Generic ODBC adapter configuration."""
        generic_odbc_config = extract_config.setdefault("genericodbc", {})

        ui.label("Generic ODBC Configuration").classes(
            "text-lg font-semibold mb-3"
        )
        ui.label(
            "Generic ODBC-based extraction from any ODBC-compatible database"
        ).classes("text-sm mb-4")

        with ui.column().classes("gap-4"):
            # ODBC Connection String
            def on_odbc_connstr_change(e):
                generic_odbc_config["odbc_connstr"] = e.value
                mark_changed()

            ui.textarea(
                label="ODBC Connection String",
                value=generic_odbc_config.get(
                    "odbc_connstr",
                    "Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                ),
                placeholder="Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                on_change=on_odbc_connstr_change,
            ).props("outlined").classes("w-full").style("min-height: 100px").tooltip(get_tip("extract.adapter.odbc_connstr"))

            # Options row
            with ui.row().classes("gap-4"):
                # Chunk Size
                def on_chunk_size_change(e):
                    try:
                        generic_odbc_config["chunk_size"] = (
                            int(e.value) if e.value else 10000
                        )
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Chunk Size",
                    value=generic_odbc_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

                # Timeout
                def on_timeout_change(e):
                    try:
                        generic_odbc_config["timeout"] = (
                            int(e.value) if e.value else 300
                        )
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Timeout (seconds)",
                    value=generic_odbc_config.get("timeout", 300),
                    min=1,
                    on_change=on_timeout_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.timeout"))

                # Table Prefix (optional for generic ODBC)
                def on_table_prefix_change(e):
                    generic_odbc_config["table_prefix"] = e.value
                    mark_changed()
            
                ui.input(
                    label="Table Prefix (optional)",
                    value=generic_odbc_config.get("table_prefix", ""),
                    placeholder="e.g. schema_name.",
                    on_change=on_table_prefix_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.table_prefix"))
            

            # Table Selector
            def on_table_selector_change(e):
                generic_odbc_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=generic_odbc_config.get(
                        "table_selector", "join_parser"
                    ),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
            current_tables = generic_odbc_config.get("tables", [])

            with ui.column().classes("w-full") as tables_container:
                ui.label("Tables").classes("text-base font-medium mt-2")

                def on_tables_change(e):
                    generic_odbc_config["tables"] = e.value if e.value else []
                    mark_changed()

                default_generic_odbc_tables = (
                    global_config.get("extract", {})
                    .get("genericodbc", {})
                    .get("tables", [])
                )

                # Combine default tables with any custom tables that might be in current _render_tables_pickerconfig
                all_available_tables = list(
                    set(default_generic_odbc_tables + current_tables)
                )
                all_available_tables.sort()

                tables_select = (
                    ui.select(
                        options=all_available_tables,
                        value=current_tables,
                        multiple=True,
                        label="Select tables to extract",
                        on_change=on_tables_change,
                    )
                    .props("use-chips clearable use-input input-debounce=0")
                    .classes("w-full")
                    .tooltip(get_tip("extract.tables_select"))
                )

                # Add custom table functionality
                with ui.row().classes("gap-2 items-end mt-3"):
                    new_table_input = (
                        ui.input(
                            label="Add custom table",
                            placeholder="Enter table name (e.g. schema.table_name)",
                        )
                        .props("dense")
                        .classes("flex-1")
                        .tooltip(get_tip("extract.add_custom_table"))
                    )

                    def add_custom_table():
                        table_name = new_table_input.value.strip()
                        if (
                            table_name
                            and table_name not in all_available_tables
                        ):
                            # Add to available options
                            all_available_tables.append(table_name)
                            all_available_tables.sort()
                            tables_select.options = all_available_tables

                            # Add to current selection
                            current_selection = generic_odbc_config.get(
                                "tables", []
                            )
                            if table_name not in current_selection:
                                current_selection.append(table_name)
                                generic_odbc_config["tables"] = (
                                    current_selection
                                )
                                tables_select.value = current_selection
                                mark_changed()

                            # Clear input
                            new_table_input.value = ""
                            ui.notify(
                                f"Added table: {table_name}", type="positive"
                            )
                        elif table_name in all_available_tables:
                            ui.notify(
                                f"Table '{table_name}' already exists",
                                type="warning",
                            )
                        else:
                            ui.notify(
                                "Please enter a table name", type="warning"
                            )

                    ui.button(
                        "Add Table", icon="add", on_click=add_custom_table
                    ).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

                ui.label(
                    "You can add custom tables not in the default list above."
                ).classes("text-sm text-gray-600 mt-2")

    def render_saptenant_config(extract_config):
        """Render SAP Tenant adapter configuration."""
        saptenant_config = extract_config.setdefault("saptenant", {})

        ui.label("SAP Tenant Configuration").classes("text-lg font-semibold mb-3")
        ui.label("HANA DB-based extraction from SAP Tenant").classes(
            "text-sm mb-4"
        )

        with ui.column().classes("gap-4"):
            # Connection parameters row
            with ui.row().classes("gap-4 w-full"):
                # Address
                def on_address_change(e):
                    saptenant_config["address"] = e.value
                    mark_changed()

                ui.input(
                    label="Server Address",
                    value=saptenant_config.get("address", "your_sap_hana_server"),
                    placeholder="your_sap_hana_server",
                    on_change=on_address_change,
                ).props("dense").classes("flex-grow").tooltip(get_tip("extract.sap.server_address"))

                # Port
                def on_port_change(e):
                    try:
                        port = int(e.value) if e.value else 31015
                        if 1 <= port <= 65535:
                            saptenant_config["port"] = port
                            mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Port",
                    value=saptenant_config.get("port", 31015),
                    min=1,
                    max=65535,
                    on_change=on_port_change,
                ).props("dense").classes("w-32").tooltip(get_tip("extract.sap.port"))

            # Credentials row
            with ui.row().classes("gap-4 w-full"):
                # Username
                def on_user_change(e):
                    saptenant_config["user"] = e.value
                    mark_changed()

                ui.input(
                    label="Username",
                    value=saptenant_config.get("user", "your_username"),
                    placeholder="your_username",
                    on_change=on_user_change,
                ).props("dense").classes("flex-1").tooltip(get_tip("extract.sap.username"))

                # Password
                def on_password_change(e):
                    saptenant_config["password"] = e.value
                    mark_changed()

                ui.input(
                    label="Password",
                    value=saptenant_config.get("password", "your_password"),
                    placeholder="your_password",
                    password=True,
                    password_toggle_button=True,
                    on_change=on_password_change,
                ).props("dense").classes("flex-1").tooltip(get_tip("extract.sap.password"))

            # Settings row
            with ui.row().classes("gap-4 items-center"):
                # Autocommit
                def on_autocommit_change(e):
                    saptenant_config["autocommit"] = e.value
                    mark_changed()

                ui.switch(
                    "Autocommit",
                    value=saptenant_config.get("autocommit", True),
                    on_change=on_autocommit_change,
                ).tooltip(get_tip("extract.sap.autocommit"))

                # Chunk Size
                def on_chunk_size_change(e):
                    try:
                        saptenant_config["chunk_size"] = int(e.value) if e.value else 10000
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Chunk Size",
                    value=saptenant_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

            # Table Selector
            def on_table_selector_change(e):
                saptenant_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=saptenant_config.get("table_selector", "join_parser"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
            current_tables = saptenant_config.get("tables", [])

            with ui.column().classes("w-full") as tables_container:
                ui.label("Tables").classes("text-base font-medium mt-2")

                def on_tables_change(e):
                    saptenant_config["tables"] = e.value if e.value else []
                    mark_changed()

                default_saptenant_tables = (
                    global_config.get("extract", {})
                    .get("saptenant", {})
                    .get("tables", [])
                )

                # Combine default tables with any custom tables that might be in current config
                all_available_tables = list(
                    set(default_saptenant_tables + current_tables)
                )
                all_available_tables.sort()

                tables_select = (
                    ui.select(
                        options=all_available_tables,
                        value=current_tables,
                        multiple=True,
                        label="Select tables to extract",
                        on_change=on_tables_change,
                    )
                    .props("use-chips clearable use-input input-debounce=0")
                    .classes("w-full")
                    .tooltip(get_tip("extract.tables_select"))
                )

                # Add custom table functionality
                with ui.row().classes("gap-2 items-end mt-3"):
                    new_table_input = (
                        ui.input(
                            label="Add custom table",
                            placeholder="Enter table name (e.g. SAPSR3.CUSTOM_TABLE)",
                        )
                        .props("dense")
                        .classes("flex-1")
                        .tooltip(get_tip("extract.add_custom_table"))
                    )

                    def add_custom_table():
                        table_name = new_table_input.value.strip()
                        if table_name and table_name not in all_available_tables:
                            all_available_tables.append(table_name)
                            all_available_tables.sort()
                            tables_select.options = all_available_tables

                            current_selection = saptenant_config.get(
                                "tables", []
                            )
                            if table_name not in current_selection:
                                current_selection.append(table_name)
                                saptenant_config["tables"] = current_selection
                                tables_select.value = current_selection
                                mark_changed()

                            new_table_input.value = ""
                            ui.notify(
                                f"Added table: {table_name}", type="positive"
                            )
                        elif table_name in all_available_tables:
                            ui.notify(
                                f"Table '{table_name}' already exists",
                                type="warning",
                            )
                        else:
                            ui.notify(
                                "Please enter a table name", type="warning"
                            )

                    ui.button(
                        "Add Table", icon="add", on_click=add_custom_table
                    ).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

                ui.label(
                    "You can add custom tables not in the default list above."
                ).classes("text-sm text-gray-600 mt-2")

    def render_sapsystemdb_config(extract_config):
        """Render SAP SystemDB adapter configuration."""
        sapsystemdb_config = extract_config.setdefault("sapsystemdb", {})

        ui.label("SAP SystemDB Configuration").classes("text-lg font-semibold mb-3")
        ui.label("HANA DB-based extraction from SAP SystemDB").classes(
            "text-sm mb-4"
        )

        with ui.column().classes("gap-4"):
            # Connection parameters row
            with ui.row().classes("gap-4 w-full"):
                # Address
                def on_address_change(e):
                    sapsystemdb_config["address"] = e.value
                    mark_changed()

                ui.input(
                    label="Server Address",
                    value=sapsystemdb_config.get("address", "your_sap_hana_server"),
                    placeholder="your_sap_hana_server",
                    on_change=on_address_change,
                ).props("dense").classes("flex-grow").tooltip(get_tip("extract.sap.server_address"))

                # Database Name
                def on_database_name_change(e):
                    sapsystemdb_config["database_name"] = e.value
                    mark_changed()

                ui.input(
                    label="Database Name",
                    value=sapsystemdb_config.get("database_name", "your_databasename"),
                    placeholder="your_databasename",
                    on_change=on_database_name_change,
                ).props("dense").classes("flex-grow").tooltip(get_tip("extract.sap.database_name"))

                # Port
                def on_port_change(e):
                    try:
                        port = int(e.value) if e.value else 31013
                        if 1 <= port <= 65535:
                            sapsystemdb_config["port"] = port
                            mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Port",
                    value=sapsystemdb_config.get("port", 31013),
                    min=1,
                    max=65535,
                    on_change=on_port_change,
                ).props("dense").classes("w-32").tooltip(get_tip("extract.sap.port"))

            # Credentials row
            with ui.row().classes("gap-4 w-full"):
                # Username
                def on_user_change(e):
                    sapsystemdb_config["user"] = e.value
                    mark_changed()

                ui.input(
                    label="Username",
                    value=sapsystemdb_config.get("user", "your_username"),
                    placeholder="your_username",
                    on_change=on_user_change,
                ).props("dense").classes("flex-1").tooltip(get_tip("extract.sap.username"))

                # Password
                def on_password_change(e):
                    sapsystemdb_config["password"] = e.value
                    mark_changed()

                ui.input(
                    label="Password",
                    value=sapsystemdb_config.get("password", "your_password"),
                    placeholder="your_password",
                    password=True,
                    password_toggle_button=True,
                    on_change=on_password_change,
                ).props("dense").classes("flex-1").tooltip(get_tip("extract.sap.password"))

            # Settings row
            with ui.row().classes("gap-4 items-center"):
                # Autocommit
                def on_autocommit_change(e):
                    sapsystemdb_config["autocommit"] = e.value
                    mark_changed()

                ui.switch(
                    "Autocommit",
                    value=sapsystemdb_config.get("autocommit", True),
                    on_change=on_autocommit_change,
                ).tooltip(get_tip("extract.sap.autocommit"))

                # Chunk Size
                def on_chunk_size_change(e):
                    try:
                        sapsystemdb_config["chunk_size"] = int(e.value) if e.value else 10000
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Chunk Size",
                    value=sapsystemdb_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

            # Table Selector
            def on_table_selector_change(e):
                sapsystemdb_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=sapsystemdb_config.get("table_selector", "join_parser"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
            current_tables = sapsystemdb_config.get("tables", [])

            with ui.column().classes("w-full") as tables_container:
                ui.label("Tables").classes("text-base font-medium mt-2")

                def on_tables_change(e):
                    sapsystemdb_config["tables"] = e.value if e.value else []
                    mark_changed()

                default_tables = (
                    global_config.get("extract", {})
                    .get("sapsystemdb", {})
                    .get("tables", [])
                )

                # Combine default tables with any custom tables that might be in current config
                all_available_tables = list(
                    set(default_tables + current_tables)
                )
                all_available_tables.sort()

                tables_select = (
                    ui.select(
                        options=all_available_tables,
                        value=current_tables,
                        multiple=True,
                        label="Select tables to extract",
                        on_change=on_tables_change,
                    )
                    .props("use-chips clearable use-input input-debounce=0")
                    .classes("w-full")
                    .tooltip(get_tip("extract.tables_select"))
                )

                # Add custom table functionality
                with ui.row().classes("gap-2 items-end mt-3"):
                    new_table_input = (
                        ui.input(
                            label="Add custom table",
                            placeholder="Enter table name (e.g. SAPDB.CUSTOM_TABLE)",
                        )
                        .props("dense")
                        .classes("flex-1")
                        .tooltip(get_tip("extract.add_custom_table"))
                    )

                    def add_custom_table():
                        table_name = new_table_input.value.strip()
                        if table_name and table_name not in all_available_tables:
                            all_available_tables.append(table_name)
                            all_available_tables.sort()
                            tables_select.options = all_available_tables

                            current_selection = sapsystemdb_config.get(
                                "tables", []
                            )
                            if table_name not in current_selection:
                                current_selection.append(table_name)
                                sapsystemdb_config["tables"] = current_selection
                                tables_select.value = current_selection
                                mark_changed()

                            new_table_input.value = ""
                            ui.notify(
                                f"Added table: {table_name}", type="positive"
                            )
                        elif table_name in all_available_tables:
                            ui.notify(
                                f"Table '{table_name}' already exists",
                                type="warning",
                            )
                        else:
                            ui.notify(
                                "Please enter a table name", type="warning"
                            )

                    ui.button(
                        "Add Table", icon="add", on_click=add_custom_table
                    ).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

                ui.label(
                    "You can add custom tables not in the default list above."
                ).classes("text-sm text-gray-600 mt-2")

    def render_proalpha_config(extract_config):
        """Render ProAlpha adapter configuration."""
        proalpha_config = extract_config.setdefault("proalpha", {})

        ui.label("ProAlpha Configuration").classes("text-lg font-semibold mb-3")
        ui.label("ODBC-based extraction from ProAlpha ERP system").classes(
            "text-sm mb-4"
        )

        with ui.column().classes("gap-4"):
            # ODBC Connection String
            def on_odbc_connstr_change(e):
                proalpha_config["odbc_connstr"] = e.value
                mark_changed()

            ui.textarea(
                label="ODBC Connection String",
                value=proalpha_config.get(
                    "odbc_connstr",
                    "Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                ),
                placeholder="Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                on_change=on_odbc_connstr_change,
            ).props("outlined").classes("w-full").style("min-height: 100px").tooltip(get_tip("extract.adapter.odbc_connstr"))

            # Options row
            with ui.row().classes("gap-4"):
                # Chunk Size
                def on_chunk_size_change(e):
                    try:
                        proalpha_config["chunk_size"] = (
                            int(e.value) if e.value else 10000
                        )
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Chunk Size",
                    value=proalpha_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

                # Timeout
                def on_timeout_change(e):
                    try:
                        proalpha_config["timeout"] = (
                            int(e.value) if e.value else 300
                        )
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Timeout (seconds)",
                    value=proalpha_config.get("timeout", 300),
                    min=1,
                    on_change=on_timeout_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.timeout"))

                # Table Prefix
                def on_table_prefix_change(e):
                    proalpha_config["table_prefix"] = e.value
                    mark_changed()
            
                ui.input(
                    label="Table Prefix",
                    value=proalpha_config.get("table_prefix", "PROALPHA."),
                    placeholder="PROALPHA.",
                    on_change=on_table_prefix_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.table_prefix"))
            

            # Table Selector
            def on_table_selector_change(e):
                proalpha_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=proalpha_config.get("table_selector", "join_parser"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
            current_tables = proalpha_config.get("tables", [])

            with ui.column().classes("w-full") as tables_container:
                ui.label("Tables").classes("text-base font-medium mt-2")

                def on_tables_change(e):
                    proalpha_config["tables"] = e.value if e.value else []
                    mark_changed()

                default_proalpha_tables = (
                    global_config.get("extract", {})
                    .get("proalpha", {})
                    .get("tables", [])
                )

                # Combine default tables with any custom tables that might be in current config
                all_available_tables = list(
                    set(default_proalpha_tables + current_tables)
                )
                all_available_tables.sort()

                tables_select = (
                    ui.select(
                        options=all_available_tables,
                        value=current_tables,
                        multiple=True,
                        label="Select tables to extract",
                        on_change=on_tables_change,
                    )
                    .props("use-chips clearable use-input input-debounce=0")
                    .classes("w-full")
                    .tooltip(get_tip("extract.tables_select"))
                )

                # Add custom table functionality
                with ui.row().classes("gap-2 items-end mt-3"):
                    new_table_input = (
                        ui.input(
                            label="Add custom table",
                            placeholder="Enter table name (e.g. PROALPHA.CUSTOM_TABLE)",
                        )
                        .props("dense")
                        .classes("flex-1")
                        .tooltip(get_tip("extract.add_custom_table"))
                    )

                    def add_custom_table():
                        table_name = new_table_input.value
                        if table_name and table_name.strip():
                            table_name = table_name.strip()
                            if table_name not in all_available_tables:
                                # Add to the options list
                                all_available_tables.append(table_name)
                                all_available_tables.sort()
                                tables_select.options = all_available_tables

                                # Add to current selection
                                current_selection = proalpha_config.get(
                                    "tables", []
                                )
                                if table_name not in current_selection:
                                    current_selection.append(table_name)
                                    proalpha_config["tables"] = (
                                        current_selection
                                    )
                                    tables_select.value = current_selection
                                    mark_changed()

                                ui.notify(
                                    f"Added table: {table_name}",
                                    type="positive",
                                )
                                new_table_input.value = ""
                            else:
                                ui.notify(
                                    f"Table {table_name} already exists",
                                    type="warning",
                                )
                        else:
                            ui.notify(
                                "Please enter a table name", type="warning"
                            )

                    ui.button(
                        "Add Table", icon="add", on_click=add_custom_table
                    ).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

                ui.label(
                    "You can add custom tables not in the default list above."
                ).classes("text-sm text-gray-600 mt-2")

    def render_sagekhk_config(extract_config):
        """Render Sage KHK adapter configuration."""
        sagekhk_config = extract_config.setdefault("sagekhk", {})

        ui.label("Sage KHK Configuration").classes("text-lg font-semibold mb-3")
        ui.label("ODBC-based extraction from Sage KHK ERP system").classes(
            "text-sm mb-4"
        )

        with ui.column().classes("gap-4"):
            # ODBC Connection String
            def on_odbc_connstr_change(e):
                sagekhk_config["odbc_connstr"] = e.value
                mark_changed()

            ui.textarea(
                label="ODBC Connection String",
                value=sagekhk_config.get(
                    "odbc_connstr",
                    "Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                ),
                placeholder="Driver={SQL Server};Server=your_server;Database=your_database;UID=your_username;PWD=your_password;",
                on_change=on_odbc_connstr_change,
            ).props("outlined").classes("w-full").style("min-height: 100px").tooltip(get_tip("extract.adapter.odbc_connstr"))

            # Options row
            with ui.row().classes("gap-4"):
                # Chunk Size
                def on_chunk_size_change(e):
                    try:
                        sagekhk_config["chunk_size"] = (
                            int(e.value) if e.value else 10000
                        )
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Chunk Size",
                    value=sagekhk_config.get("chunk_size", 10000),
                    min=1,
                    on_change=on_chunk_size_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.chunk_size"))

                # Timeout
                def on_timeout_change(e):
                    try:
                        sagekhk_config["timeout"] = (
                            int(e.value) if e.value else 300
                        )
                        mark_changed()
                    except ValueError:
                        pass

                ui.number(
                    label="Timeout (seconds)",
                    value=sagekhk_config.get("timeout", 300),
                    min=1,
                    on_change=on_timeout_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.timeout"))

                # Table Prefix
                def on_table_prefix_change(e):
                    sagekhk_config["table_prefix"] = e.value
                    mark_changed()
            
                ui.input(
                    label="Table Prefix",
                    value=sagekhk_config.get("table_prefix", "SAGE."),
                    placeholder="SAGE.",
                    on_change=on_table_prefix_change,
                ).props("dense").classes("w-48").tooltip(get_tip("extract.table_prefix"))
            

            # Table Selector
            def on_table_selector_change(e):
                sagekhk_config["table_selector"] = e.value
                mark_changed()

            table_selector = (
                ui.select(
                    label="Table Selector",
                    options=["all", "join_parser"],
                    value=sagekhk_config.get("table_selector", "join_parser"),
                    on_change=on_table_selector_change,
                )
                .props("dense")
                .classes("w-48")
                .tooltip(get_tip("extract.table_selector"))
            )

            # Tables List (conditionally visible)
            current_tables = sagekhk_config.get("tables", [])

            with ui.column().classes("w-full") as tables_container:
                ui.label("Tables").classes("text-base font-medium mt-2")

                def on_tables_change(e):
                    sagekhk_config["tables"] = e.value if e.value else []
                    mark_changed()

                default_sagekhk_tables = (
                    global_config.get("extract", {})
                    .get("sagekhk", {})
                    .get("tables", [])
                )

                # Combine default tables with any custom tables that might be in current config
                all_available_tables = list(
                    set(default_sagekhk_tables + current_tables)
                )
                all_available_tables.sort()

                tables_select = (
                    ui.select(
                        options=all_available_tables,
                        value=current_tables,
                        multiple=True,
                        label="Select tables to extract",
                        on_change=on_tables_change,
                    )
                    .props("use-chips clearable use-input input-debounce=0")
                    .classes("w-full")
                    .tooltip(get_tip("extract.tables_select"))
                )

                # Add custom table functionality
                with ui.row().classes("gap-2 items-end mt-3"):
                    new_table_input = (
                        ui.input(
                            label="Add custom table",
                            placeholder="Enter table name (e.g. SAGE.table_name)",
                        )
                        .props("dense")
                        .classes("flex-1")
                        .tooltip(get_tip("extract.add_custom_table"))
                    )

                    def add_custom_table():
                        table_name = new_table_input.value.strip()
                        if (
                            table_name
                            and table_name not in all_available_tables
                        ):
                            # Add to available options
                            all_available_tables.append(table_name)
                            all_available_tables.sort()
                            tables_select.options = all_available_tables

                            # Add to current selection
                            current_selection = sagekhk_config.get("tables", [])
                            if table_name not in current_selection:
                                current_selection.append(table_name)
                                sagekhk_config["tables"] = current_selection
                                tables_select.value = current_selection
                                mark_changed()

                            # Clear input
                            new_table_input.value = ""
                            ui.notify(
                                f"Added table: {table_name}", type="positive"
                            )
                        elif table_name in all_available_tables:
                            ui.notify(
                                f"Table '{table_name}' already exists",
                                type="warning",
                            )
                        else:
                            ui.notify(
                                "Please enter a table name", type="warning"
                            )

                    ui.button(
                        "Add Table", icon="add", on_click=add_custom_table
                    ).props("size=sm color=primary").tooltip(get_tip("extract.add_custom_table_apply"))

                ui.label(
                    "You can add custom tables not in the default list above."
                ).classes("text-sm text-gray-600 mt-2")

    # --- refreshable panel for Extract -> File Configuration ---
    @ui.refreshable
    def extract_file_config_panel() -> None:
        extract_config = form_state["data"].setdefault("extract", {})
        extract_active = extract_config.get("active", True)
        file_config = extract_config.setdefault("file", [])

        with ui.card().classes("mt-4"):
            ui.label("File Configuration").classes("text-lg font-semibold mb-2")
            ui.label(
                "Configure manual file imports for the extraction process. File paths are automatically validated for existence."
            ).classes("text-sm mb-4")

            def validate_file_path(file_path: str, status_element) -> None:
                import os
                if file_path and str(file_path).strip():
                    try:
                        if os.path.isfile(file_path):
                            status_element.text = "✓ File exists"
                            status_element.classes(remove="text-red-500")
                            status_element.classes("text-green-600")
                        else:
                            status_element.text = "⚠ File not found"
                            status_element.classes(remove="text-green-600")
                            status_element.classes("text-red-500")
                    except (OSError, PermissionError):
                        status_element.text = "⚠ Cannot access file"
                        status_element.classes(remove="text-green-600")
                        status_element.classes("text-red-500")
                else:
                    status_element.text = ""
                    status_element.classes(remove="text-green-600 text-red-500")

            def remove_file_config(idx: int) -> None:
                if 0 <= idx < len(file_config):
                    removed = file_config.pop(idx)
                    mark_changed()
                    # notify BEFORE refresh to avoid "slot deleted" errors
                    ui.notify(
                        f"Removed file configuration: {removed.get('project', '')}",
                        type="info",
                    )
                    extract_file_config_panel.refresh()

            # Display existing files
            for i, file_item in enumerate(file_config):
                with ui.card().classes("mt-2 p-3"):
                    with ui.row().classes("items-center justify-between w-full"):
                        import os

                        file_display = (
                            f"{file_item.get('project', 'No Project')} - "
                            f"{os.path.basename(file_item.get('file_path', 'No Path'))}"
                        )
                        file_label = ui.label(f"File: {file_display}").classes("font-medium")
                        if not extract_active:
                            file_label.classes("text-gray-500")

                        delete_button = ui.button(
                            icon="delete",
                            on_click=lambda e, idx=i: remove_file_config(idx),
                        ).props("flat round color=negative size=sm")
                        if not extract_active:
                            delete_button.props("disable")

                    with ui.column().classes("gap-2 w-full"):
                        def on_file_active_change(e, idx=i):
                            file_config[idx]["active"] = e.value
                            mark_changed()

                        file_active_switch = ui.switch(
                            "Active",
                            value=file_item.get("active", True),
                            on_change=lambda e, idx=i: on_file_active_change(e, idx),
                        )
                        file_active_switch.tooltip(get_tip("extract.file.active"))
                        if not extract_active:
                            file_active_switch.props("disable")

                        def on_project_change(e, idx=i):
                            file_config[idx]["project"] = e.value
                            mark_changed()

                        project_input = (
                            ui.input(
                                label="Project",
                                value=file_item.get("project", ""),
                                placeholder="Enter project name",
                                on_change=lambda e, idx=i: on_project_change(e, idx),
                            )
                            .props("dense")
                            .classes("w-full")
                            .tooltip(get_tip("extract.file.project"))
                        )
                        if not extract_active:
                            project_input.props("disable")

                        def on_file_path_change(e, idx=i):
                            file_config[idx]["file_path"] = e.value
                            mark_changed()
                            validate_file_path(e.value, file_status_label)

                        file_path_input = (
                            ui.input(
                                label="File Path",
                                value=file_item.get("file_path", ""),
                                placeholder="e.g. /path/to/your/file.csv",
                                on_change=lambda e, idx=i: on_file_path_change(e, idx),
                            )
                            .props("dense")
                            .classes("w-full")
                            .tooltip(get_tip("extract.file.path"))
                        )
                        if not extract_active:
                            file_path_input.props("disable")

                        file_status_label = ui.label("").classes("text-sm")

                        # File format settings
                        with ui.row().classes("gap-4 mt-3"):
                            def on_separator_change(e, idx=i):
                                file_config[idx]["separator"] = e.value
                                mark_changed()

                            separator_options = {
                                ";": "Semicolon (;)",
                                ",": "Comma (,)",
                                "\t": "Tab (\\t)",
                                "|": "Pipe (|)",
                                ":": "Colon (:)",
                                " ": "Space ( )",
                            }

                            separator_input = (
                                ui.select(
                                    label="Separator",
                                    options=separator_options,
                                    value=file_item.get("separator", ";"),
                                    on_change=lambda e, idx=i: on_separator_change(e, idx),
                                )
                                .props("dense")
                                .classes("w-40")
                                .tooltip(get_tip("extract.file.separator"))
                            )
                            if not extract_active:
                                separator_input.props("disable")

                            def on_quote_change(e, idx=i):
                                file_config[idx]["quote"] = e.value
                                mark_changed()

                            quote_input = (
                                ui.input(
                                    label="Quote",
                                    value=file_item.get("quote", '"'),
                                    placeholder='"',
                                    on_change=lambda e, idx=i: on_quote_change(e, idx),
                                )
                                .props("dense")
                                .classes("w-24")
                                .tooltip(get_tip("extract.file.quote"))
                            )
                            if not extract_active:
                                quote_input.props("disable")

                            def on_dateformat_change(e, idx=i):
                                file_config[idx]["dateformat"] = e.value
                                mark_changed()

                            dateformat_input = (
                                ui.input(
                                    label="Date format",
                                    value=file_item.get("dateformat", "%d-%m-%Y"),
                                    placeholder="%d-%m-%Y",
                                    on_change=lambda e, idx=i: on_dateformat_change(e, idx),
                                )
                                .props("dense")
                                .classes("w-32")
                                .tooltip(get_tip("extract.file.dateformat"))
                            )
                            if not extract_active:
                                dateformat_input.props("disable")

                            def on_encoding_change(e, idx=i):
                                file_config[idx]["encoding"] = e.value
                                mark_changed()

                            encoding_input = (
                                ui.select(
                                    label="Encoding",
                                    options=["utf-8", "utf-16", "latin-1", "CP1252"],
                                    value=file_item.get("encoding", "utf-8"),
                                    on_change=lambda e, idx=i: on_encoding_change(e, idx),
                                )
                                .props("dense")
                                .classes("w-32")
                                .tooltip(get_tip("extract.file.encoding"))
                            )
                            if not extract_active:
                                encoding_input.props("disable")

                        def on_header_change(e, idx=i):
                            file_config[idx]["header"] = e.value
                            mark_changed()

                        header_switch = ui.switch(
                            "File has header row",
                            value=file_item.get("header", True),
                            on_change=lambda e, idx=i: on_header_change(e, idx),
                        ).classes("mt-3")
                        header_switch.tooltip(get_tip("extract.file.header"))
                        if not extract_active:
                            header_switch.props("disable")

                        def on_all_varchar_change(e, idx=i):
                            file_config[idx]["all_varchar"] = e.value
                            mark_changed()

                        all_varchar_switch = ui.switch(
                            "Import all columns as text (VARCHAR) - recommended to avoid conversion errors",
                            value=file_item.get("all_varchar", True),
                            on_change=lambda e, idx=i: on_all_varchar_change(e, idx),
                        ).classes("mt-2")
                        all_varchar_switch.tooltip(get_tip("extract.file.all_varchar"))
                        if not extract_active:
                            all_varchar_switch.props("disable")

                        with ui.column().classes("w-full") as columns_container:

                            def add_column_chip(column_name, idx=i):
                                current_columns = file_config[idx].get("columns", [])
                                if column_name and column_name not in current_columns:
                                    current_columns.append(column_name)
                                    file_config[idx]["columns"] = current_columns
                                    mark_changed()
                                    render_columns_section(idx)

                            def remove_column_chip(column_name, idx=i):
                                current_columns = file_config[idx].get("columns", [])
                                if column_name in current_columns:
                                    current_columns.remove(column_name)
                                    file_config[idx]["columns"] = current_columns
                                    mark_changed()
                                    render_columns_section(idx)

                            def render_columns_section(idx=i):
                                current_columns = file_config[idx].get("columns", [])
                                columns_container.clear()
                                with columns_container:
                                    ui.label("Column Names").classes("text-sm font-medium mt-2 mb-2").tooltip(get_tip("extract.file.columns"))

                                    chips_container = ui.row().classes("gap-1 mb-2 flex-wrap")

                                    def update_chips():
                                        chips_container.clear()
                                        current_cols = file_config[idx].get("columns", [])
                                        with chips_container:
                                            for col_idx, column in enumerate(current_cols):

                                                def make_remove_handler(col_name):
                                                    def remove_handler():
                                                        remove_column_chip(col_name, idx)
                                                    return remove_handler

                                                def make_move_handlers(ci):
                                                    def move_left():
                                                        if ci > 0:
                                                            cols = file_config[idx].get("columns", [])
                                                            cols[ci], cols[ci - 1] = cols[ci - 1], cols[ci]
                                                            file_config[idx]["columns"] = cols
                                                            mark_changed()
                                                            update_chips()

                                                    def move_right():
                                                        cols = file_config[idx].get("columns", [])
                                                        if ci < len(cols) - 1:
                                                            cols[ci], cols[ci + 1] = cols[ci + 1], cols[ci]
                                                            file_config[idx]["columns"] = cols
                                                            mark_changed()
                                                            update_chips()

                                                    return move_left, move_right

                                                move_left, move_right = make_move_handlers(col_idx)

                                                with ui.row().classes("items-center gap-1"):
                                                    if col_idx > 0:
                                                        ui.button(icon="chevron_left", on_click=move_left).props(
                                                            "size=xs flat round"
                                                        ).classes("p-1")
                                                    else:
                                                        ui.element("div").classes("w-6")

                                                    chip = ui.chip(column, removable=True).classes("bg-blue-100 text-blue-800")
                                                    chip.on("remove", make_remove_handler(column))

                                                    if col_idx < len(current_cols) - 1:
                                                        ui.button(icon="chevron_right", on_click=move_right).props(
                                                            "size=xs flat round"
                                                        ).classes("p-1")
                                                    else:
                                                        ui.element("div").classes("w-6")

                                    update_chips()

                                    def on_add_column():
                                        if new_column_input.value and new_column_input.value.strip():
                                            new_columns = [
                                                col.strip()
                                                for col in new_column_input.value.split(",")
                                                if col.strip()
                                            ]
                                            for col in new_columns:
                                                current_columns = file_config[idx].get("columns", [])
                                                if col and col not in current_columns:
                                                    current_columns.append(col)
                                                    file_config[idx]["columns"] = current_columns
                                                    mark_changed()
                                            update_chips()
                                            new_column_input.value = ""

                                    def on_enter_key(e):
                                        on_add_column()

                                    new_column_input = ui.input(
                                        placeholder="Type column name and press Enter to add",
                                    ).props("dense").classes("w-full").tooltip(get_tip("extract.file.columns"))
                                    if not extract_active:
                                        new_column_input.props("disable")

                                    new_column_input.on("keydown.enter", on_enter_key)

                                    with ui.row().classes("gap-2 mt-2"):
                                        add_btn = ui.button("Add Column", icon="add", on_click=on_add_column).props("size=sm outline").tooltip(get_tip("extract.file.add_column"))
                                        if not extract_active:
                                            add_btn.props("disable")

                                    ui.label(
                                        "Type column names and press Enter or click 'Add Column'. Use ← → arrows to reorder columns."
                                    ).classes("text-sm text-gray-600 mt-1")

                            render_columns_section(i)

                        def check_file_config(idx=i):
                            file_item = file_config[idx]
                            file_path = file_item.get("file_path", "")

                            if not file_path:
                                ui.notify("File path is required for checking.", type="warning")
                                return

                            if not file_item.get("header", True) and not file_item.get("columns", []):
                                ui.notify("Column names are required when file has no header.", type="warning")
                                return

                            try:
                                import pandas as pd
                                import os

                                if not os.path.isfile(file_path):
                                    ui.notify(f"File not found: {file_path}", type="negative")
                                    return

                                detect_params = {
                                    "filepath_or_buffer": file_path,
                                    "sep": file_item.get("separator", ";"),
                                    "quotechar": file_item.get("quote", '"'),
                                    "encoding": file_item.get("encoding", "utf-8"),
                                    "nrows": 10,
                                    "header": None,
                                }
                                if file_item.get("all_varchar", True):
                                    detect_params.update({"dtype": str})

                                detect_df = pd.read_csv(**detect_params)
                                actual_cols = detect_df.shape[1]

                                issues = []
                                if not file_item.get("header", True):
                                    user_columns = file_item.get("columns", [])
                                    if user_columns:
                                        expected_cols = len(user_columns)
                                        if actual_cols != expected_cols:
                                            issues.append(
                                                f"Expected {expected_cols} columns (based on column list) but found {actual_cols} in file."
                                            )

                                if actual_cols > 0:
                                    first_col_values = detect_df.iloc[:, 0].astype(str)
                                    wrong_sep_indicators = []
                                    for sep_char in [";", ",", "\t", "|", ":"]:
                                        if sep_char != detect_params["sep"]:
                                            if any(sep_char in str(val) for val in first_col_values):
                                                wrong_sep_indicators.append(sep_char)
                                    if wrong_sep_indicators:
                                        issues.append(
                                            f"First column contains '{', '.join(wrong_sep_indicators)}' characters. Current separator '{detect_params['sep']}' might be incorrect."
                                        )

                                    if actual_cols == 1 and len(first_col_values) > 0:
                                        sample_val = str(first_col_values.iloc[0])
                                        potential_seps = [s for s in [";", ",", "\t", "|"] if s in sample_val]
                                        if potential_seps:
                                            issues.append(
                                                f"Only 1 column detected, but data contains '{', '.join(potential_seps)}'. Consider changing separator from '{detect_params['sep']}'."
                                            )

                                    if actual_cols > 1:
                                        empty_cols = []
                                        for col_idx in range(1, min(actual_cols, 5)):
                                            col_data = detect_df.iloc[:, col_idx]
                                            if col_data.isna().all() or (col_data.astype(str).str.strip() == "").all():
                                                empty_cols.append(col_idx)
                                        if len(empty_cols) >= 2:
                                            issues.append(
                                                f"Columns {empty_cols} appear to be empty. This might indicate incorrect separator."
                                            )

                                full_read_params = {
                                    "filepath_or_buffer": file_path,
                                    "sep": file_item.get("separator", ";"),
                                    "quotechar": file_item.get("quote", '"'),
                                    "encoding": file_item.get("encoding", "utf-8"),
                                    "nrows": 500,
                                }

                                if file_item.get("header", True):
                                    full_read_params["header"] = 0
                                else:
                                    full_read_params["header"] = None
                                    columns = file_item.get("columns", [])
                                    if columns and len(columns) == actual_cols:
                                        full_read_params["names"] = columns

                                final_df = pd.read_csv(**full_read_params)
                                final_rows, _ = final_df.shape

                                param_info = (
                                    f"sep='{full_read_params['sep']}', quote='{full_read_params['quotechar']}', encoding='{full_read_params['encoding']}'"
                                )

                                if issues:
                                    ui.notify(
                                        f"⚠️ File configuration issues detected: {actual_cols} actual columns. {'; '.join(issues)} ({param_info})",
                                        type="warning",
                                    )
                                else:
                                    column_preview = list(final_df.columns[:5])
                                    column_text = f"{column_preview}{' ...' if len(final_df.columns) > 5 else ''}"
                                    ui.notify(
                                        f"✅ File configuration appears correct: {actual_cols} columns detected, {final_rows} rows read. Columns: {column_text} ({param_info})",
                                        type="positive",
                                    )

                            except ImportError:
                                ui.notify("❌ pandas library not available for file checking.", type="negative")
                            except Exception as e:
                                import pandas as pd
                                if isinstance(e, pd.errors.EmptyDataError):
                                    ui.notify("❌ File is empty or has no valid data.", type="negative")
                                elif isinstance(e, pd.errors.ParserError):
                                    ui.notify(f"❌ Parse error: {str(e)}", type="negative")
                                elif isinstance(e, UnicodeDecodeError):
                                    ui.notify(f"❌ Encoding error: {str(e)}. Try a different encoding.", type="negative")
                                elif isinstance(e, FileNotFoundError):
                                    ui.notify(f"❌ File not found: {file_path}", type="negative")
                                elif isinstance(e, PermissionError):
                                    ui.notify(f"❌ Permission denied accessing file: {file_path}", type="negative")
                                else:
                                    ui.notify(f"❌ Error reading file: {str(e)}", type="negative")

                        check_button = (
                            ui.button(
                                "Check Configuration",
                                icon="check_circle",
                                on_click=lambda e, idx=i: check_file_config(idx),
                            )
                            .props("color=primary outline size=sm")
                            .classes("mt-3")
                            .tooltip(get_tip("extract.file.check_configuration"))
                        )
                        if not extract_active:
                            check_button.props("disable")

                        if file_item.get("file_path"):
                            validate_file_path(file_item.get("file_path"), file_status_label)

            # Add new file
            with ui.card().classes("mt-2 p-3 border-dashed"):
                ui.label("Add New File").classes("font-medium mb-2")

                new_file_data = {
                    "active": True,
                    "project": "",
                    "file_path": "",
                    "separator": ";",
                    "quote": '"',
                    "dateformat": "%d-%m-%Y",
                    "encoding": "utf-8",
                    "header": True,
                    "columns": [],
                    "all_varchar": True,
                }

                def on_new_project_change(e):
                    new_file_data["project"] = e.value

                def on_new_file_path_change(e):
                    new_file_data["file_path"] = e.value

                def on_new_separator_change(e):
                    new_file_data["separator"] = e.value

                def on_new_quote_change(e):
                    new_file_data["quote"] = e.value

                def on_new_dateformat_change(e):
                    new_file_data["dateformat"] = e.value

                def on_new_encoding_change(e):
                    new_file_data["encoding"] = e.value

                def on_new_header_change(e):
                    new_file_data["header"] = e.value

                def on_new_all_varchar_change(e):
                    new_file_data["all_varchar"] = e.value

                def on_new_columns_change(e):
                    columns = [col.strip() for col in e.value.split(",") if col.strip()]
                    new_file_data["columns"] = columns

                def add_file():
                    if new_file_data["project"] and new_file_data["file_path"]:
                        if not new_file_data["header"] and not new_file_data["columns"]:
                            ui.notify(
                                "Please provide column names when file has no header row.",
                                type="warning",
                            )
                            return

                        file_config.append(new_file_data.copy())
                        mark_changed()
                        ui.notify(f"Added file configuration: {new_file_data['project']}", type="positive")
                        extract_file_config_panel.refresh()
                    else:
                        ui.notify(
                            "Please provide both project name and file path.",
                            type="warning",
                        )

                with ui.column().classes("gap-2 w-full"):
                    new_project_input = ui.input(
                        label="Project",
                        placeholder="Enter project name",
                        on_change=on_new_project_change,
                    ).props("dense").classes("w-full")

                    new_path_input = ui.input(
                        label="File Path",
                        placeholder="e.g. /path/to/your/file.csv",
                        on_change=on_new_file_path_change,
                    ).props("dense").classes("w-full").tooltip(get_tip("extract.file.path"))

                    if not extract_active:
                        new_project_input.props("disable")
                        new_path_input.props("disable")

                    with ui.row().classes("gap-4 mt-3"):
                        sep_input = ui.input(
                            label="Separator",
                            value=new_file_data["separator"],
                            placeholder=";",
                            on_change=on_new_separator_change,
                        ).props("dense").classes("w-24").tooltip(get_tip("extract.file.separator"))
                        quote_input2 = ui.input(
                            label="Quote",
                            value=new_file_data["quote"],
                            placeholder='"',
                            on_change=on_new_quote_change,
                        ).props("dense").classes("w-24").tooltip(get_tip("extract.file.quote"))
                        datefmt_input2 = ui.input(
                            label="Date format",
                            value=new_file_data["dateformat"],
                            placeholder="%d-%m-%Y",
                            on_change=on_new_dateformat_change,
                        ).props("dense").classes("w-32").tooltip(get_tip("extract.file.dateformat"))
                        enc_input2 = ui.select(
                            label="Encoding",
                            options=["utf-8", "utf-16", "latin-1"],
                            value=new_file_data["encoding"],
                            on_change=on_new_encoding_change,
                        ).props("dense").classes("w-32").tooltip(get_tip("extract.file.encoding"))

                        if not extract_active:
                            sep_input.props("disable")
                            quote_input2.props("disable")
                            datefmt_input2.props("disable")
                            enc_input2.props("disable")

                    header_switch2 = ui.switch(
                        "File has header row",
                        value=new_file_data["header"],
                        on_change=on_new_header_change,
                    ).classes("mt-3")
                    header_switch2.tooltip(get_tip("extract.file.header"))
                    all_varchar_switch2 = ui.switch(
                        "Import all columns as text (VARCHAR) - recommended to avoid conversion errors",
                        value=new_file_data["all_varchar"],
                        on_change=on_new_all_varchar_change,
                    ).classes("mt-2")
                    all_varchar_switch2.tooltip(get_tip("extract.file.all_varchar"))

                    if not extract_active:
                        header_switch2.props("disable")
                        all_varchar_switch2.props("disable")

                    # NOTE: Like the original implementation, the new-file form does not dynamically show/hide
                    # the columns field when toggling "header". If you want that, we can make the form itself
                    # refreshable as well (or bind visibility reactively).
                    if not new_file_data["header"]:
                        columns_value = ", ".join(new_file_data["columns"])
                        cols_input2 = ui.input(
                            label="Column Names",
                            value=columns_value,
                            placeholder="column1, column2, column3",
                            on_change=on_new_columns_change,
                        ).props("dense").classes("w-full mt-2").tooltip(get_tip("extract.file.columns"))
                        if not extract_active:
                            cols_input2.props("disable")

                        ui.label(
                            "Enter column names separated by commas (required when file has no header)"
                        ).classes("text-sm text-gray-600 mt-1")

                    add_btn2 = ui.button("Add File", icon="add", on_click=add_file).props("color=primary").classes("mt-3").tooltip(get_tip("extract.file.add_file"))
                    if not extract_active:
                        add_btn2.props("disable")

    render_extract()
