from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, cast

from qtpy import QtCore
from qtpy.QtCore import QAbstractItemModel, QSortFilterProxyModel, QTimer, Qt
from qtpy.QtGui import QCursor
from qtpy.QtWidgets import (
    QAbstractItemDelegate,
    QAbstractItemView,
    QAbstractScrollArea,
    QFrame,
    QStyle,
    QStyleOptionViewItem,
)

from loggerplus import RobustLogger
from utility.gui.qt.widgets.itemviews.baseview import RobustBaseWidget
from utility.gui.qt.widgets.itemviews.html_delegate import HTMLDelegate

if TYPE_CHECKING:
    from qtpy.QtCore import QAbstractItemModel, QItemSelectionModel, QMargins, QModelIndex, QPoint
    from qtpy.QtGui import QFont, QResizeEvent, QWheelEvent
    from qtpy.QtWidgets import QAbstractItemDelegate, QScrollBar, QWidget, _QMenu
    from typing_extensions import Literal  # pyright: ignore[reportMissingModuleSource]


class RobustAbstractItemView(RobustBaseWidget, QAbstractItemView if TYPE_CHECKING else object):
    def __init__(
        self,
        parent: QWidget | None = None,
        *args,
        **kwargs,
    ):
        RobustBaseWidget.__init__(self, parent, *args, **kwargs)
        self._layout_changed_debounce_timer: QTimer = QTimer(self)
        self._fix_drawer_button()

    def _create_drawer_button(self):
        super()._create_drawer_button()
        self._robust_drawer.clicked.connect(lambda _: self.show_header_context_menu())

    def debounce_layout_changed(
        self,
        timeout: int = 100,
        *,
        pre_change_emit: bool = False,
    ):
        view_port: QWidget | None = self.viewport()
        assert view_port is not None
        view_port.update()
        if self._layout_changed_debounce_timer.isActive():
            self._layout_changed_debounce_timer.stop()
        elif pre_change_emit:
            view_model: QAbstractItemModel | None = self.model()
            assert view_model is not None
            view_model.layoutAboutToBeChanged.emit()
        self._layout_changed_debounce_timer.start(timeout)

    def setParent(
        self,
        parent: QWidget,
        f: Qt.WindowType | None = None,
    ) -> None:
        super().setParent(parent) if f is None else super().setParent(parent, f)

    def _fix_drawer_button(self):
        if self._robust_drawer is None:
            self._create_drawer_button()
            self._robust_drawer.show()
        vert_scroll_bar: QScrollBar | None = self.verticalScrollBar()
        assert vert_scroll_bar is not None
        if vert_scroll_bar.isVisible():
            self._robust_drawer.move(
                self.width() - self._robust_drawer.width() - vert_scroll_bar.width(),
                0,
            )
        else:
            self._robust_drawer.move(self.width() - self._robust_drawer.width(), 0)

    def show_header_context_menu(
        self,
        pos: QPoint | None = None,
        parent: QWidget | None = None,
    ):
        menu = self.build_context_menu()
        menu.exec(QCursor.pos())

    def selected_source_indexes(self) -> list[QModelIndex]:
        """Same as QAbstractItemView.selectedIndexes, but returns the source indexes instead of the proxy model indexes."""
        indexes: list[QModelIndex] = []
        current_model: QAbstractItemModel | None = self.model()
        for index in self.selectedIndexes():
            sourceIndex: QModelIndex = (
                current_model.mapToSource(index)  # pyright: ignore[reportArgumentType]
                if isinstance(current_model, QSortFilterProxyModel)
                else index
            )
            if not sourceIndex.isValid():
                RobustLogger().warning("Invalid source index for row %d", index.row())
                continue
            indexes.append(sourceIndex)
        return indexes

    def resizeEvent(
        self,
        event: QResizeEvent,
    ):
        super().resizeEvent(event)
        self.debounce_layout_changed()
        self._fix_drawer_button()

    def wheelEvent(
        self,
        event: QWheelEvent,
    ) -> None:
        modifiers: Qt.KeyboardModifier = event.modifiers()
        handled: bool = False

        if bool(modifiers & Qt.KeyboardModifier.ShiftModifier) and bool(modifiers & Qt.KeyboardModifier.ControlModifier):
            handled = self._wheel_changes_item_spacing(event)
        elif bool(modifiers & Qt.KeyboardModifier.ControlModifier):
            handled = self._wheel_changes_text_size(event)

        if not handled:
            super().wheelEvent(event)

    def _wheel_changes_item_spacing(
        self,
        event: QWheelEvent,
    ) -> bool:
        delta: int = event.angleDelta().y()
        if not delta:
            return False
        item_delegate: QAbstractItemDelegate | None = self.itemDelegate()
        if isinstance(item_delegate, HTMLDelegate):
            single_step: Literal[-1, 1] = 1 if delta > 0 else -1
            new_vertical_spacing: int = max(0, item_delegate.custom_vertical_spacing + single_step)
            item_delegate.setVerticalSpacing(new_vertical_spacing)
            self.emit_layout_changed()  # Requires immediate update
            return True
        return False

    def _wheel_changes_text_size(
        self,
        event: QWheelEvent,
    ) -> bool:
        delta: int = event.angleDelta().y()
        if not delta:
            return False
        self.set_text_size(self.get_text_size() + (1 if delta > 0 else -1))
        return True

    def _handle_color_action(
        self,
        get_func: Callable[[], Any],
        title: str,
        settings_key: str,
    ):
        super()._handle_color_action(get_func, title, settings_key)
        self.debounce_layout_changed()
        view_port: QWidget | None = self.viewport()
        assert view_port is not None
        view_port.update()

    def set_text_size(self, size: int):
        delegate: QAbstractItemDelegate | None = self.itemDelegate()
        if isinstance(delegate, HTMLDelegate):
            text_size: int = max(1, size)
            model: QAbstractItemModel | None = self.model()
            assert model is not None
            delegate.set_text_size(text_size)
            self.update_columns_after_text_size_change()
        else:
            font: QFont = self.font()
            font.setPointSize(max(1, size))
            self.setFont(font)
            self.updateGeometry()
        self.debounce_layout_changed()

    def get_text_size(self) -> int:
        delegate: QAbstractItemDelegate | None = self.itemDelegate()
        return delegate.text_size if isinstance(delegate, HTMLDelegate) else self.font().pointSize()

    def update_columns_after_text_size_change(self):
        """This method should be implemented by subclasses if needed."""

    def emit_layout_changed(self):
        model: QAbstractItemModel | None = self.model()
        if model is None:
            return
        model.layoutChanged.emit()

    def styleOptionForIndex(
        self,
        index: QModelIndex,
    ) -> QStyleOptionViewItem:
        """Construct and configure a QStyleOptionViewItem for the given index.

        Required for non-pyqt5 versions of Qt.
        """
        option = QStyleOptionViewItem()
        if index.isValid():
            # Initialize style option from the widget
            option.initFrom(self)

            # Set state flags based on item's selection, focus, and enabled states
            sel_model: QItemSelectionModel | None = self.selectionModel()
            assert sel_model is not None
            if sel_model.isSelected(index):
                option.state |= QStyle.StateFlag.State_Selected
            if index == self.currentIndex() and self.hasFocus():
                option.state |= QStyle.StateFlag.State_HasFocus
            if not self.isEnabled():
                option.state = cast("QStyle.StateFlag", option.state & ~QStyle.StateFlag.State_Enabled)

            # Additional properties
            check_state_data: Any = index.data(Qt.ItemDataRole.CheckStateRole)
            option.checkState = Qt.CheckState.Unchecked if check_state_data is None else check_state_data
            option.decorationPosition = QStyleOptionViewItem.Position.Top
            option.decorationAlignment = Qt.AlignmentFlag.AlignCenter
            option.displayAlignment = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
            option.index = index
            option.locale = self.locale()
            option.showDecorationSelected = True
            option.text = index.data(Qt.ItemDataRole.DisplayRole)
            # option.backgroundBrush = QColor(Qt.GlobalColor.yellow)
            # option.decorationSize = QSize(32, 32)
            # option.font = QFont("Arial", 12, QFont.Weight.Bold)
            # option.icon = QIcon("/path/to/icon.png")  # Example path to an icon
            # option.textElideMode = Qt.TextElideMode.ElideMiddle
            # option.viewItemPosition = QStyleOptionViewItem.ViewItemPosition.Middle
            # if index.row() % 2 == 0:
            #    option.backgroundBrush = QColor(Qt.GlobalColor.lightGray)

        return option

    def build_context_menu(
        self,
        parent: QWidget | None = None,
    ) -> _QMenu:
        print(f"{self.__class__.__name__}.build_context_menu")
        menu: _QMenu | None = super().build_context_menu(parent)
        assert menu is not None
        context_menu = menu.addMenu("QAbstractItemView")
        assert context_menu is not None
        advanced_menu = context_menu.addMenu("Advanced")
        assert advanced_menu is not None
        # Display menu
        display_menu = context_menu.addMenu("Display")
        assert display_menu is not None
        self._add_menu_action(
            display_menu,
            "Alternating Row Colors",
            self.alternatingRowColors,
            self.setAlternatingRowColors,
            settings_key="alternatingRowColors",
        )

        # Advanced submenu for Display
        display_advanced_menu = display_menu.addMenu("Advanced")
        assert display_advanced_menu is not None
        self._add_exclusive_menu_action(
            display_advanced_menu,
            "Text Elide Mode",
            self.textElideMode,
            lambda x: self.setTextElideMode(Qt.TextElideMode(x)),
            options={
                "Elide Left": Qt.TextElideMode.ElideLeft,
                "Elide Right": Qt.TextElideMode.ElideRight,
                "Elide Middle": Qt.TextElideMode.ElideMiddle,
                "Elide None": Qt.TextElideMode.ElideNone,
            },
            settings_key="textElideMode",
        )

        self._add_menu_action(
            display_menu,
            "Font Size",
            self.get_text_size,
            self.set_text_size,
            settings_key="fontSize",
            param_type=int,
        )
        self._add_menu_action(
            display_menu,
            "Icon Size",
            self.iconSize,
            self.setIconSize,
            settings_key="iconSize",
            param_type=QtCore.QSize,
        )
        self._add_menu_action(
            display_menu,
            "Show Drop Indicator",
            self.showDropIndicator,
            self.setDropIndicatorShown,
            settings_key="showDropIndicator",
        )

        self._add_exclusive_menu_action(
            advanced_menu,
            "Edit Triggers",
            self.editTriggers,
            self.setEditTriggers,
            options={
                "No Edit Triggers": QAbstractItemView.EditTrigger.NoEditTriggers,
                "Current Changed": QAbstractItemView.EditTrigger.CurrentChanged,
                "Double Clicked": QAbstractItemView.EditTrigger.DoubleClicked,
                "Selected Clicked": QAbstractItemView.EditTrigger.SelectedClicked,
                "Edit Key Pressed": QAbstractItemView.EditTrigger.EditKeyPressed,
                "Any Key Pressed": QAbstractItemView.EditTrigger.AnyKeyPressed,
                "All Edit Triggers": QAbstractItemView.EditTrigger.AllEditTriggers,
            },
            settings_key="editTriggers",
            param_type=QAbstractItemView.EditTrigger,
        )

        # Drag and Drop menu
        self._add_exclusive_menu_action(
            advanced_menu,
            "Selection Mode",
            self.selectionMode,
            self.setSelectionMode,
            options={
                "No Selection": QAbstractItemView.SelectionMode.NoSelection,
                "Single Selection": QAbstractItemView.SelectionMode.SingleSelection,
                "Multi Selection": QAbstractItemView.SelectionMode.MultiSelection,
                "Extended Selection": QAbstractItemView.SelectionMode.ExtendedSelection,
                "Contiguous Selection": QAbstractItemView.SelectionMode.ContiguousSelection,
            },
            settings_key="selectionMode",
            param_type=QAbstractItemView.SelectionMode,
        )
        self._add_exclusive_menu_action(
            advanced_menu,
            "Selection Behavior",
            self.selectionBehavior,
            self.setSelectionBehavior,
            options={
                "Select Items": QAbstractItemView.SelectionBehavior.SelectItems,
                "Select Rows": QAbstractItemView.SelectionBehavior.SelectRows,
                "Select Columns": QAbstractItemView.SelectionBehavior.SelectColumns,
            },
            settings_key="selectionBehavior",
            param_type=QAbstractItemView.SelectionBehavior,
        )
        drag_drop_menu = advanced_menu.addMenu("Drag and Drop")
        assert drag_drop_menu is not None
        self._add_exclusive_menu_action(
            drag_drop_menu,
            "Drag Drop Mode",
            self.dragDropMode,
            self.setDragDropMode,
            options={
                "No Drag Drop": QAbstractItemView.DragDropMode.NoDragDrop,
                "Drag Only": QAbstractItemView.DragDropMode.DragOnly,
                "Drop Only": QAbstractItemView.DragDropMode.DropOnly,
                "Drag Drop": QAbstractItemView.DragDropMode.DragDrop,
                "Internal Move": QAbstractItemView.DragDropMode.InternalMove,
            },
            settings_key="dragDropMode",
            param_type=QAbstractItemView.DragDropMode,
        )
        self._add_menu_action(
            drag_drop_menu,
            "Drag Enabled",
            self.dragEnabled,
            self.setDragEnabled,
            settings_key="dragEnabled",
        )
        self._add_menu_action(
            drag_drop_menu,
            "Drag Drop Overwrite Mode",
            self.dragDropOverwriteMode,
            self.setDragDropOverwriteMode,
            settings_key="dragDropOverwriteMode",
        )
        self._add_exclusive_menu_action(
            drag_drop_menu,
            "Default Drop Action",
            self.defaultDropAction,
            self.setDefaultDropAction,
            options={
                "Copy Action": Qt.DropAction.CopyAction,
                "Move Action": Qt.DropAction.MoveAction,
                "Link Action": Qt.DropAction.LinkAction,
                "Ignore Action": Qt.DropAction.IgnoreAction,
            },
            settings_key="defaultDropAction",
            param_type=Qt.DropAction,
        )

        # Behavior menu
        behavior_menu = context_menu.addMenu("Behavior")
        assert behavior_menu is not None
        behavior_frame_menu = behavior_menu.addMenu("Frame")
        assert behavior_frame_menu is not None
        self._add_exclusive_menu_action(
            behavior_frame_menu,
            "Frame Shape",
            self.frameShape,
            self.setFrameShape,
            options={
                "No Frame": QFrame.Shape.NoFrame,
                "Box": QFrame.Shape.Box,
                "Panel": QFrame.Shape.Panel,
                "Win Panel": QFrame.Shape.WinPanel,
                "HLine": QFrame.Shape.HLine,
                "VLine": QFrame.Shape.VLine,
                "StyledPanel": QFrame.Shape.StyledPanel,
            },
            settings_key="frameShape",
            param_type=QFrame.Shape,
        )
        self._add_exclusive_menu_action(
            behavior_frame_menu,
            "Frame Shadow",
            self.frameShadow,
            self.setFrameShadow,
            options={
                "Plain": QFrame.Shadow.Plain,
                "Raised": QFrame.Shadow.Raised,
                "Sunken": QFrame.Shadow.Sunken,
            },
            settings_key="frameShadow",
            param_type=QFrame.Shadow,
        )
        self._add_menu_action(
            behavior_menu,
            "Tab Key Navigation",
            self.tabKeyNavigation,
            self.setTabKeyNavigation,
            settings_key="tabKeyNavigation",
        )
        self._add_menu_action(
            behavior_menu,
            "Line Width",
            self.lineWidth,
            self.setLineWidth,
            settings_key="lineWidth",
            param_type=int,
        )
        self._add_menu_action(
            behavior_menu,
            "Mid Line Width",
            self.midLineWidth,
            self.setMidLineWidth,
            settings_key="midLineWidth",
            param_type=int,
        )

        # Scroll menu
        behavior_menu.addSeparator()
        self._add_exclusive_menu_action(
            behavior_menu,
            "Horizontal Scroll Mode",
            self.horizontalScrollMode,
            self.setHorizontalScrollMode,
            options={
                "Scroll Per Item": QAbstractItemView.ScrollMode.ScrollPerItem,
                "Scroll Per Pixel": QAbstractItemView.ScrollMode.ScrollPerPixel,
            },
            settings_key="horizontalScrollMode",
            param_type=QAbstractItemView.ScrollMode,
        )
        self._add_exclusive_menu_action(
            behavior_menu,
            "Vertical Scroll Mode",
            self.verticalScrollMode,
            self.setVerticalScrollMode,
            options={
                "Scroll Per Item": QAbstractItemView.ScrollMode.ScrollPerItem,
                "Scroll Per Pixel": QAbstractItemView.ScrollMode.ScrollPerPixel,
            },
            settings_key="verticalScrollMode",
            param_type=QAbstractItemView.ScrollMode,
        )
        scroll_advanced_menu = behavior_menu.addMenu("Advanced")
        assert scroll_advanced_menu is not None

        self._add_menu_action(
            scroll_advanced_menu,
            "Auto Scroll",
            self.hasAutoScroll,
            self.setAutoScroll,
            settings_key="autoScroll",
        )
        self._add_menu_action(
            scroll_advanced_menu,
            "Auto Scroll Margin",
            self.autoScrollMargin,
            self.setAutoScrollMargin,
            settings_key="autoScrollMargin",
            param_type=int,
        )

        # Size Adjustment menu
        self._add_exclusive_menu_action(  # QAbstractScrollArea
            advanced_menu,
            "Size Adjust Policy",
            self.sizeAdjustPolicy,
            self.setSizeAdjustPolicy,
            options={
                "Adjust Ignored": QAbstractScrollArea.SizeAdjustPolicy.AdjustIgnored,
                "Adjust To Contents On First Show": QAbstractScrollArea.SizeAdjustPolicy.AdjustToContentsOnFirstShow,
                "Adjust To Contents": QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents,
            },
            settings_key="sizeAdjustPolicy",
            param_type=QAbstractScrollArea.SizeAdjustPolicy,
        )

        # Viewport menu
        def set_viewport_margins(m: QMargins):
            if isinstance(m, (tuple, list)):
                self.setViewportMargins(*m)
            elif isinstance(m, QtCore.QMargins):
                self.setViewportMargins(m.left(), m.top(), m.right(), m.bottom())
            else:
                self.setViewportMargins(0, 0, 0, 0)  # Default values if neither tuple/list nor QMargins

        viewport_menu = context_menu.addMenu("Viewport")
        assert viewport_menu is not None
        self._add_menu_action(
            viewport_menu,
            "Viewport Margins",
            self.viewportMargins,  # QAbstractScrollArea
            set_viewport_margins,
            settings_key="viewportMargins",
            param_type=tuple,
        )

        # Actions menu
        refresh_menu = context_menu.addMenu("Refresh...")
        assert refresh_menu is not None

        self._add_simple_action(refresh_menu, "Update Geometries", self.updateGeometries)
        self._add_simple_action(refresh_menu, "Reset View", self.reset)
        self._add_simple_action(refresh_menu, "Clear Selection", self.clearSelection)
        self._add_simple_action(refresh_menu, "Select All", self.selectAll)
        self._add_simple_action(refresh_menu, "Emit Layout Changed", self.emit_layout_changed)
        return menu
