"""
Build and validate a calculated field for a result table.

The user enters a field name, picks a type, and writes a QGIS expression
against the source layer's columns. The dialog validates the expression
with ``QgsExpression`` and previews the first few rows so they can see
their formula working before committing.

Persistence (writing the new column into the GeoPackage) is the caller's
responsibility — this dialog just collects and validates the spec.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import List, Optional

from qgis.core import (
    QgsExpression,
    QgsExpressionContext,
    QgsExpressionContextUtils,
    QgsField,
    QgsVectorLayer,
)

from ..qt_compat import (
    QAbstractItemView,
    QComboBox,
    QDialog,
    QDialogButtonBox,
    QFormLayout,
    QHBoxLayout,
    QHeaderView,
    QLabel,
    QLineEdit,
    QPlainTextEdit,
    QPushButton,
    QTableWidget,
    QTableWidgetItem,
    QVBoxLayout,
    QVariant,
    Qt,
)
from ..translator import tr, add_listener, remove_listener
from ..theme import error_text_color, muted_text_color, success_text_color


# Identity tuples — key + QVariant type. Labels resolve through tr() at runtime.
_FIELD_TYPE_KEYS: List[tuple] = [
    ("real", QVariant.Double),
    ("integer", QVariant.Int),
    ("text", QVariant.String),
    ("date", QVariant.Date),
]


def _field_type_choices() -> List[tuple]:
    """Return field-type choices (label, key, QVariant type) in the active language."""
    return [(tr(f"calculated_field.field_type_{key}"), key, qv) for key, qv in _FIELD_TYPE_KEYS]


@dataclass
class CalculatedFieldSpec:
    """The output of the dialog — what the caller needs to materialize the field."""

    name: str
    type_label: str
    qvariant_type: int
    expression: str


class CalculatedFieldDialog(QDialog):
    """Collect and validate a calculated-field definition."""

    PREVIEW_ROW_COUNT = 8

    def __init__(
        self,
        *,
        layer: QgsVectorLayer,
        existing_field_names: Optional[List[str]] = None,
        parent=None,
    ):
        super().__init__(parent)
        self.setWindowTitle(tr("calculated_field.window_title"))
        self.resize(700, 540)
        self._layer = layer
        self._existing_names = {str(name).lower() for name in (existing_field_names or [])}

        self._init_ui()
        self._populate_field_list()
        self._update_validation_message(tr("calculated_field.prompt_initial"))
        self.retranslateUi()
        add_listener(self.retranslateUi)

    # ------------------------------------------------------------------
    # UI
    # ------------------------------------------------------------------
    def _init_ui(self) -> None:
        layout = QVBoxLayout()
        self.setLayout(layout)

        form = QFormLayout()
        layout.addLayout(form)

        self.name_input = QLineEdit()
        self.name_input.setPlaceholderText(tr("calculated_field.name_placeholder"))
        self.row_field_name_label = QLabel(tr("calculated_field.row_field_name"))
        form.addRow(self.row_field_name_label, self.name_input)

        self.type_combo = QComboBox()
        for label, key, _qvariant_type in _field_type_choices():
            self.type_combo.addItem(label, key)
        self.row_field_type_label = QLabel(tr("calculated_field.row_field_type"))
        form.addRow(self.row_field_type_label, self.type_combo)

        self.expression_header_label = QLabel(tr("calculated_field.lbl_expression"))
        layout.addWidget(self.expression_header_label)
        self.expression_input = QPlainTextEdit()
        self.expression_input.setPlaceholderText(tr("calculated_field.expression_placeholder"))
        self.expression_input.setMaximumHeight(120)
        layout.addWidget(self.expression_input)

        validate_row = QHBoxLayout()
        self.btn_validate = QPushButton(tr("calculated_field.btn_validate"))
        self.btn_validate.clicked.connect(self._validate_and_preview)
        validate_row.addWidget(self.btn_validate)
        validate_row.addStretch()
        layout.addLayout(validate_row)

        self.validation_label = QLabel("")
        self.validation_label.setWordWrap(True)
        layout.addWidget(self.validation_label)

        self.source_cols_header_label = QLabel(tr("calculated_field.lbl_source_cols"))
        layout.addWidget(self.source_cols_header_label)
        self.field_list_label = QLabel("")
        self.field_list_label.setWordWrap(True)
        self.field_list_label.setStyleSheet(f"color: {muted_text_color()};")
        layout.addWidget(self.field_list_label)

        self.preview_header_label = QLabel(tr("calculated_field.lbl_preview"))
        layout.addWidget(self.preview_header_label)
        self.preview_table = QTableWidget(0, 2, self)
        self.preview_table.setHorizontalHeaderLabels([
            tr("calculated_field.preview_col_row"),
            tr("calculated_field.preview_col_value"),
        ])
        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
        self.preview_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
        layout.addWidget(self.preview_table, 1)

        buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
        self._ok_button = buttons.button(QDialogButtonBox.StandardButton.Ok)
        self._ok_button.setText(tr("calculated_field.btn_add_field"))
        self._ok_button.setEnabled(False)
        buttons.accepted.connect(self._on_accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)

    def retranslateUi(self) -> None:
        self.setWindowTitle(tr("calculated_field.window_title"))
        if hasattr(self, "name_input"):
            self.name_input.setPlaceholderText(tr("calculated_field.name_placeholder"))
        if hasattr(self, "row_field_name_label"):
            self.row_field_name_label.setText(tr("calculated_field.row_field_name"))
        if hasattr(self, "row_field_type_label"):
            self.row_field_type_label.setText(tr("calculated_field.row_field_type"))
        if hasattr(self, "expression_header_label"):
            self.expression_header_label.setText(tr("calculated_field.lbl_expression"))
        if hasattr(self, "expression_input"):
            self.expression_input.setPlaceholderText(tr("calculated_field.expression_placeholder"))
        if hasattr(self, "btn_validate"):
            self.btn_validate.setText(tr("calculated_field.btn_validate"))
        if hasattr(self, "source_cols_header_label"):
            self.source_cols_header_label.setText(tr("calculated_field.lbl_source_cols"))
        if hasattr(self, "preview_header_label"):
            self.preview_header_label.setText(tr("calculated_field.lbl_preview"))
        if hasattr(self, "preview_table"):
            self.preview_table.setHorizontalHeaderLabels([
                tr("calculated_field.preview_col_row"),
                tr("calculated_field.preview_col_value"),
            ])
        if hasattr(self, "_ok_button"):
            self._ok_button.setText(tr("calculated_field.btn_add_field"))
        # Field-type combo: refresh labels while preserving the selected key.
        if hasattr(self, "type_combo"):
            current_key = str(self.type_combo.currentData() or "real")
            self.type_combo.blockSignals(True)
            self.type_combo.clear()
            for label, key, _qv in _field_type_choices():
                self.type_combo.addItem(label, key)
                if key == current_key:
                    self.type_combo.setCurrentIndex(self.type_combo.count() - 1)
            self.type_combo.blockSignals(False)
        # Refresh the "no layer / no fields" placeholder text if currently shown.
        if hasattr(self, "field_list_label"):
            self._populate_field_list()

    def closeEvent(self, event):
        remove_listener(self.retranslateUi)
        super().closeEvent(event)

    def _populate_field_list(self) -> None:
        if not isinstance(self._layer, QgsVectorLayer):
            self.field_list_label.setText(tr("calculated_field.no_layer"))
            return
        names = [field.name() for field in self._layer.fields()]
        if names:
            self.field_list_label.setText(", ".join(f'"{name}"' for name in names))
        else:
            self.field_list_label.setText(tr("calculated_field.no_fields"))

    # ------------------------------------------------------------------
    # Validation + preview
    # ------------------------------------------------------------------
    def _validate_and_preview(self) -> None:
        name = self.name_input.text().strip()
        expression_text = self.expression_input.toPlainText().strip()

        if not name:
            self._update_validation_message(tr("calculated_field.err_name_required"), is_error=True)
            self._ok_button.setEnabled(False)
            return
        if " " in name:
            self._update_validation_message(tr("calculated_field.err_name_no_spaces"), is_error=True)
            self._ok_button.setEnabled(False)
            return
        if name.lower() in self._existing_names:
            self._update_validation_message(tr("calculated_field.err_name_exists", name=name), is_error=True)
            self._ok_button.setEnabled(False)
            return
        if not expression_text:
            self._update_validation_message(tr("calculated_field.err_expression_required"), is_error=True)
            self._ok_button.setEnabled(False)
            return

        expression = QgsExpression(expression_text)
        if expression.hasParserError():
            self._update_validation_message(
                tr("calculated_field.err_parser", error=expression.parserErrorString()),
                is_error=True,
            )
            self._populate_preview([])
            self._ok_button.setEnabled(False)
            return

        context = QgsExpressionContext()
        context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(self._layer))

        if not expression.prepare(context):
            self._update_validation_message(
                tr("calculated_field.err_prepare", error=expression.evalErrorString()),
                is_error=True,
            )
            self._populate_preview([])
            self._ok_button.setEnabled(False)
            return

        previews: List[tuple] = []
        eval_error: Optional[str] = None
        for index, feature in enumerate(self._layer.getFeatures()):
            if index >= self.PREVIEW_ROW_COUNT:
                break
            context.setFeature(feature)
            value = expression.evaluate(context)
            if expression.hasEvalError():
                eval_error = expression.evalErrorString()
                break
            row_id = feature.id()
            previews.append((row_id, value))

        if eval_error:
            self._update_validation_message(
                tr("calculated_field.err_eval", row=len(previews) + 1, error=eval_error),
                is_error=True,
            )
            self._populate_preview(previews)
            self._ok_button.setEnabled(False)
            return

        if not previews:
            self._update_validation_message(tr("calculated_field.info_no_features"), is_error=False)
            self._populate_preview([])
            self._ok_button.setEnabled(True)
            return

        self._update_validation_message(
            tr("calculated_field.info_ok_preview", count=len(previews)),
            is_error=False,
        )
        self._populate_preview(previews)
        self._ok_button.setEnabled(True)

    def _populate_preview(self, rows: List[tuple]) -> None:
        self.preview_table.setRowCount(len(rows))
        for row_index, (row_id, value) in enumerate(rows):
            self.preview_table.setItem(row_index, 0, QTableWidgetItem(str(row_id)))
            self.preview_table.setItem(row_index, 1, QTableWidgetItem(str(value)))

    def _update_validation_message(self, text: str, *, is_error: bool = False) -> None:
        if is_error:
            self.validation_label.setStyleSheet(f"color: {error_text_color()};")
        else:
            self.validation_label.setStyleSheet(f"color: {success_text_color()};")
        self.validation_label.setText(text)

    # ------------------------------------------------------------------
    # Accept
    # ------------------------------------------------------------------
    def _on_accept(self) -> None:
        # Re-validate one final time before committing.
        self._validate_and_preview()
        if not self._ok_button.isEnabled():
            return
        self.accept()

    def spec(self) -> CalculatedFieldSpec:
        type_key = str(self.type_combo.currentData() or "real")
        qvariant_type = next(
            (qt for key, qt in _FIELD_TYPE_KEYS if key == type_key),
            QVariant.Double,
        )
        type_label = self.type_combo.currentText()
        return CalculatedFieldSpec(
            name=self.name_input.text().strip(),
            type_label=type_label,
            qvariant_type=qvariant_type,
            expression=self.expression_input.toPlainText().strip(),
        )
