# -*- coding: utf-8 -*-

"""
/***************************************************************************
 CSVGeometryExportDialog
 A QGIS plugin
 Export layers as CSV with custom geometry column names
 -------------------
 begin    : 2025-07-13
 copyright: (C) 2025 by Mirjan Ali Sha
 email    : mastools.help@gmail.com
 ***************************************************************************/
 /***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
"""

import os
import encodings
import pkgutil
import urllib.parse
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QCheckBox,
    QPushButton, QTableWidget, QTableWidgetItem, QHeaderView, QFileDialog,
    QMessageBox, QGroupBox, QComboBox)
from qgis.PyQt.QtCore import Qt, QSettings
from qgis.core import (
    QgsCoordinateReferenceSystem, Qgis, QgsVectorLayer, QgsProject, QgsFeature, QgsWkbTypes
)
from qgis.gui import QgsProjectionSelectionWidget

from .geometry_serializers import GeometrySerializer, GeometryFormat


class CSVGeometryExportDialog(QDialog):
    """
    Dialog for exporting vector layers as CSV with custom geometry column names
    """
    SEPARATOR_OPTIONS = {
        'Comma (,)': ',',
        'Semicolon (;)': ';',
        'Tab': '\t',
        'Space': ' '
    }

    def __init__(self, layer, parent=None):
        super().__init__(parent)
        self.layer = layer
        self.setup_ui()
        self.populate_fields()
        self.load_settings()
        self.update_selected_features_text()

    def get_all_available_encodings(self):
        try:
            available_encodings = []
            for _, modname, ispkg in pkgutil.iter_modules(encodings.__path__):
                if not ispkg and modname != 'aliases':
                    encoding_name = modname.replace('_', '-')
                    available_encodings.append(encoding_name)
            common_encodings = [
                'utf-8', 'utf-16', 'utf-32', 'ascii', 'latin1', 'iso-8859-1',
                'cp1252', 'windows-1252', 'big5', 'gb2312', 'gbk', 'gb18030',
                'shift-jis', 'euc-jp', 'euc-kr', 'koi8-r', 'koi8-u'
            ]
            all_encodings = sorted(set(available_encodings + common_encodings))
            return [enc.upper() for enc in all_encodings]
        except Exception as e:
            print(f"Warning: Could not get all encodings: {e}")
            return [
                'UTF-8', 'UTF-16', 'UTF-32', 'ASCII', 'LATIN1', 'ISO-8859-1',
                'CP1252', 'WINDOWS-1252', 'BIG5', 'GB2312', 'SHIFT-JIS'
            ]

    def setup_ui(self):
        self.setWindowTitle("Export Layer as CSV with Geometry")
        self.setModal(True)
        self.resize(700, 750)
        main_layout = QVBoxLayout()

        # Input Layer group
        input_group = QGroupBox("Input Layer")
        input_layout = QHBoxLayout()
        input_layout.addWidget(QLabel("Layer:"))
        self.input_layer_combo = QComboBox()
        self.input_layer_combo.setMinimumWidth(400)
        self.populate_layer_combo()
        self.input_layer_combo.currentIndexChanged.connect(
            self.on_input_layer_changed)
        input_layout.addWidget(self.input_layer_combo)
        self.browse_layer_button = QPushButton("...")
        self.browse_layer_button.setMaximumWidth(30)
        self.browse_layer_button.setToolTip("Browse for a vector file")
        self.browse_layer_button.clicked.connect(self.browse_input_layer)
        input_layout.addWidget(self.browse_layer_button)
        input_layout.addStretch()
        input_group.setLayout(input_layout)
        main_layout.addWidget(input_group)

        # Output File group
        file_group = QGroupBox("Output File")
        file_layout = QVBoxLayout()
        file_path_layout = QHBoxLayout()
        file_path_layout.addWidget(QLabel("File name:"))
        self.file_path_edit = QLineEdit()
        self.file_path_edit.setPlaceholderText("Select output CSV file...")
        self.browse_button = QPushButton("...")
        self.browse_button.setMaximumWidth(30)
        file_path_layout.addWidget(self.file_path_edit)
        file_path_layout.addWidget(self.browse_button)
        file_layout.addLayout(file_path_layout)

        separator_layout = QHBoxLayout()
        separator_layout.addWidget(QLabel("Separator:"))
        self.separator_combo = QComboBox()
        self.separator_combo.addItems(list(self.SEPARATOR_OPTIONS.keys()))
        self.separator_combo.setCurrentText('Comma (,)')
        separator_layout.addWidget(self.separator_combo)
        separator_layout.addStretch()
        file_layout.addLayout(separator_layout)

        encoding_layout = QHBoxLayout()
        encoding_layout.addWidget(QLabel("Encoding:"))
        self.encoding_combo = QComboBox()
        self.encoding_combo.setEditable(True)
        self.encoding_combo.addItems(self.get_all_available_encodings())
        encoding_layout.addWidget(self.encoding_combo)
        encoding_layout.addStretch()
        file_layout.addLayout(encoding_layout)

        file_group.setLayout(file_layout)
        main_layout.addWidget(file_group)

        # Geometry group
        geom_group = QGroupBox("Geometry Options")
        geom_layout = QVBoxLayout()

        # Geometry format dropdown
        format_layout = QHBoxLayout()
        format_layout.addWidget(QLabel("Geometry format:"))
        self.geometry_format_combo = QComboBox()
        self.geometry_format_combo.addItems(GeometryFormat.all_formats())
        self.geometry_format_combo.setCurrentText('WKT')  # Default to WKT
        self.geometry_format_combo.currentTextChanged.connect(
            self.on_format_changed)
        format_layout.addWidget(self.geometry_format_combo)
        format_layout.addStretch()
        geom_layout.addLayout(format_layout)

        # Format description label
        self.format_description_label = QLabel(
            GeometryFormat.get_description('WKT'))
        self.format_description_label.setStyleSheet(
            'color: #666; font-style: italic; margin-left: 10px;')
        geom_layout.addWidget(self.format_description_label)

        # Geometry column name
        geom_name_layout = QHBoxLayout()
        self.geom_column_label = QLabel("Geometry column name:")
        geom_name_layout.addWidget(self.geom_column_label)
        self.geometry_field_edit = QLineEdit("geometry")
        geom_name_layout.addWidget(self.geometry_field_edit)
        geom_layout.addLayout(geom_name_layout)

        # X-Y column names (hidden by default)
        xy_layout = QHBoxLayout()
        self.x_column_label = QLabel("X column name:")
        xy_layout.addWidget(self.x_column_label)
        self.x_column_edit = QLineEdit("longitude")
        xy_layout.addWidget(self.x_column_edit)
        self.y_column_label = QLabel("Y column name:")
        xy_layout.addWidget(self.y_column_label)
        self.y_column_edit = QLineEdit("latitude")
        xy_layout.addWidget(self.y_column_edit)
        geom_layout.addLayout(xy_layout)

        # Keep original geometry option (for X-Y format - hidden by default)
        orig_geom_layout = QHBoxLayout()
        self.keep_original_geom_checkbox = QCheckBox(
            "Keep original geometry column (WKT)")
        self.keep_original_geom_checkbox.setChecked(False)
        self.keep_original_geom_checkbox.toggled.connect(
            self.toggle_original_geom_name)
        orig_geom_layout.addWidget(self.keep_original_geom_checkbox)
        self.original_geom_name_label = QLabel("Column name:")
        orig_geom_layout.addWidget(self.original_geom_name_label)
        self.original_geom_name_edit = QLineEdit("org_geom")
        self.original_geom_name_edit.setMaximumWidth(120)
        orig_geom_layout.addWidget(self.original_geom_name_edit)
        orig_geom_layout.addStretch()
        geom_layout.addLayout(orig_geom_layout)

        # Keep original geometry checkbox - always visible
        # Column name input only visible when checked
        self.keep_original_geom_checkbox.setVisible(True)
        self.original_geom_name_label.setVisible(False)
        self.original_geom_name_edit.setVisible(False)

        # Initially hide X-Y options
        self.x_column_label.setVisible(False)
        self.x_column_edit.setVisible(False)
        self.y_column_label.setVisible(False)
        self.y_column_edit.setVisible(False)

        crs_layout = QHBoxLayout()
        crs_layout.addWidget(QLabel("CRS:"))
        self.crs_selector = QgsProjectionSelectionWidget()
        self.crs_selector.setCrs(self.layer.crs())
        self.crs_selector.setShowAccuracyWarnings(True)
        crs_layout.addWidget(self.crs_selector)
        geom_layout.addLayout(crs_layout)
        geom_group.setLayout(geom_layout)
        main_layout.addWidget(geom_group)

        # Fields group
        fields_group = QGroupBox("Select Fields to Export")
        fields_layout = QVBoxLayout()
        select_buttons_layout = QHBoxLayout()
        self.select_all_button = QPushButton("Select All")
        self.deselect_all_button = QPushButton("Deselect All")
        self.reset_names_button = QPushButton("Reset Export Names")
        select_buttons_layout.addWidget(self.select_all_button)
        select_buttons_layout.addWidget(self.deselect_all_button)
        select_buttons_layout.addWidget(self.reset_names_button)
        select_buttons_layout.addStretch()
        fields_layout.addLayout(select_buttons_layout)
        self.fields_table = QTableWidget()
        self.fields_table.setColumnCount(4)
        self.fields_table.setHorizontalHeaderLabels([
            "Export", "Field Name", "Export Name", "Type"
        ])
        header = self.fields_table.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
        header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
        header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
        header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
        fields_layout.addWidget(self.fields_table)
        fields_group.setLayout(fields_layout)
        main_layout.addWidget(fields_group)

        # Export options
        options_group = QGroupBox("Export Options")
        options_layout = QVBoxLayout()
        self.selected_only_checkbox = QCheckBox(
            "Export only selected features")
        self.selected_only_checkbox.setChecked(True)
        options_layout.addWidget(self.selected_only_checkbox)
        self.add_geometry_checkbox = QCheckBox("Add geometry column")
        self.add_geometry_checkbox.setChecked(True)
        options_layout.addWidget(self.add_geometry_checkbox)
        self.open_layer_checkbox = QCheckBox("Open result in QGIS Layer")
        self.open_layer_checkbox.setChecked(True)
        options_layout.addWidget(self.open_layer_checkbox)
        options_group.setLayout(options_layout)
        main_layout.addWidget(options_group)

        button_layout = QHBoxLayout()
        button_layout.addStretch()
        self.export_button = QPushButton("OK")
        self.export_button.setDefault(True)
        self.cancel_button = QPushButton("Cancel")
        button_layout.addWidget(self.export_button)
        button_layout.addWidget(self.cancel_button)
        main_layout.addLayout(button_layout)

        self.setLayout(main_layout)

        # Connect signals
        self.export_button.clicked.connect(self.export_layer)
        self.cancel_button.clicked.connect(self.reject)
        self.browse_button.clicked.connect(self.browse_file)
        self.select_all_button.clicked.connect(self.select_all_fields)
        self.deselect_all_button.clicked.connect(self.deselect_all_fields)
        self.reset_names_button.clicked.connect(self.reset_export_names)
        self.add_geometry_checkbox.toggled.connect(
            self.toggle_geometry_options)
        self.crs_selector.crsChanged.connect(self.validate_crs_selection)
        self.layer.selectionChanged.connect(self.update_selected_features_text)

    def get_selected_separator(self):
        selected_text = self.separator_combo.currentText()
        return self.SEPARATOR_OPTIONS.get(selected_text, ',')

    def get_separator_name_for_gdal(self):
        separator = self.get_selected_separator()
        separator_map = {
            ',': 'COMMA',
            ';': 'SEMICOLON',
            '\t': 'TAB',
            ' ': 'SPACE'
        }
        return separator_map.get(separator, 'COMMA')

    def populate_layer_combo(self):
        """Populate the input layer dropdown with all loaded vector layers"""
        from qgis.core import QgsProject, QgsVectorLayer

        self.input_layer_combo.clear()

        # Get all vector layers from the project
        layers = QgsProject.instance().mapLayers().values()
        vector_layers = [
            l for l in layers if isinstance(
                l, QgsVectorLayer) and l.isValid()]

        # Sort layers by name
        vector_layers.sort(key=lambda x: x.name())

        default_index = 0
        for i, layer in enumerate(vector_layers):
            # Store layer ID as item data
            self.input_layer_combo.addItem(layer.name(), layer.id())
            # Set the passed layer as default
            if self.layer and layer.id() == self.layer.id():
                default_index = i

        if self.input_layer_combo.count() > 0:
            self.input_layer_combo.setCurrentIndex(default_index)

    def on_input_layer_changed(self, index):
        """Handle input layer selection change"""
        from qgis.core import QgsProject

        if index < 0:
            return

        layer_id = self.input_layer_combo.itemData(index)
        if layer_id:
            new_layer = QgsProject.instance().mapLayer(layer_id)
            if new_layer and new_layer.isValid():
                self.layer = new_layer
                self.populate_fields()
                self.update_selected_features_text()
                # Update CRS selector to match new layer
                self.crs_selector.setCrs(self.layer.crs())

    def browse_input_layer(self):
        """Browse for a vector file to load"""
        from qgis.core import QgsVectorLayer, QgsProject

        settings = QSettings()
        last_dir = settings.value(
            "csv_geometry_export/last_input_dir",
            os.path.expanduser("~"))

        file_path, _ = QFileDialog.getOpenFileName(
            self,
            "Select Vector Layer",
            last_dir,
            "Vector Files (*.shp *.gpkg *.geojson *.kml *.gml *.json);;Shapefiles (*.shp);;GeoPackage (*.gpkg);;GeoJSON (*.geojson *.json);;KML (*.kml);;All files (*.*)"
        )

        if file_path:
            settings.setValue(
                "csv_geometry_export/last_input_dir",
                os.path.dirname(file_path))

            # Load the layer
            layer_name = os.path.splitext(os.path.basename(file_path))[0]
            new_layer = QgsVectorLayer(file_path, layer_name, "ogr")

            if new_layer.isValid():
                # Add to project
                QgsProject.instance().addMapLayer(new_layer)

                # Refresh dropdown and select the new layer
                self.layer = new_layer
                self.populate_layer_combo()

                # Find and select the new layer in combo
                for i in range(self.input_layer_combo.count()):
                    if self.input_layer_combo.itemData(i) == new_layer.id():
                        self.input_layer_combo.setCurrentIndex(i)
                        break
            else:
                QMessageBox.warning(
                    self,
                    "Invalid Layer",
                    f"Could not load the selected file as a valid vector layer:\n{file_path}"
                )

    def update_selected_features_text(self):
        selected_count = self.layer.selectedFeatureCount()
        if selected_count > 0:
            self.selected_only_checkbox.setText(
                f"Export only selected features ({selected_count})"
            )
            self.selected_only_checkbox.setEnabled(True)
        else:
            self.selected_only_checkbox.setText(
                "Export only selected features")
            self.selected_only_checkbox.setEnabled(False)
            self.selected_only_checkbox.setChecked(False)

    def populate_fields(self):
        fields = self.layer.fields()
        self.fields_table.setRowCount(len(fields))
        for i, field in enumerate(fields):
            checkbox = QCheckBox()
            checkbox.setChecked(True)
            self.fields_table.setCellWidget(i, 0, checkbox)
            name_item = QTableWidgetItem(field.name())
            name_item.setFlags(name_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
            self.fields_table.setItem(i, 1, name_item)
            export_name_item = QTableWidgetItem(field.name())
            self.fields_table.setItem(i, 2, export_name_item)
            type_item = QTableWidgetItem(field.typeName())
            type_item.setFlags(type_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
            self.fields_table.setItem(i, 3, type_item)

    def select_all_fields(self):
        for i in range(self.fields_table.rowCount()):
            checkbox = self.fields_table.cellWidget(i, 0)
            checkbox.setChecked(True)

    def deselect_all_fields(self):
        for i in range(self.fields_table.rowCount()):
            checkbox = self.fields_table.cellWidget(i, 0)
            checkbox.setChecked(False)

    def reset_export_names(self):
        fields = self.layer.fields()
        for i, field in enumerate(fields):
            export_name_item = QTableWidgetItem(field.name())
            self.fields_table.setItem(i, 2, export_name_item)

    def toggle_geometry_options(self, checked):
        """Toggle geometry options based on checkbox state"""
        self.geometry_format_combo.setEnabled(checked)
        self.format_description_label.setEnabled(checked)
        self.geometry_field_edit.setEnabled(checked)
        self.crs_selector.setEnabled(checked)
        self.x_column_edit.setEnabled(checked)
        self.y_column_edit.setEnabled(checked)

        # Update visibility based on format
        if checked:
            self.on_format_changed(self.geometry_format_combo.currentText())

    def on_format_changed(self, format_type):
        """Handle geometry format change"""
        # Update description label
        self.format_description_label.setText(
            GeometryFormat.get_description(format_type))

        # Show/hide X-Y options based on format
        is_xy = GeometrySerializer.requires_xy_columns(format_type)

        # X-Y column options
        self.x_column_label.setVisible(is_xy)
        self.x_column_edit.setVisible(is_xy)
        self.y_column_label.setVisible(is_xy)
        self.y_column_edit.setVisible(is_xy)

        # Keep original geometry checkbox - always visible for all formats
        # Update column name visibility based on checkbox state
        self.toggle_original_geom_name(
            self.keep_original_geom_checkbox.isChecked())

        # Single geometry column option
        self.geom_column_label.setVisible(not is_xy)
        self.geometry_field_edit.setVisible(not is_xy)

    def toggle_original_geom_name(self, checked):
        """Toggle original geometry column name input based on checkbox state"""
        self.original_geom_name_label.setVisible(checked)
        self.original_geom_name_edit.setVisible(checked)

    def validate_crs_selection(self):
        selected_crs = self.crs_selector.crs()
        layer_crs = self.layer.crs()
        if selected_crs != layer_crs:
            if not selected_crs.isValid():
                QMessageBox.warning(
                    self,
                    "Invalid CRS",
                    "The selected CRS is not valid. Please choose a valid coordinate reference system."
                )
                self.crs_selector.setCrs(layer_crs)

    def browse_file(self):
        settings = QSettings()
        last_dir = settings.value(
            "csv_geometry_export/last_dir",
            os.path.expanduser("~"))
        file_path, _ = QFileDialog.getSaveFileName(
            self,
            "Save CSV File",
            os.path.join(last_dir, f"{self.layer.name()}.csv").replace('\\', '/'),
            "CSV files (*.csv);;All files (*.*)"
        )
        if file_path:
            self.file_path_edit.setText(file_path)
            settings.setValue(
                "csv_geometry_export/last_dir",
                os.path.dirname(file_path))

    def get_selected_fields_with_export_names(self):
        selected = []
        for i in range(self.fields_table.rowCount()):
            checkbox = self.fields_table.cellWidget(i, 0)
            if checkbox.isChecked():
                export_name_item = self.fields_table.item(i, 2)
                export_name = export_name_item.text() if export_name_item else ""
                selected.append((i, export_name))
        return selected

    def export_layer(self):
        if not self.file_path_edit.text():
            QMessageBox.warning(
                self, "Warning", "Please select an output file.")
            return
        selected_fields = self.get_selected_fields_with_export_names()
        if not selected_fields and not self.add_geometry_checkbox.isChecked():
            QMessageBox.warning(
                self,
                "Warning",
                "Please select at least one field to export or include geometry.")
            return

        # Get geometry format
        geometry_format = self.geometry_format_combo.currentText()

        # Validate geometry column names based on format
        original_geom_col_name = None
        if self.add_geometry_checkbox.isChecked():
            if GeometrySerializer.requires_xy_columns(geometry_format):
                x_col_name = self.x_column_edit.text().strip()
                y_col_name = self.y_column_edit.text().strip()
                if not x_col_name or not y_col_name:
                    QMessageBox.warning(
                        self, "Warning", "Please provide X and Y column names.")
                    return
                geometry_field_name = (x_col_name, y_col_name)  # Tuple for X-Y
            else:
                geometry_field_name = self.geometry_field_edit.text().strip()
                if not geometry_field_name:
                    QMessageBox.warning(
                        self, "Warning", "Please provide a geometry column name.")
                    return

            # Get original geometry column name if checked (works for all
            # formats)
            if self.keep_original_geom_checkbox.isChecked():
                original_geom_col_name = self.original_geom_name_edit.text().strip()
                if not original_geom_col_name:
                    original_geom_col_name = "org_geom"
        else:
            geometry_field_name = None

        encoding = self.encoding_combo.currentText()
        if not encoding:
            QMessageBox.warning(
                self, "Warning", "Please select or enter an encoding.")
            return
        try:
            success, err_msg = self.perform_export(
                self.file_path_edit.text(),
                selected_fields,
                geometry_field_name if self.add_geometry_checkbox.isChecked() else None,
                encoding,
                geometry_format,
                original_geom_col_name
            )
            if success:
                self.save_settings()
                file_location = self.file_path_edit.text()
                file_url = f"file:///{file_location.replace(os.sep, '/')}"
                from qgis.utils import iface
                iface.messageBar().pushMessage(
                    "Layer Exported",
                    f'Successfully saved vector layer to: <a href="{file_url}">{file_location}</a>',
                    Qgis.MessageLevel.Success,
                    0
                )
                self.accept()
            else:
                QMessageBox.critical(self, "Error", err_msg)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Export failed: {str(e)}")

    def perform_export(self, output_path, selected_fields, geometry_field_name=None,
                       encoding="UTF-8", geometry_format="WKT", original_geom_col_name=None):
        import traceback
        import csv
        import sys
        from qgis.core import QgsProject, QgsCoordinateTransform

        try:
            layer = self.layer
            if not output_path or not layer or not layer.isValid():
                return False, "Invalid export parameters"

            separator = self.get_selected_separator()
            crs = self.crs_selector.crs()

            # Set CSV field size limit
            try:
                csv.field_size_limit(sys.maxsize)
            except Exception:
                csv.field_size_limit(2 ** 31 - 1)

            # For WKT format, we can use GDAL's native export (faster)
            if geometry_format == GeometryFormat.WKT and geometry_field_name and not isinstance(
                    geometry_field_name, tuple):
                return self._perform_wkt_export(
                    output_path, selected_fields, geometry_field_name, encoding)

            # For all other formats, use custom CSV writing
            # Build header
            header = []
            field_indices = []
            field_export_names = {}

            if selected_fields:
                for field_index, export_name in selected_fields:
                    original_name = layer.fields()[field_index].name()
                    header.append(
                        export_name if export_name else original_name)
                    field_indices.append(field_index)
                    field_export_names[field_index] = export_name if export_name else original_name

            # Add geometry column(s)
            is_xy = GeometrySerializer.requires_xy_columns(geometry_format)
            if geometry_field_name:
                if is_xy:
                    x_col_name, y_col_name = geometry_field_name
                    header.append(x_col_name)
                    header.append(y_col_name)
                else:
                    header.append(geometry_field_name)

                # Add original geometry column if requested (for all formats)
                if original_geom_col_name:
                    header.append(original_geom_col_name)

            # Get SRID for EWKT/EWKB formats
            srid = 4326  # Default
            if crs and crs.isValid():
                auth_id = crs.authid()
                if auth_id.startswith('EPSG:'):
                    try:
                        srid = int(auth_id.split(':')[1])
                    except ValueError:
                        pass

            # Setup coordinate transformation if needed
            transform = None
            if crs and crs.isValid() and crs != layer.crs():
                transform = QgsCoordinateTransform(
                    layer.crs(), crs, QgsProject.instance())

            # Get features
            if self.selected_only_checkbox.isChecked():
                features = layer.selectedFeatures()
            else:
                features = layer.getFeatures()

            # Write CSV
            with open(output_path, 'w', encoding=encoding, newline='') as outfile:
                writer = csv.writer(outfile, delimiter=separator)

                # Write header
                writer.writerow(header)

                # Write data rows
                for feature in features:
                    row = []

                    # Add attribute values
                    for field_index in field_indices:
                        value = feature.attribute(field_index)
                        row.append(str(value) if value is not None else '')

                    # Add geometry
                    if geometry_field_name:
                        geom = feature.geometry()

                        # Transform geometry if needed
                        if transform and geom and not geom.isNull():
                            geom.transform(transform)

                        if is_xy:
                            # For X-Y format, use centroid for non-point
                            # geometries
                            xy = GeometrySerializer.serialize(
                                geom, geometry_format, srid)
                            if xy and isinstance(xy, tuple):
                                row.append(str(xy[0]) if xy[0]
                                           is not None else '')
                                row.append(str(xy[1]) if xy[1]
                                           is not None else '')
                            else:
                                row.append('')
                                row.append('')
                        else:
                            geom_str = GeometrySerializer.serialize(
                                geom, geometry_format, srid)
                            row.append(geom_str if geom_str else '')

                        # Add original geometry WKT if requested (for all
                        # formats)
                        if original_geom_col_name:
                            wkt_str = geom.asWkt() if geom and not geom.isNull() else ''
                            row.append(wkt_str)

                    writer.writerow(row)

            if not os.path.exists(output_path):
                return False, f"The exported CSV file was not found at: {output_path}"

            # Open layer if requested
            if self.open_layer_checkbox.isChecked():
                self.open_exported_layer(output_path)

            return True, ""

        except Exception as e:
            return False, f"Export error: {str(e)}\n{traceback.format_exc()}"

    def _perform_wkt_export(
            self, output_path, selected_fields, geometry_field_name, encoding):
        """Use GDAL's native WKT export for better performance"""
        import traceback
        from qgis.core import QgsVectorFileWriter, QgsProject

        try:
            layer = self.layer
            separator = self.get_selected_separator()
            separator_name = self.get_separator_name_for_gdal()

            field_mapping = {}
            selected_field_indices = []
            if selected_fields:
                for field_index, export_name in selected_fields:
                    original_name = layer.fields()[field_index].name()
                    field_mapping[original_name] = export_name
                    selected_field_indices.append(field_index)

            save_options = QgsVectorFileWriter.SaveVectorOptions()
            save_options.driverName = "CSV"
            save_options.fileEncoding = encoding
            if selected_field_indices:
                save_options.attributes = selected_field_indices

            layer_options = [
                "GEOMETRY=AS_WKT",
                f"SEPARATOR={separator_name}",
                "STRING_QUOTING=NEVER"
            ]
            wkt_name = geometry_field_name if geometry_field_name and geometry_field_name.strip() else "geometry"
            layer_options.append(f"WKT_FIELD={wkt_name}")
            save_options.layerOptions = layer_options

            crs = self.crs_selector.crs()
            if crs and crs != layer.crs():
                save_options.destCRS = crs
            save_options.onlySelectedFeatures = self.selected_only_checkbox.isChecked()
            if field_mapping:
                save_options.fieldNameMap = field_mapping

            error = QgsVectorFileWriter.writeAsVectorFormatV3(
                layer,
                output_path,
                QgsProject.instance().transformContext(),
                save_options
            )

            result_code = error[0] if isinstance(
                error, (tuple, list)) else error
            if result_code != QgsVectorFileWriter.NoError:
                return False, f"Export failed with error code: {error}\nPath: {output_path}"

            if not os.path.exists(output_path):
                return False, f"The exported CSV file was not found at: {output_path}"

            # Double-check column rename fallback
            with open(output_path, "r", encoding=encoding) as f:
                header = f.readline()
                if geometry_field_name and geometry_field_name not in header:
                    self.rename_wkt_column_in_csv(
                        output_path, geometry_field_name, separator, encoding)

            # Open layer if requested
            if self.open_layer_checkbox.isChecked():
                self.open_exported_layer(output_path)

            return True, ""

        except Exception as e:
            return False, f"Export error: {str(e)}\n{traceback.format_exc()}"

    def rename_wkt_column_in_csv(
            self, file_path, new_column_name, separator=',', encoding="UTF-8"):
        # No limit for csv fields (dangerous only for malicious CSV, never for
        # spatial data)
        import csv
        import sys
        try:
            # Set to CSV module maximum possible value on this platform (for
            # all practical files!)
            max_csv_size = sys.maxsize
            csv.field_size_limit(max_csv_size)
        except Exception:
            # Fallback to a very high value
            csv.field_size_limit(2 ** 31 - 1)
        with open(file_path, "r", encoding=encoding, newline='') as infile:
            reader = csv.reader(infile, delimiter=separator)
            rows = list(reader)
        if not rows:
            raise Exception("CSV seems empty.")
        header = rows[0]
        for i, col in enumerate(header):
            if col.strip().upper() == "WKT":
                header[i] = new_column_name
                break
        rows[0] = header
        with open(file_path, "w", encoding=encoding, newline='') as outfile:
            writer = csv.writer(outfile, delimiter=separator)
            writer.writerows(rows)

    # def perform_export(self, output_path, selected_fields, geometry_field_name=None, encoding="UTF-8"):
    #     import traceback
    #     try:
    #         layer = self.layer
    #         if not output_path or not layer or not layer.isValid():
    #             return False, "Invalid export parameters"

    #         separator = self.get_selected_separator()
    #         separator_name = self.get_separator_name_for_gdal()

    #         field_mapping = {}
    #         selected_field_indices = []
    #         if selected_fields:
    #             for field_index, export_name in selected_fields:
    #                 original_name = layer.fields()[field_index].name()
    #                 field_mapping[original_name] = export_name
    #                 selected_field_indices.append(field_index)

    #         save_options = QgsVectorFileWriter.SaveVectorOptions()
    #         save_options.driverName = "CSV"
    #         save_options.fileEncoding = encoding
    #         if selected_field_indices:
    #             save_options.attributes = selected_field_indices

    #         # Layer options setup
    #         layer_options = [
    #             "GEOMETRY=AS_WKT",
    #             f"SEPARATOR={separator_name}",
    #             "STRING_QUOTING=NEVER"
    #         ]
    #         # Geometry column name for WKT (QGIS API >= 3.14)
    #         wkt_name = geometry_field_name if geometry_field_name and geometry_field_name.strip() else "geometry"
    #         if wkt_name.lower() != "wkt":
    #             layer_options.append(f"WKT_FIELD={wkt_name}")
    #         save_options.layerOptions = layer_options

    #         crs = self.crs_selector.crs()
    #         if crs and crs != layer.crs():
    #             save_options.destCRS = crs
    #         save_options.onlySelectedFeatures = self.selected_only_checkbox.isChecked()
    #         if field_mapping:
    #             save_options.fieldNameMap = field_mapping

    #         error = QgsVectorFileWriter.writeAsVectorFormatV3(
    #             layer,
    #             output_path,
    #             QgsProject.instance().transformContext(),
    #             save_options
    #         )
    #         # Handle return value
    #         if isinstance(error, (tuple, list)):
    #             result_code = error[0]
    #         else:
    #             result_code = error

    #         if result_code != QgsVectorFileWriter.NoError:
    # return False, f"Export failed with error code: {error}\nPath:
    # {output_path}"

    #         if not os.path.exists(output_path):
    # return False, f"The exported CSV file was not found at: {output_path}"

    #         # Post-process geometry column name if WKT column is not renamed
    #         try:
    #             with open(output_path, "r", encoding=encoding) as f:
    #                 header = f.readline()
    #                 if geometry_field_name and geometry_field_name.strip() and geometry_field_name not in header:
    #                     self.rename_wkt_column_in_csv(output_path, geometry_field_name, separator, encoding)
    #         except Exception:
    #             pass

    #         return True, ""
    #     except Exception as e:
    #         return False, f"Export error: {str(e)}\n{traceback.format_exc()}"

    # def rename_wkt_column_in_csv(self, file_path, new_column_name, separator=',', encoding="UTF-8"):
    #     import csv
    #     with open(file_path, "r", encoding=encoding, newline='') as infile:
    #         reader = csv.reader(infile, delimiter=separator)
    #         rows = list(reader)
    #     if not rows:
    #         raise Exception("CSV seems empty.")
    #     header = rows[0]
    #     for i, col in enumerate(header):
    #         if col.strip().upper() == "WKT":
    #             header[i] = new_column_name
    #             break
    #     rows[0] = header
    #     with open(file_path, "w", encoding=encoding, newline='') as outfile:
    #         writer = csv.writer(outfile, delimiter=separator)
    #         writer.writerows(rows)

    def open_exported_layer(self, file_path):
        try:
            import os
            from qgis.core import QgsVectorLayer, QgsProject
            from qgis.utils import iface
            if not os.path.exists(file_path):
                QMessageBox.critical(
                    self,
                    "File Not Found",
                    f"The exported CSV file was not found:\n{file_path}"
                )
                return
            crs_authid = self.crs_selector.crs().authid()
            normalized_path = file_path.replace('\\', '/')
            separator = self.get_selected_separator()
            encoded_separator = urllib.parse.quote(separator)
            uri = f"file:///{normalized_path}?type=csv&delimiter={encoded_separator}&crs={crs_authid}&spatialIndex=no&subsetIndex=no&watchFile=no&detectTypes=yes"
            if self.add_geometry_checkbox.isChecked():
                geom_format = self.geometry_format_combo.currentText()
                if GeometrySerializer.requires_xy_columns(geom_format):
                    x_col = self.x_column_edit.text().strip()
                    y_col = self.y_column_edit.text().strip()
                    uri += f"&xField={x_col}&yField={y_col}"
                elif geom_format in ["WKT", "EWKT"]:
                    geometry_field = self.geometry_field_edit.text().strip()
                    uri += f"&wktField={geometry_field}"
                else:
                    # QGIS delimited text provider does not natively support WKB/GeoJSON/KML.
                    # We will create a memory layer and parse the geometries programmatically.
                    geometry_field = self.geometry_field_edit.text().strip()
                    self._load_unsupported_geometry(file_path, geom_format, geometry_field, crs_authid, separator)
                    return
            else:
                uri += "&geomType=none"
            layer_name = os.path.splitext(os.path.basename(file_path))[0]
            csv_layer = QgsVectorLayer(uri, layer_name, "delimitedtext")
            if csv_layer.isValid():
                QgsProject.instance().addMapLayer(csv_layer)
                iface.messageBar().pushMessage(
                    "Success",
                    f"CSV layer '{layer_name}' loaded successfully with {csv_layer.featureCount()} features",
                    Qgis.MessageLevel.Success, 5
                )
            else:
                error_msg = csv_layer.error().message() if csv_layer.error() else "Unknown error"
                QMessageBox.warning(
                    self,
                    "Layer Loading Error",
                    f"Failed to load CSV as layer.\n\nFile: {file_path}\n\nPossible solutions:\n"
                    "1. Check if file exists and is readable\n"
                    "2. Verify geometry column contains valid WKT\n"
                    "3. Check file encoding matches selection\n"
                    "4. Verify separator matches the exported file\n\n"
                    f"Technical details:\nURI: {uri}\nError: {error_msg}"
                )
        except Exception as e:
            QMessageBox.critical(
                self,
                "Error Loading Layer",
                f"An unexpected error occurred while loading the layer:\n{str(e)}"
            )

    def load_settings(self):
        settings = QSettings()
        geometry_name = settings.value(
            "csv_geometry_export/geometry_field_name", "geometry")
        self.geometry_field_edit.setText(geometry_name)

        # Geometry format
        geometry_format = settings.value(
            "csv_geometry_export/geometry_format", "WKT")
        if geometry_format in GeometryFormat.all_formats():
            self.geometry_format_combo.setCurrentText(geometry_format)
            self.on_format_changed(geometry_format)

        # X-Y column names
        x_col_name = settings.value(
            "csv_geometry_export/x_column_name", "longitude")
        self.x_column_edit.setText(x_col_name)
        y_col_name = settings.value(
            "csv_geometry_export/y_column_name", "latitude")
        self.y_column_edit.setText(y_col_name)

        # Keep original geometry option
        keep_original = settings.value(
            "csv_geometry_export/keep_original_geom", False, type=bool)
        self.keep_original_geom_checkbox.setChecked(keep_original)
        original_geom_col = settings.value(
            "csv_geometry_export/original_geom_col_name", "org_geom")
        self.original_geom_name_edit.setText(original_geom_col)

        encoding = settings.value("csv_geometry_export/encoding", "UTF-8")
        self.encoding_combo.setCurrentText(encoding.upper())
        separator_text = settings.value(
            "csv_geometry_export/separator", "Comma (,)")
        if separator_text in self.SEPARATOR_OPTIONS.keys():
            self.separator_combo.setCurrentText(separator_text)
        add_geometry = settings.value(
            "csv_geometry_export/add_geometry", True, type=bool)
        self.add_geometry_checkbox.setChecked(add_geometry)
        selected_only = settings.value(
            "csv_geometry_export/selected_only", True, type=bool)
        self.selected_only_checkbox.setChecked(selected_only)
        open_layer = settings.value(
            "csv_geometry_export/open_layer", True, type=bool)
        self.open_layer_checkbox.setChecked(open_layer)
        last_crs_authid = settings.value(
            "csv_geometry_export/last_crs",
            self.layer.crs().authid())
        if last_crs_authid:
            crs = QgsCoordinateReferenceSystem(last_crs_authid)
            if crs.isValid():
                self.crs_selector.setCrs(crs)

    def save_settings(self):
        settings = QSettings()
        settings.setValue(
            "csv_geometry_export/geometry_field_name",
            self.geometry_field_edit.text())
        settings.setValue(
            "csv_geometry_export/geometry_format",
            self.geometry_format_combo.currentText())
        settings.setValue(
            "csv_geometry_export/x_column_name",
            self.x_column_edit.text())
        settings.setValue(
            "csv_geometry_export/y_column_name",
            self.y_column_edit.text())
        settings.setValue(
            "csv_geometry_export/keep_original_geom",
            self.keep_original_geom_checkbox.isChecked())
        settings.setValue(
            "csv_geometry_export/original_geom_col_name",
            self.original_geom_name_edit.text())
        settings.setValue(
            "csv_geometry_export/encoding",
            self.encoding_combo.currentText())
        settings.setValue(
            "csv_geometry_export/separator",
            self.separator_combo.currentText())
        settings.setValue(
            "csv_geometry_export/add_geometry",
            self.add_geometry_checkbox.isChecked())
        settings.setValue(
            "csv_geometry_export/selected_only",
            self.selected_only_checkbox.isChecked())
        settings.setValue(
            "csv_geometry_export/open_layer",
            self.open_layer_checkbox.isChecked())
        settings.setValue(
            "csv_geometry_export/last_crs",
            self.crs_selector.crs().authid())

    def _load_unsupported_geometry(self, file_path, geom_format, geometry_field, crs_authid, separator):
        """
        Loads the CSV file into a Memory Layer by manually parsing geometries.
        Used for formats that QGIS's delimited text provider doesn't natively support (e.g. WKB, GeoJSON).
        """
        try:
            from .geometry_parsers import GeometryParser
        except ImportError:
            from qgis.utils import iface
            iface.messageBar().pushMessage(
                "Error", "Could not load geometry_parsers. Please reinstall plugin.", Qgis.MessageLevel.Critical, 5)
            return

        from qgis.utils import iface
        from qgis.core import QgsMessageLog, QgsField
        from qgis.PyQt.QtCore import QVariant
        import csv
        import codecs
        
        encoding = self.encoding_combo.currentText()
        
        # 1. Read CSV using robust Python parser
        try:
            with codecs.open(file_path, 'r', encoding=encoding, errors='replace') as f:
                reader = csv.reader(f, delimiter=separator)
                rows = list(reader)
        except Exception as e:
            iface.messageBar().pushMessage("Error", f"Failed to read CSV file: {str(e)}", Qgis.MessageLevel.Critical, 5)
            return

        if not rows or len(rows) < 2:
            return
            
        headers = rows[0]
        data_rows = rows[1:]
        
        if geometry_field not in headers:
            iface.messageBar().pushMessage("Error", f"Geometry field '{geometry_field}' not found in exported CSV.", Qgis.MessageLevel.Critical, 5)
            return
            
        geom_col_idx = headers.index(geometry_field)

        QgsMessageLog.logMessage(f"CSV Layer loaded with {len(data_rows)} features using csv.reader", "CSVGeometryExport", Qgis.MessageLevel.Info)

        # 2. Find the geometry type by sampling the first few rows
        sample_geoms = []
        for row in data_rows[:10]:
            if geom_col_idx < len(row):
                val_str = row[geom_col_idx].strip().strip("'\"")
                if val_str:
                    sample_geoms.append(val_str)
        
        QgsMessageLog.logMessage(f"Extracted {len(sample_geoms)} sample geometries.", "CSVGeometryExport", Qgis.MessageLevel.Info)
        
        # Try parsing with the selected format, fallback to auto-detection if it fails
        wkb_type = GeometryParser.get_geometry_type_from_sample(sample_geoms, geom_format)
        
        actual_format = geom_format
        if wkb_type == QgsWkbTypes.Unknown and sample_geoms:
            detected_format = GeometryParser.detect_format(sample_geoms[0])
            if detected_format and detected_format != "Unknown":
                actual_format = detected_format
                wkb_type = GeometryParser.get_geometry_type_from_sample(sample_geoms, actual_format)
        
        # Promote to MultiType to avoid strict geometry type rejections in QGIS
        geom_type = QgsWkbTypes.geometryType(wkb_type)
        if geom_type == QgsWkbTypes.PointGeometry:
            type_name = "MultiPoint"
        elif geom_type == QgsWkbTypes.LineGeometry:
            type_name = "MultiLineString"
        else:
            type_name = "MultiPolygon" # Default fallback and Polygon fallback
            
        type_name = type_name.replace(" ", "")
        
        # 3. Create Memory Layer
        layer_name = os.path.splitext(os.path.basename(file_path))[0]
        mem_uri = f"{type_name}?crs={crs_authid}"
        mem_layer = QgsVectorLayer(mem_uri, layer_name, "memory")
        pr = mem_layer.dataProvider()
        
        # 4. Create fields as strings to ensure perfect column matching
        mem_fields = []
        for h in headers:
            mem_fields.append(QgsField(h, QVariant.String))
        pr.addAttributes(mem_fields)
        mem_layer.updateFields()
        
        # 5. Copy features and parse geometries
        features = []
        for row in data_rows:
            new_feat = QgsFeature(mem_layer.fields())
            
            # Set attributes
            for col_idx, val in enumerate(row):
                if col_idx < len(headers):
                    new_feat.setAttribute(col_idx, val)
            
            # Parse geometry
            if geom_col_idx < len(row):
                val_str = row[geom_col_idx].strip().strip("'\"")
                if val_str:
                    geom = GeometryParser.parse(val_str, actual_format)
                    if geom and not geom.isNull():
                        if not geom.isMultipart():
                            geom.convertToMultiType()
                        new_feat.setGeometry(geom)
                    
            features.append(new_feat)
            
        success, _ = pr.addFeatures(features)
        mem_layer.updateExtents()
        
        # 6. Add to project
        if mem_layer.isValid() and success:
            QgsProject.instance().addMapLayer(mem_layer)
            iface.messageBar().pushMessage(
                "Success", f"Layer loaded successfully from '{actual_format}'.", Qgis.MessageLevel.Success, 5)
        else:
            iface.messageBar().pushMessage("Error", f"Failed to add features to memory layer. Success: {success}, Valid: {mem_layer.isValid()}", Qgis.MessageLevel.Critical, 7)
