"""Mouse handlers for exclusion shapes."""

from __future__ import annotations


class ExclusionMixin:
    """Manage exclusion shapes (rectangles and freehand strokes) on the preview canvas."""

    def _exclude_start(self, event):
        if self.preview_img is None:
            return
        mode = getattr(self, "exclude_mode", "rect")
        x = max(0, min(self.preview_img.width - 1, int(event.x)))
        y = max(0, min(self.preview_img.height - 1, int(event.y)))
        if mode == "free":
            self._current_stroke = [(x, y)]
            width = int(getattr(self, "free_draw_width", 14))
            preview_id = getattr(self, "_stroke_preview_id", None)
            if preview_id:
                try:
                    self.canvas_orig.delete(preview_id)
                except Exception:
                    pass
            self._stroke_preview_id = self.canvas_orig.create_line(
                x,
                y,
                x,
                y,
                fill="yellow",
                width=width,
                smooth=True,
                capstyle="round",
                joinstyle="round",
            )
            self._rubber_start = None
            return
        x = max(0, min(self.preview_img.width - 1, int(event.x)))
        y = max(0, min(self.preview_img.height - 1, int(event.y)))
        self._rubber_start = (x, y)
        if self._rubber_id:
            try:
                self.canvas_orig.delete(self._rubber_id)
            except Exception:
                pass
        self._rubber_id = self.canvas_orig.create_rectangle(x, y, x, y, outline="yellow", width=2)

    def _exclude_drag(self, event):
        mode = getattr(self, "exclude_mode", "rect")
        if mode == "free":
            stroke = getattr(self, "_current_stroke", None)
            if not stroke:
                return
            x = max(0, min(self.preview_img.width - 1, int(event.x)))
            y = max(0, min(self.preview_img.height - 1, int(event.y)))
            if stroke[-1] != (x, y):
                stroke.append((x, y))
                preview_id = getattr(self, "_stroke_preview_id", None)
                if preview_id:
                    coords = [coord for point in stroke for coord in point]
                    self.canvas_orig.coords(preview_id, *coords)
            return
        if not self._rubber_start:
            return
        x0, y0 = self._rubber_start
        x1 = max(0, min(self.preview_img.width - 1, int(event.x)))
        y1 = max(0, min(self.preview_img.height - 1, int(event.y)))
        self.canvas_orig.coords(self._rubber_id, x0, y0, x1, y1)

    def _exclude_end(self, event):
        mode = getattr(self, "exclude_mode", "rect")
        if mode == "free":
            stroke = getattr(self, "_current_stroke", None)
            if stroke and len(stroke) > 1:
                cleaned = self._compress_stroke(stroke)
                if len(cleaned) > 1:
                    shape = {
                        "kind": "stroke",
                        "points": cleaned,
                        "width": int(getattr(self, "free_draw_width", 14)),
                    }
                    self.exclude_shapes.append(shape)
                    stamper = getattr(self, "_stamp_shape_on_mask", None)
                    if callable(stamper):
                        stamper(shape)
                    else:
                        self._exclude_mask_dirty = True
            self._current_stroke = None
            preview_id = getattr(self, "_stroke_preview_id", None)
            if preview_id:
                try:
                    self.canvas_orig.delete(preview_id)
                except Exception:
                    pass
            self._stroke_preview_id = None
            self.update_preview()
            return
        if not self._rubber_start:
            return
        x0, y0 = self._rubber_start
        x1 = max(0, min(self.preview_img.width - 1, int(event.x)))
        y1 = max(0, min(self.preview_img.height - 1, int(event.y)))
        rx0, rx1 = sorted((x0, x1))
        ry0, ry1 = sorted((y0, y1))
        if (rx1 - rx0) > 0 and (ry1 - ry0) > 0:
            shape = {"kind": "rect", "coords": (rx0, ry0, rx1, ry1)}
            self.exclude_shapes.append(shape)
            stamper = getattr(self, "_stamp_shape_on_mask", None)
            if callable(stamper):
                stamper(shape)
            else:
                self._exclude_mask_dirty = True
        if self._rubber_id:
            try:
                self.canvas_orig.delete(self._rubber_id)
            except Exception:
                pass
        self._rubber_start = None
        self._rubber_id = None
        self.update_preview()

    def clear_excludes(self):
        self.exclude_shapes = []
        self._rubber_start = None
        self._current_stroke = None
        if self._rubber_id:
            try:
                self.canvas_orig.delete(self._rubber_id)
            except Exception:
                pass
            self._rubber_id = None
        if self._stroke_preview_id:
            try:
                self.canvas_orig.delete(self._stroke_preview_id)
            except Exception:
                pass
            self._stroke_preview_id = None
        for item in getattr(self, "_exclude_canvas_ids", []):
            try:
                self.canvas_orig.delete(item)
            except Exception:
                pass
        self._exclude_canvas_ids = []
        self._exclude_mask = None
        self._exclude_mask_px = None
        self._exclude_mask_dirty = True
        self.update_preview()

    def undo_exclude(self):
        if not getattr(self, "exclude_shapes", None):
            return
        self.exclude_shapes.pop()
        self._exclude_mask_dirty = True
        self.update_preview()

    def toggle_exclusion_mode(self):
        current = getattr(self, "exclude_mode", "rect")
        next_mode = "free" if current == "rect" else "rect"
        self.exclude_mode = next_mode
        self._current_stroke = None
        if next_mode == "free":
            if self._rubber_id:
                try:
                    self.canvas_orig.delete(self._rubber_id)
                except Exception:
                    pass
                self._rubber_id = None
            self._rubber_start = None
        else:
            if self._stroke_preview_id:
                try:
                    self.canvas_orig.delete(self._stroke_preview_id)
                except Exception:
                    pass
                self._stroke_preview_id = None
        message_key = "status.free_draw_enabled" if next_mode == "free" else "status.free_draw_disabled"
        if hasattr(self, "status"):
            try:
                self.status.config(text=self._t(message_key))
            except Exception:
                pass

    @staticmethod
    def _compress_stroke(points: list[tuple[int, int]]) -> list[tuple[int, int]]:
        """Reduce duplicate points without altering the drawn path too much."""
        if not points:
            return []
        compressed: list[tuple[int, int]] = [points[0]]
        for point in points[1:]:
            if point != compressed[-1]:
                compressed.append(point)
        return compressed


__all__ = ["ExclusionMixin"]
