"""
Reusable Qt table model for EMDS result grids.
"""

from __future__ import annotations

from typing import Any, Optional

import pandas as pd
from .netweaver_scheme import is_no_data
from .qt_compat import QAbstractTableModel, QModelIndex, Qt


class DataFrameTableModel(QAbstractTableModel):
    """Expose a pandas DataFrame through Qt's table model API."""

    def __init__(self, dataframe: Optional[pd.DataFrame] = None, parent=None):
        super().__init__(parent)
        self._dataframe = dataframe.copy() if dataframe is not None else pd.DataFrame()

    def set_dataframe(self, dataframe: Optional[pd.DataFrame]) -> None:
        """Replace the underlying DataFrame."""
        self.beginResetModel()
        self._dataframe = dataframe.copy() if dataframe is not None else pd.DataFrame()
        self.endResetModel()

    def dataframe(self) -> pd.DataFrame:
        """Return a copy of the current DataFrame."""
        return self._dataframe.copy()

    def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:  # noqa: N802
        if parent.isValid():
            return 0
        return len(self._dataframe.index)

    def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:  # noqa: N802
        if parent.isValid():
            return 0
        return len(self._dataframe.columns)

    def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any:
        if not index.isValid():
            return None

        value = self._dataframe.iloc[index.row(), index.column()]

        if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
            # D5: tables written before the NULL fix still hold raw sentinels.
            # Render them the way NaN already renders — blank — rather than
            # showing "-9999.888777" or "1.7976931348623157e+308" as a score.
            if pd.isna(value) or is_no_data(value):
                return ""
            return str(value)

        if role == Qt.ItemDataRole.TextAlignmentRole:
            if isinstance(value, (int, float)) and not isinstance(value, bool):
                return Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignRight
            return Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft

        if role == Qt.ItemDataRole.UserRole:
            return value

        return None

    def headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole.DisplayRole):  # noqa: N802
        if role != Qt.ItemDataRole.DisplayRole:
            return None

        if orientation == Qt.Orientation.Horizontal:
            if 0 <= section < len(self._dataframe.columns):
                return str(self._dataframe.columns[section])
            return None

        if orientation == Qt.Orientation.Vertical:
            if 0 <= section < len(self._dataframe.index):
                return str(self._dataframe.index[section])
            return None

        return None

    def sort(self, column: int, order: Qt.SortOrder = Qt.SortOrder.AscendingOrder) -> None:
        """Sort the table by a column."""
        if not (0 <= column < len(self._dataframe.columns)):
            return

        ascending = order == Qt.SortOrder.AscendingOrder
        column_name = self._dataframe.columns[column]

        self.layoutAboutToBeChanged.emit()
        try:
            self._dataframe = self._dataframe.sort_values(by=column_name, ascending=ascending, kind="mergesort")
        finally:
            self.layoutChanged.emit()
