from __future__ import annotations

from typing import Optional

from PyQt6.QtCore import QPoint, QRect, Qt
from PyQt6.QtGui import QPainter, QPixmap
from PyQt6.QtWidgets import QGraphicsPixmapItem, QGraphicsScene, QGraphicsTextItem, QGraphicsView, QRubberBand, QWidget


class InteractiveImageView(QGraphicsView):
    """Image view with wheel-zoom and drag-to-zoom rectangle."""

    def __init__(self, title: str, parent: Optional[QWidget] = None) -> None:
        super().__init__(parent)
        self._title = title
        self._scene = QGraphicsScene(self)
        self.setScene(self._scene)
        self._pix_item = QGraphicsPixmapItem()
        self._scene.addItem(self._pix_item)
        self._text_item = QGraphicsTextItem(f"{title}: no image")
        self._scene.addItem(self._text_item)
        self._drag_start: Optional[QPoint] = None
        self._pan_last: Optional[QPoint] = None
        self._rubber = QRubberBand(QRubberBand.Shape.Rectangle, self.viewport())

        self.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.SmoothPixmapTransform)
        self.setDragMode(QGraphicsView.DragMode.NoDrag)
        self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter)
        self.setMinimumHeight(360)
        self.setStyleSheet("border: 1px solid #d0d0d0; background: #fafafa;")

    def set_placeholder(self, text: str) -> None:
        self._pix_item.setPixmap(QPixmap())
        self._text_item.setPlainText(text)
        self._text_item.setVisible(True)
        self.resetTransform()

    def set_image_bytes(self, content: bytes) -> bool:
        pix = QPixmap()
        if not pix.loadFromData(content):
            self.set_placeholder(f"{self._title}: invalid image")
            return False
        return self.set_pixmap(pix)

    def set_pixmap(self, pix: QPixmap) -> bool:
        if pix.isNull():
            self.set_placeholder(f"{self._title}: invalid image")
            return False
        self._pix_item.setPixmap(pix)
        self._text_item.setVisible(False)
        self.resetTransform()
        self.fitInView(self._pix_item, Qt.AspectRatioMode.KeepAspectRatio)
        return True

    def wheelEvent(self, event) -> None:  # type: ignore[override]
        delta = event.angleDelta().y()
        factor = 1.15 if delta > 0 else 1.0 / 1.15
        self.scale(factor, factor)

    def mousePressEvent(self, event) -> None:  # type: ignore[override]
        if self._pix_item.pixmap().isNull():
            super().mousePressEvent(event)
            return

        if event.button() == Qt.MouseButton.LeftButton:
            self._drag_start = event.position().toPoint()
            self._rubber.setGeometry(QRect(self._drag_start, self._drag_start))
            self._rubber.show()
            event.accept()
            return
        if event.button() == Qt.MouseButton.RightButton:
            self._pan_last = event.position().toPoint()
            self.setCursor(Qt.CursorShape.ClosedHandCursor)
            event.accept()
            return
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event) -> None:  # type: ignore[override]
        if self._drag_start is not None:
            cur = event.position().toPoint()
            self._rubber.setGeometry(QRect(self._drag_start, cur).normalized())
            event.accept()
            return
        if self._pan_last is not None:
            cur = event.position().toPoint()
            delta = cur - self._pan_last
            self._pan_last = cur
            self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x())
            self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y())
            event.accept()
            return
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event) -> None:  # type: ignore[override]
        if event.button() == Qt.MouseButton.LeftButton and self._drag_start is not None:
            rect = self._rubber.geometry()
            self._rubber.hide()
            self._drag_start = None
            if rect.width() > 10 and rect.height() > 10:
                scene_rect = self.mapToScene(rect).boundingRect()
                self.fitInView(scene_rect, Qt.AspectRatioMode.KeepAspectRatio)
            event.accept()
            return

        if event.button() == Qt.MouseButton.RightButton and self._pan_last is not None:
            self._pan_last = None
            self.setCursor(Qt.CursorShape.ArrowCursor)
            event.accept()
            return

        super().mouseReleaseEvent(event)

    def mouseDoubleClickEvent(self, event) -> None:  # type: ignore[override]
        if not self._pix_item.pixmap().isNull():
            self.resetTransform()
            self.fitInView(self._pix_item, Qt.AspectRatioMode.KeepAspectRatio)
            event.accept()
            return
        super().mouseDoubleClickEvent(event)
