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

# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_grid_contour_overlay.py
# Режим наложения и обнаружение / валидация зон

from __future__ import annotations

from typing import TYPE_CHECKING

from PySide6.QtCore import QTimer
from PySide6.QtWidgets import QMessageBox

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

try:
    import pyvista as pv
except ImportError:  # pragma: no cover
    pv = None

class ContourOverlayComponent:
    def _get_contour_plane_z(self: "ModelViewWidget") -> float:
        z = self._get_grid_plane_z(getattr(self, "_grid_origin", None))
        if (
            self._contour_zone_overlay_enabled
            and self._contour_zone_mode == "overlay"
            and self._grid_plane_z_override is not None
        ):
            z = float(self._grid_plane_z_override)
        return float(z)

    def _get_contour_blocking_polygons(self: "ModelViewWidget") -> list[list[tuple[float, float]]]:
        plane_z = self._get_contour_plane_z()
        step = max(1.0, float(getattr(self, "_current_zone_size", 1.0)))
        eps = max(1e-6, step * 0.25)
        ignore_zone_id = self._contour_zone_id if self._contour_zone_mode == "overlay" else None
        edit_ignore_zone_id = getattr(self, "_contour_ignore_zone_id", None)
        blockers: list[list[tuple[float, float]]] = []
        for zone_id, polygon in self._zone_polygons.items():
            if zone_id == ignore_zone_id:
                continue
            if edit_ignore_zone_id and zone_id == edit_ignore_zone_id:
                continue
            if not self._zone_intersects_height(zone_id, plane_z, eps=eps):
                continue
            if polygon and len(polygon) >= 3:
                blockers.append(polygon)
        return blockers

    def _point_in_polygon_strict(
        self: "ModelViewWidget",
        px: float,
        py: float,
        polygon: list[tuple[float, float]],
        eps: float,
    ) -> bool:
        inside, boundary = self._classify_point_in_polygon(px, py, polygon, eps=eps)
        return bool(inside and not boundary)

    def _segments_strictly_intersect(
        self: "ModelViewWidget",
        a: tuple[float, float],
        b: tuple[float, float],
        c: tuple[float, float],
        d: tuple[float, float],
        eps: float,
    ) -> bool:
        def orient(p: tuple[float, float], q: tuple[float, float], r: tuple[float, float]) -> float:
            return (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])

        o1 = orient(a, b, c)
        o2 = orient(a, b, d)
        o3 = orient(c, d, a)
        o4 = orient(c, d, b)

        if abs(o1) <= eps or abs(o2) <= eps or abs(o3) <= eps or abs(o4) <= eps:
            return False
        return (o1 > 0) != (o2 > 0) and (o3 > 0) != (o4 > 0)

    def _segment_crosses_polygon_strict(
        self: "ModelViewWidget",
        a: tuple[float, float],
        b: tuple[float, float],
        polygon: list[tuple[float, float]],
        eps: float,
    ) -> bool:
        if self._point_in_polygon_strict(a[0], a[1], polygon, eps=eps):
            return True
        if self._point_in_polygon_strict(b[0], b[1], polygon, eps=eps):
            return True

        n = len(polygon)
        for i in range(n):
            c = polygon[i]
            d = polygon[(i + 1) % n]
            if self._segments_strictly_intersect(a, b, c, d, eps=eps):
                return True

        mx = (a[0] + b[0]) * 0.5
        my = (a[1] + b[1]) * 0.5
        if self._point_in_polygon_strict(mx, my, polygon, eps=eps):
            return True
        return False

    def _segments_intersect_or_touch(
        self: "ModelViewWidget",
        a: tuple[float, float],
        b: tuple[float, float],
        c: tuple[float, float],
        d: tuple[float, float],
        eps: float,
    ) -> bool:
        """Пересечение отрезков с учётом касаний и коллинеарности."""

        def orient(p: tuple[float, float], q: tuple[float, float], r: tuple[float, float]) -> float:
            return (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0])

        o1 = orient(a, b, c)
        o2 = orient(a, b, d)
        o3 = orient(c, d, a)
        o4 = orient(c, d, b)

        # Общий случай пересечения.
        if ((o1 > eps and o2 < -eps) or (o1 < -eps and o2 > eps)) and (
            (o3 > eps and o4 < -eps) or (o3 < -eps and o4 > eps)
        ):
            return True

        # Касания/коллинеарность.
        if abs(o1) <= eps and self._point_on_segment_2d(c[0], c[1], a, b, eps):
            return True
        if abs(o2) <= eps and self._point_on_segment_2d(d[0], d[1], a, b, eps):
            return True
        if abs(o3) <= eps and self._point_on_segment_2d(a[0], a[1], c, d, eps):
            return True
        if abs(o4) <= eps and self._point_on_segment_2d(b[0], b[1], c, d, eps):
            return True
        return False

    def _segment_intersects_polygon_any(
        self: "ModelViewWidget",
        a: tuple[float, float],
        b: tuple[float, float],
        polygon: list[tuple[float, float]],
        eps: float,
    ) -> bool:
        """True, если отрезок пересекает ИЛИ касается полигона."""
        a_inside, a_boundary = self._classify_point_in_polygon(a[0], a[1], polygon, eps=eps)
        if a_inside or a_boundary:
            return True
        b_inside, b_boundary = self._classify_point_in_polygon(b[0], b[1], polygon, eps=eps)
        if b_inside or b_boundary:
            return True

        n = len(polygon)
        for i in range(n):
            c = polygon[i]
            d = polygon[(i + 1) % n]
            if self._segments_intersect_or_touch(a, b, c, d, eps=eps):
                return True

        mx = (a[0] + b[0]) * 0.5
        my = (a[1] + b[1]) * 0.5
        m_inside, m_boundary = self._classify_point_in_polygon(mx, my, polygon, eps=eps)
        return bool(m_inside or m_boundary)

    def _validate_contour_point_against_existing_volumes(
        self: "ModelViewWidget",
        nx: float,
        ny: float,
    ) -> bool:
        step = max(1.0, float(getattr(self, "_current_zone_size", 1.0)))
        eps = max(1e-6, step * 0.25)
        blockers = self._get_contour_blocking_polygons()

        is_overlay = bool(getattr(self, "_contour_zone_mode", "") == "overlay")

        for polygon in blockers:
            if is_overlay:
                # В overlay допускаем смежные границы зон; запрещаем только
                # попадание внутрь чужого объёма.
                if self._point_in_polygon_strict(nx, ny, polygon, eps=eps):
                    return False
                continue
            inside, boundary = self._classify_point_in_polygon(nx, ny, polygon, eps=eps)
            if inside or boundary:
                return False

        if not self._contour_points:
            return True

        a = self._contour_points[-1]
        b = (float(nx), float(ny))
        for polygon in blockers:
            if is_overlay:
                # В overlay разрешаем касания/проход по смежной границе.
                if self._segment_crosses_polygon_strict(a, b, polygon, eps=eps):
                    return False
            elif self._segment_intersects_polygon_any(a, b, polygon, eps=eps):
                return False
        return True
