# -*- coding: utf-8 -*-
# gui/components/model_view/contour/contour_geometry_component.py

# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_grid_sel_geom.py
# Геометрия / помощники полигонов, поиск ячеек

from __future__ import annotations

from typing import Optional, Tuple, TYPE_CHECKING

if TYPE_CHECKING:  # pragma: no cover
    from gui.components.model_view_widget import ModelViewWidget

class ContourGeometryComponent:
    def _is_axis_aligned_segment(
        self: "ModelViewWidget",
        a: tuple[float, float],
        b: tuple[float, float],
        eps: float = 1e-6,
    ) -> bool:
        return abs(a[0] - b[0]) <= eps or abs(a[1] - b[1]) <= eps

    def _extract_orthogonal_polygon(
        self: "ModelViewWidget",
    ) -> Optional[list[tuple[float, float]]]:
        points = list(self._contour_points)
        points = self._dedupe_contour_points(points)
        points = self._simplify_collinear_contour_points(points, closed=True)
        if len(points) < 4:
            return None
        eps = 1e-6
        unique = set((round(p[0], 6), round(p[1], 6)) for p in points)
        if len(unique) < 4:
            return None
        n = len(points)
        for i in range(n):
            a = points[i]
            b = points[(i + 1) % n]
            if not self._is_axis_aligned_segment(a, b, eps):
                return None
            if abs(a[0] - b[0]) <= eps and abs(a[1] - b[1]) <= eps:
                return None
        return points

    def _dedupe_contour_points(
        self: "ModelViewWidget",
        points: list[tuple[float, float]],
        eps: float = 1e-6,
    ) -> list[tuple[float, float]]:
        if not points:
            return []
        out: list[tuple[float, float]] = []
        for px, py in points:
            p = (float(px), float(py))
            if not out:
                out.append(p)
                continue
            lx, ly = out[-1]
            if abs(lx - p[0]) <= eps and abs(ly - p[1]) <= eps:
                continue
            out.append(p)
        return out

    def _is_collinear_middle_point(
        self: "ModelViewWidget",
        prev_pt: tuple[float, float],
        mid_pt: tuple[float, float],
        next_pt: tuple[float, float],
        eps: float = 1e-6,
    ) -> bool:
        px, py = prev_pt
        mx, my = mid_pt
        nx, ny = next_pt
        if abs(px - mx) <= eps and abs(mx - nx) <= eps:
            mn_y = min(py, ny) - eps
            mx_y = max(py, ny) + eps
            return mn_y <= my <= mx_y
        if abs(py - my) <= eps and abs(my - ny) <= eps:
            mn_x = min(px, nx) - eps
            mx_x = max(px, nx) + eps
            return mn_x <= mx <= mx_x
        return False

    def _simplify_collinear_contour_points(
        self: "ModelViewWidget",
        points: list[tuple[float, float]],
        closed: bool,
        eps: float = 1e-6,
    ) -> list[tuple[float, float]]:
        out = list(points)
        if len(out) < 3:
            return out
        changed = True
        while changed and len(out) >= 3:
            changed = False
            if closed:
                limit = len(out)
                for i in range(limit):
                    if len(out) < 4:
                        break
                    prev_pt = out[(i - 1) % len(out)]
                    mid_pt = out[i % len(out)]
                    next_pt = out[(i + 1) % len(out)]
                    if self._is_collinear_middle_point(prev_pt, mid_pt, next_pt, eps=eps):
                        out.pop(i % len(out))
                        changed = True
                        break
            else:
                for i in range(1, len(out) - 1):
                    prev_pt = out[i - 1]
                    mid_pt = out[i]
                    next_pt = out[i + 1]
                    if self._is_collinear_middle_point(prev_pt, mid_pt, next_pt, eps=eps):
                        out.pop(i)
                        changed = True
                        break
        return out

    def _point_on_segment(
        self: "ModelViewWidget",
        px: float, py: float,
        a: tuple[float, float],
        b: tuple[float, float],
        eps: float = 1e-6,
    ) -> bool:
        if abs(a[0] - b[0]) <= eps:
            if abs(px - a[0]) > eps:
                return False
            return min(a[1], b[1]) - eps <= py <= max(a[1], b[1]) + eps
        if abs(a[1] - b[1]) <= eps:
            if abs(py - a[1]) > eps:
                return False
            return min(a[0], b[0]) - eps <= px <= max(a[0], b[0]) + eps
        return False

    def _point_inside_or_on_polygon(
        self: "ModelViewWidget",
        px: float, py: float,
        polygon: list[tuple[float, float]],
    ) -> bool:
        eps = 1e-6
        inside = False
        n = len(polygon)
        for i in range(n):
            x1, y1 = polygon[i]
            x2, y2 = polygon[(i + 1) % n]
            if self._point_on_segment(px, py, (x1, y1), (x2, y2), eps):
                return True
            if (y1 > py) != (y2 > py):
                denom = (y2 - y1) if abs(y2 - y1) > eps else eps
                xinters = (x2 - x1) * (py - y1) / denom + x1
                if px < xinters:
                    inside = not inside
        return inside

    def _update_selected_cells_from_contour(self: "ModelViewWidget") -> None:
        polygon = self._extract_orthogonal_polygon()
        if polygon is None:
            self._selected_cells = set()
            self._selection_anchor = None
            self._selection_start_cell = None
            self._update_selection_visualization()
            return

        selected = set()
        for cell_id, (mn_x, mx_x, mn_y, mx_y) in self._grid_cells.items():
            cx = (mn_x + mx_x) * 0.5
            cy = (mn_y + mx_y) * 0.5
            if self._point_inside_or_on_polygon(cx, cy, polygon):
                selected.add(cell_id)
        self._selected_cells = selected
        self._selection_anchor = self._resolve_selection_anchor()
        self._selection_start_cell = None
        if self._selection_anchor is not None:
            ax, ay = self._selection_anchor
            eps = 1e-6
            for _, (mn_x, mx_x, mn_y, mx_y) in self._grid_cells.items():
                if abs(mn_x - ax) <= eps and abs(mn_y - ay) <= eps:
                    self._selection_start_cell = (mn_x, mx_x, mn_y, mx_y)
                    break
        self._update_selection_visualization()
