# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_grid_core_floor.py
# Помощники геометрии пола: извлечение треугольников, проверки вхождения точки/ячейки

from __future__ import annotations

from typing import TYPE_CHECKING

from error_logger import log_exception

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


class GridCoreFloorMixin:
    """Геометрия пола: извлечение треугольников и 2D-проверки вхождения."""

    # ------------------------------------------------------------------
    # Геометрия пола
    # ------------------------------------------------------------------

    def _extract_floor_triangles_2d(
        self: "ModelViewWidget",
    ) -> list[tuple[tuple[float, float], tuple[float, float], tuple[float, float]]]:
        floor_mesh = getattr(self, "_floor_mesh", None)
        if floor_mesh is None:
            return []
        try:
            tri = floor_mesh.extract_surface().triangulate()
            pts = tri.points
            faces = tri.faces
            if pts is None or faces is None or len(faces) == 0:
                return []
            triangles: list[tuple[tuple[float, float], tuple[float, float], tuple[float, float]]] = []
            idx = 0
            faces_len = len(faces)
            while idx < faces_len:
                n = int(faces[idx])
                if n < 3:
                    idx += n + 1
                    continue
                i0 = int(faces[idx + 1])
                for k in range(2, n):
                    i1 = int(faces[idx + k])
                    i2 = int(faces[idx + k + 1])
                    a = (float(pts[i0][0]), float(pts[i0][1]))
                    b = (float(pts[i1][0]), float(pts[i1][1]))
                    c = (float(pts[i2][0]), float(pts[i2][1]))
                    triangles.append((a, b, c))
                idx += n + 1
            return triangles
        except Exception as e:
            log_exception(__name__, "_extract_floor_triangles_2d", e)
            return []

    def _is_cell_inside_floor_2d(
        self: "ModelViewWidget",
        min_x: float, max_x: float,
        min_y: float, max_y: float,
        floor_triangles: list[tuple[tuple[float, float], tuple[float, float], tuple[float, float]]],
    ) -> bool:
        if not floor_triangles:
            return True
        corners = (
            (min_x, min_y), (max_x, min_y),
            (max_x, max_y), (min_x, max_y),
        )
        for px, py in corners:
            if not self._is_point_inside_floor_2d(px, py, floor_triangles):
                return False
        return True

    def _is_point_inside_floor_2d(
        self: "ModelViewWidget",
        px: float, py: float,
        floor_triangles: list[tuple[tuple[float, float], tuple[float, float], tuple[float, float]]],
    ) -> bool:
        for a, b, c in floor_triangles:
            if self._point_in_triangle_2d(px, py, a, b, c):
                return True
        return False

    def _point_in_triangle_2d(
        self: "ModelViewWidget",
        px: float, py: float,
        a: tuple[float, float],
        b: tuple[float, float],
        c: tuple[float, float],
        eps: float = 1e-7,
    ) -> bool:
        def sign(p1, p2, p3):
            return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1])

        p = (px, py)
        d1 = sign(p, a, b)
        d2 = sign(p, b, c)
        d3 = sign(p, c, a)
        has_neg = (d1 < -eps) or (d2 < -eps) or (d3 < -eps)
        has_pos = (d1 > eps) or (d2 > eps) or (d3 > eps)
        return not (has_neg and has_pos)


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Геометрический контроль попадания сетки и точек в пол помещения по треугольной геометрии.
#
# 2) Последовательность действий и вызовов:
#    A. Класс GridCoreFloorMixin: точки входа
#       Публичные методы отсутствуют; сценарий запускается через методы родительских модулей и внутренние обработчики.
#
#    B. GridCoreFloorMixin: основной сценарий:
#       GridCoreFloorMixin._is_cell_inside_floor_2d(...)
#         Назначение: проверяет, что cell inside floor 2d в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> GridCoreFloorMixin._is_point_inside_floor_2d(...)
#       GridCoreFloorMixin._is_point_inside_floor_2d(...)
#         Назначение: проверяет, что point inside floor 2d в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> GridCoreFloorMixin._point_in_triangle_2d(...)
#
#    C. GridCoreFloorMixin: вспомогательные расчёты:
#       GridCoreFloorMixin._extract_floor_triangles_2d(...)
#         Назначение: извлекает floor triangles 2d в рамках текущего сценария модуля.
#       GridCoreFloorMixin._point_in_triangle_2d(...)
#         Назначение: выполняет шаг "point in triangle 2d" в рамках текущего сценария модуля.
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
