# -*- coding: utf-8 -*-
"""
LVT Layer Compare Tool -- tool.py
==================================
Author  : Loc Vu Trung / LVT Center
License : MIT

So sanh 2 lop ban do bang thanh chia (divider) keo duoc theo
chieu ngang HOAC doc.

FIX v2:
  - Pan ban do khi keo NGOAI divider (trai/phai)
  - Zoom bang scroll wheel (khong doi map tool)
  - Khong double-emit deactivated signal
  - transparent color fix
"""

from qgis.PyQt.QtCore import Qt, QPoint, QRect, QObject, pyqtSignal
from ..shared.qt_compat import QtCompat
from qgis.PyQt.QtGui import (
    QColor, QPainter, QPen, QBrush, QLinearGradient,
    QCursor, QImage,
)
from qgis.core import (
    QgsMapLayer, QgsMapSettings, QgsMapRendererParallelJob,
    QgsProject, QgsPointXY,
)
from qgis.gui import (
    QgsMapCanvas, QgsMapCanvasItem, QgsMapTool, QgsMapMouseEvent,
)


# ─────────────────────────────────────────────────────────────
# Signals helper (QgsMapCanvasItem khong phai QObject)
# ─────────────────────────────────────────────────────────────
class _CompareSignals(QObject):
    rendering_started = pyqtSignal(str)   # layer name
    rendering_done    = pyqtSignal()


# ─────────────────────────────────────────────────────────────
# Canvas overlay item
# ─────────────────────────────────────────────────────────────
class CompareCanvasItem(QgsMapCanvasItem):
    """
    Ve clip cua layer_b len canvas, cat theo vi tri divider.

    Chieu NGANG (horizontal=True):
        layer_b chiem phan BEN TRAI tinh tu divider_x
    Chieu DOC  (horizontal=False):
        layer_b chiem phan PHIA TREN tinh tu divider_y

    Nguoi dung co the SWAP de doi phan nao hien layer_b.
    """

    DIVIDER_THICK = 3
    HANDLE_RADIUS = 14
    DIVIDER_COLOR = QColor(255, 90, 0)
    HANDLE_COLOR  = QColor(255, 255, 255)

    def __init__(self, canvas: QgsMapCanvas):
        super().__init__(canvas)
        self.canvas      = canvas
        self.setZValue(20)

        self.signals     = _CompareSignals()
        self.layer_b     = None
        self.image_b     = None
        self._job        = None

        self.divider_pos = 0.5
        self.horizontal  = True
        self.swap        = False
        self._active     = False

    # ── Activate / deactivate ─────────────────────────────────
    def activate(self):
        self._active = True
        self._request_render()

    def deactivate(self):
        self._active = False
        if self._job:
            try:
                self._job.cancel()
            except Exception:  # nosec B110
                pass  # nosec B110
            self._job = None
        self.image_b = None
        self.updateCanvas()

    # ── Render layer B ────────────────────────────────────────
    def _request_render(self):
        if not self._active or self.layer_b is None:
            return

        self.signals.rendering_started.emit(
            self.layer_b.name() if self.layer_b else ""
        )

        settings = QgsMapSettings()
        cs = self.canvas.mapSettings()
        settings.setBackgroundColor(QColor(0, 0, 0, 0))   # fully transparent
        settings.setDevicePixelRatio(1)
        settings.setLayers([self.layer_b])
        settings.setDestinationCrs(cs.destinationCrs())
        settings.setOutputSize(self.canvas.size())
        settings.setExtent(self.canvas.extent())

        self.setRect(self.canvas.extent())
        self.image_b = None

        job = QgsMapRendererParallelJob(settings)
        self._job = job

        # FIX: capture `job` by default-arg so closure holds a strong ref
        # even after _request_render() returns and its frame is released.
        # Without this, Python raises NameError on the closure variable.
        def _done(j=job):
            if j is not self._job:
                return   # render job cu, bo qua
            self.image_b = j.renderedImage()
            self._job = None
            self.signals.rendering_done.emit()
            self.updateCanvas()

        job.finished.connect(_done)
        job.start()

    def refresh(self):
        """Goi khi canvas extent thay doi (pan/zoom)."""
        if self._active:
            self._request_render()

    def set_layer(self, layer):
        self.layer_b = layer
        if self._active:
            self._request_render()

    # ── Paint ─────────────────────────────────────────────────
    def paint(self, painter: QPainter, *args):
        if not self._active or self.image_b is None:
            return

        w = int(self.boundingRect().width())
        h = int(self.boundingRect().height())

        if self.horizontal:
            split = int(self.divider_pos * w)
            clip  = QRect(split, 0, w - split, h) if self.swap else QRect(0, 0, split, h)
            painter.save()
            painter.setClipRect(clip)
            painter.drawImage(0, 0, self.image_b)
            painter.restore()
            self._draw_vertical_divider(painter, split, h)

        else:
            split = int(self.divider_pos * h)
            clip  = QRect(0, split, w, h - split) if self.swap else QRect(0, 0, w, split)
            painter.save()
            painter.setClipRect(clip)
            painter.drawImage(0, 0, self.image_b)
            painter.restore()
            self._draw_horizontal_divider(painter, split, w)

    def _draw_vertical_divider(self, p: QPainter, x: int, h: int):
        grad = QLinearGradient(x - 6, 0, x + 6, 0)
        grad.setColorAt(0,   QColor(0, 0, 0, 0))
        grad.setColorAt(0.4, QColor(0, 0, 0, 60))
        grad.setColorAt(0.5, QColor(0, 0, 0, 80))
        grad.setColorAt(0.6, QColor(0, 0, 0, 60))
        grad.setColorAt(1,   QColor(0, 0, 0, 0))
        p.fillRect(QRect(x - 6, 0, 12, h), grad)

        p.setPen(QPen(self.DIVIDER_COLOR, self.DIVIDER_THICK))
        p.drawLine(x, 0, x, h)

        cy = h // 2
        p.setPen(QPen(self.DIVIDER_COLOR, 2))
        p.setBrush(QBrush(self.HANDLE_COLOR))
        p.setRenderHint(QPainter.RenderHint.Antialiasing, True)
        p.drawEllipse(QPoint(x, cy), self.HANDLE_RADIUS, self.HANDLE_RADIUS)
        ar = 5
        p.setPen(QPen(self.DIVIDER_COLOR, 2))
        p.drawLine(x - ar, cy, x + ar, cy)
        p.drawLine(x - ar, cy, x - ar + 2, cy - 2)
        p.drawLine(x - ar, cy, x - ar + 2, cy + 2)
        p.drawLine(x + ar, cy, x + ar - 2, cy - 2)
        p.drawLine(x + ar, cy, x + ar - 2, cy + 2)

    def _draw_horizontal_divider(self, p: QPainter, y: int, w: int):
        grad = QLinearGradient(0, y - 6, 0, y + 6)
        grad.setColorAt(0,   QColor(0, 0, 0, 0))
        grad.setColorAt(0.4, QColor(0, 0, 0, 60))
        grad.setColorAt(0.5, QColor(0, 0, 0, 80))
        grad.setColorAt(0.6, QColor(0, 0, 0, 60))
        grad.setColorAt(1,   QColor(0, 0, 0, 0))
        p.fillRect(QRect(0, y - 6, w, 12), grad)

        p.setPen(QPen(self.DIVIDER_COLOR, self.DIVIDER_THICK))
        p.drawLine(0, y, w, y)

        cx = w // 2
        p.setPen(QPen(self.DIVIDER_COLOR, 2))
        p.setBrush(QBrush(self.HANDLE_COLOR))
        p.setRenderHint(QPainter.RenderHint.Antialiasing, True)
        p.drawEllipse(QPoint(cx, y), self.HANDLE_RADIUS, self.HANDLE_RADIUS)
        ar = 5
        p.setPen(QPen(self.DIVIDER_COLOR, 2))
        p.drawLine(cx, y - ar, cx, y + ar)
        p.drawLine(cx, y - ar, cx - 2, y - ar + 2)
        p.drawLine(cx, y - ar, cx + 2, y - ar + 2)
        p.drawLine(cx, y + ar, cx - 2, y + ar - 2)
        p.drawLine(cx, y + ar, cx + 2, y + ar - 2)


# ─────────────────────────────────────────────────────────────
# Map Tool -- divider drag + pan + zoom in one tool
# ─────────────────────────────────────────────────────────────
class LayerCompareTool(QgsMapTool):
    """
    Map tool tich hop 3 chuc nang:
      1. Keo SAT duong chia  -> di chuyen divider
      2. Keo NGOAI duong chia -> pan ban do (khong doi tool)
      3. Scroll chuot         -> zoom ban do (khong doi tool)

    FIX: khong emit deactivated trong override deactivate()
    de tranh double-emission lam _stop() bi goi sai.
    """

    HANDLE_HIT_PX = 24      # px -- vung nhan biet divider
    ZOOM_FACTOR   = 1.2     # ty le zoom moi buoc scroll

    def __init__(self, canvas: QgsMapCanvas, item: CompareCanvasItem):
        super().__init__(canvas)
        self.canvas      = canvas
        self.item        = item
        self._dragging   = False   # dang keo divider
        self._panning    = False   # dang pan ban do
        self._pan_start  = None    # QPoint screen coords
        self._pan_center = None    # QgsPointXY map coords

    # ── Cursor ────────────────────────────────────────────────
    def _update_cursor(self, x: int, y: int):
        if self._near_divider(x, y):
            try:
                cur = (QtCompat.SplitHCursor if self.item.horizontal
                       else QtCompat.SplitVCursor)
            except AttributeError:
                cur = QtCompat.ArrowCursor
            self.canvas.setCursor(QCursor(cur))
        elif self._panning:
            try:
                self.canvas.setCursor(QCursor(QtCompat.ClosedHandCursor))
            except AttributeError:
                self.canvas.setCursor(QCursor(QtCompat.ArrowCursor))
        else:
            self.canvas.setCursor(QCursor(QtCompat.ArrowCursor))

    def _near_divider(self, x: int, y: int) -> bool:
        w = self.canvas.width()
        h = self.canvas.height()
        if self.item.horizontal:
            div_x = int(self.item.divider_pos * w)
            return abs(x - div_x) <= self.HANDLE_HIT_PX
        else:
            div_y = int(self.item.divider_pos * h)
            return abs(y - div_y) <= self.HANDLE_HIT_PX

    # ── Mouse events ──────────────────────────────────────────
    def canvasPressEvent(self, e: QgsMapMouseEvent):
        try:
            left_btn   = QtCompat.LeftButton
            middle_btn = QtCompat.MiddleButton
        except AttributeError:
            left_btn   = Qt.MouseButton.LeftButton
            middle_btn = Qt.MouseButton.MiddleButton

        if e.button() == left_btn:
            if self._near_divider(e.x(), e.y()):
                # Keo divider
                self._dragging = True
                self._panning  = False
            else:
                # Pan ban do
                self._panning    = True
                self._dragging   = False
                self._pan_start  = QPoint(e.x(), e.y())
                self._pan_center = self.canvas.center()
                try:
                    self.canvas.setCursor(QCursor(QtCompat.ClosedHandCursor))
                except AttributeError:
                    pass  # nosec B110

        elif e.button() == middle_btn:
            # Middle-drag pan
            self._panning    = True
            self._dragging   = False
            self._pan_start  = QPoint(e.x(), e.y())
            self._pan_center = self.canvas.center()

    def canvasReleaseEvent(self, e: QgsMapMouseEvent):
        try:
            left_btn   = QtCompat.LeftButton
            middle_btn = QtCompat.MiddleButton
        except AttributeError:
            left_btn   = Qt.MouseButton.LeftButton
            middle_btn = Qt.MouseButton.MiddleButton

        if e.button() in (left_btn, middle_btn):
            self._dragging   = False
            self._panning    = False
            self._pan_start  = None
            self._pan_center = None
            self._update_cursor(e.x(), e.y())

    def canvasMoveEvent(self, e: QgsMapMouseEvent):
        x, y = e.x(), e.y()

        if self._dragging:
            # Di chuyen divider
            w = self.canvas.width()
            h = self.canvas.height()
            if self.item.horizontal:
                pos = max(0.02, min(0.98, x / w))
            else:
                pos = max(0.02, min(0.98, y / h))
            self.item.divider_pos = pos
            self.item.updateCanvas()

        elif self._panning and self._pan_start is not None:
            # Pan ban do: tinh delta tu vi tri bat dau
            dx  = x - self._pan_start.x()
            dy  = y - self._pan_start.y()
            mul = self.canvas.mapUnitsPerPixel()
            new_center = QgsPointXY(
                self._pan_center.x() - dx * mul,
                self._pan_center.y() + dy * mul,
            )
            self.canvas.setCenter(new_center)
            self.canvas.refresh()

        else:
            # Chi update cursor khi khong drag/pan
            self._update_cursor(x, y)

    def wheelEvent(self, e):
        """
        Zoom bang scroll wheel tai vi tri con tro.
        Goi canvas.zoomWithCenter() truc tiep -- KHONG doi map tool.
        """
        try:
            delta = e.angleDelta().y()
            if delta == 0:
                return
            # Lay vi tri scroll
            if hasattr(e, 'position'):
                pos = e.position()          # Qt6 QPointF
                px, py = int(pos.x()), int(pos.y())
            else:
                pos = e.pos()               # Qt5 QPoint
                px, py = pos.x(), pos.y()
            zoom_in = delta > 0
            self.canvas.zoomWithCenter(px, py, zoom_in)
        except (RuntimeError, AttributeError):  # nosec B110
            pass   # fallback: ignore

    def activate(self):
        super().activate()
        self.canvas.setCursor(QCursor(QtCompat.ArrowCursor))

    def deactivate(self):
        """
        QUAN TRONG: Chi goi super().deactivate().
        KHONG emit deactivated.emit() o day -- canvas tu emit sau khi switch tool.
        Neu emit 2 lan, _on_tool_deactivated se goi _stop() 2 lan -> bug.
        """
        self._dragging = False
        self._panning  = False
        super().deactivate()
        # DO NOT call self.deactivated.emit() here
