# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_zones_highlight.py
# Подсветка зон при наведении/выделении

from __future__ import annotations

from typing import Optional, TYPE_CHECKING
from error_logger import log_exception

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


class ZoneHighlightMixin:
    """Подсветка зон при наведении и выделении."""

    def _find_zone_by_actor(self: "ModelViewWidget", picked_actor) -> Optional[str]:
        """Найти зону по VTK-актору."""
        if picked_actor is None:
            return None
        for zone_id, zone_actor in self._zones.items():
            if zone_actor is picked_actor or zone_actor == picked_actor:
                return zone_id
            try:
                if (
                    hasattr(zone_actor, "GetAddressAsString")
                    and hasattr(picked_actor, "GetAddressAsString")
                    and zone_actor.GetAddressAsString("") == picked_actor.GetAddressAsString("")
                ):
                    return zone_id
            except Exception as _exc:
                log_exception(__name__, "_find_zone_by_actor", _exc)
        # Акторы контуров объекта также могут быть возвращены пикером.
        try:
            contour_actors = self._get_facility_zone_contour_actors()
        except Exception as exc:
            log_exception(__name__, "_find_zone_by_actor", exc)
            contour_actors = {}
        for zone_id, contour_actor in contour_actors.items():
            if contour_actor is picked_actor or contour_actor == picked_actor:
                return zone_id
            try:
                if (
                    hasattr(contour_actor, "GetAddressAsString")
                    and hasattr(picked_actor, "GetAddressAsString")
                    and contour_actor.GetAddressAsString("") == picked_actor.GetAddressAsString("")
                ):
                    return zone_id
            except Exception as _exc:
                log_exception(__name__, "_find_zone_by_actor", _exc)
        return None

    def _find_zone_by_screen_ray(self: "ModelViewWidget", sx: float, sy: float) -> Optional[str]:
        """Найти ближайшую зону, пересечённую лучом камеры через точку экрана."""
        if not self._plotter or not self._plotter.renderer:
            return None

        try:
            renderer = self._plotter.renderer
            renderer.SetDisplayPoint(float(sx), float(sy), 0.0)
            renderer.DisplayToWorld()
            p0w = renderer.GetWorldPoint()
            renderer.SetDisplayPoint(float(sx), float(sy), 1.0)
            renderer.DisplayToWorld()
            p1w = renderer.GetWorldPoint()
            if p0w[3] == 0 or p1w[3] == 0:
                return None
            p0 = (p0w[0] / p0w[3], p0w[1] / p0w[3], p0w[2] / p0w[3])
            p1 = (p1w[0] / p1w[3], p1w[1] / p1w[3], p1w[2] / p1w[3])
        except Exception as exc:
            log_exception(__name__, "_find_zone_by_screen_ray", exc)
            return None

        dx = float(p1[0] - p0[0])
        dy = float(p1[1] - p0[1])
        dz = float(p1[2] - p0[2])
        eps = 1e-9

        def _axis_slab(o: float, d: float, mn: float, mx: float) -> tuple[float, float] | None:
            if abs(d) <= eps:
                if o < mn or o > mx:
                    return None
                return (-float("inf"), float("inf"))
            t1 = (mn - o) / d
            t2 = (mx - o) / d
            return (t1, t2) if t1 <= t2 else (t2, t1)

        best_zone_id = None
        best_t = None
        for zone_id, bounds in self._zone_data.items():
            try:
                mn_x, mx_x, mn_y, mx_y, mn_z, mx_z = bounds
                xr = _axis_slab(float(p0[0]), dx, float(mn_x), float(mx_x))
                yr = _axis_slab(float(p0[1]), dy, float(mn_y), float(mx_y))
                zr = _axis_slab(float(p0[2]), dz, float(mn_z), float(mx_z))
                if xr is None or yr is None or zr is None:
                    continue
                t_enter = max(xr[0], yr[0], zr[0])
                t_exit = min(xr[1], yr[1], zr[1])
                if t_exit < max(t_enter, 0.0):
                    continue
                t_hit = t_enter if t_enter >= 0.0 else t_exit
                if t_hit < 0.0:
                    continue
                if best_t is None or t_hit < best_t:
                    best_t = t_hit
                    best_zone_id = zone_id
            except Exception as exc:
                log_exception(__name__, "_find_zone_by_screen_ray", exc)
                continue
        return best_zone_id

    def _handle_zone_hover_event(
        self: "ModelViewWidget", sx: int, sy: int,
    ) -> None:
        """Определить зону под курсором и подсветить/снять подсветку.

        1. vtkPropPicker — точный пиксельный pick по видимой геометрии.
           Корректно определяет глубину (Z1 vs Z2), работает на всей
           поверхности, а не только на углах/рёбрах.
        2. Fallback: screen_to_world + 2D полигональный тест — только для
           зон в контурном режиме (solid скрыт), которые не видны пикеру.
        """
        if getattr(self, "_hide_empty_zones_mode", False):
            self._unhighlight_hover_zone()
            return

        selected_zone_id = str(getattr(self, "_selected_zone_highlight_id", "") or "")
        if selected_zone_id and selected_zone_id not in self._zones:
            self._selected_zone_highlight_id = None

        if not self._plotter or not self._plotter.renderer:
            self._unhighlight_hover_zone()
            return

        zone_id = None

        # --- 1. vtkPropPicker: точное определение ближайшего актора --------
        try:
            from vtkmodules.vtkRenderingCore import vtkPropPicker
            prop_picker = vtkPropPicker()
            prop_picker.Pick(sx, sy, 0, self._plotter.renderer)
            picked_actor = prop_picker.GetViewProp()
            if picked_actor is not None:
                zone_id = self._find_zone_by_actor(picked_actor)
        except Exception as _exc:
            log_exception(__name__, "_handle_zone_hover_event", _exc)
        # --- 2. Запасной вариант: пересечение луча с 3D-границами зоны -----
        if zone_id is None:
            zone_id = self._find_zone_by_screen_ray(sx, sy)

        # --- 3. Запасной вариант: 2D-полигональный тест с приоритетом верхней зоны
        if zone_id is None:
            plane_z = self._get_grid_plane_z(getattr(self, "_grid_origin", None))
            world = self.screen_to_world_on_plane(sx, sy, plane_z)
            if world is not None:
                wx, wy = world[0], world[1]
                best_top_z = None
                for zid, polygon in self._zone_polygons.items():
                    if not polygon or len(polygon) < 3:
                        continue
                    inside, _ = self._classify_point_in_polygon(wx, wy, polygon)
                    if inside:
                        top_z = self._get_zone_top_height(zid)
                        if best_top_z is None or top_z > best_top_z:
                            best_top_z = top_z
                            zone_id = zid

        if zone_id == getattr(self, "_hover_highlighted_zone_id", None):
            return  # та же зона — ничего не делаем

        self._unhighlight_hover_zone()

        if zone_id is not None:
            self._highlight_hover_zone(zone_id)

    def set_selected_zone_highlight(self: "ModelViewWidget", zone_id: str | None) -> None:
        """Закрепить подсветку выбранной зоны до явного сброса."""
        zid = str(zone_id or "")
        if not zid or zid not in self._zones:
            self.clear_selected_zone_highlight()
            return
        if getattr(self, "_selected_zone_highlight_id", None) == zid:
            if getattr(self, "_hover_highlighted_zone_id", None) != zid:
                self._unhighlight_hover_zone()
                self._highlight_hover_zone(zid)
            return
        self.clear_selected_zone_highlight()
        actor = self._zones.get(zid)
        if actor is not None:
            try:
                prop = actor.GetProperty()
                self._selected_zone_original_visibility = bool(actor.GetVisibility())
                self._selected_zone_original_opacity = float(prop.GetOpacity())
                actor.SetVisibility(1)
                prop.SetOpacity(1.0)
            except Exception as exc:
                log_exception(__name__, "set_selected_zone_highlight", exc)
                self._selected_zone_original_opacity = None
                self._selected_zone_original_visibility = None
        else:
            self._selected_zone_original_opacity = None
            self._selected_zone_original_visibility = None
        self._set_zone_contour_highlight(zid, active=True)
        self._hover_highlighted_zone_id = zid
        self._selected_zone_highlight_id = zid
        self._safe_render(min_interval_s=1.0 / 60.0)

    def clear_selected_zone_highlight(self: "ModelViewWidget") -> None:
        """Снять закреплённую подсветку выбранной зоны."""
        locked_zone_id = str(getattr(self, "_selected_zone_highlight_id", "") or "")
        self._selected_zone_highlight_id = None
        if not locked_zone_id:
            return
        actor = self._zones.get(locked_zone_id)
        if actor is not None:
            try:
                prop = actor.GetProperty()
                orig_opacity = getattr(self, "_selected_zone_original_opacity", None)
                if orig_opacity is not None:
                    prop.SetOpacity(float(orig_opacity))
                orig_vis = getattr(self, "_selected_zone_original_visibility", None)
                if orig_vis is not None:
                    actor.SetVisibility(1 if orig_vis else 0)
            except Exception as _exc:
                log_exception(__name__, "clear_selected_zone_highlight", _exc)
        self._selected_zone_original_opacity = None
        self._selected_zone_original_visibility = None
        if getattr(self, "_hover_highlighted_zone_id", None) == locked_zone_id:
            self._hover_highlighted_zone_id = None
        self._teardown_zone_contour_if_temporary(locked_zone_id)
        self._safe_render(min_interval_s=1.0 / 60.0)

    def _is_zone_in_contour_mode(self: "ModelViewWidget", zone_id: str) -> bool:
        """Проверить, отображена ли зона в контурном режиме (solid скрыт)."""
        actor = self._zones.get(zone_id)
        if actor is None:
            return False
        try:
            if not actor.GetVisibility():
                # Solid скрыт — проверим, есть ли facility contour
                contour_actors = self._get_facility_zone_contour_actors()
                contour_actor = contour_actors.get(zone_id)
                if contour_actor is not None:
                    return True
        except Exception as _exc:
            log_exception(__name__, "_is_zone_in_contour_mode", _exc)
        return False

    def _zone_has_rack_payload(self: "ModelViewWidget", zone_id: str) -> bool:
        try:
            return bool(self.has_racks_in_zone(zone_id))
        except Exception as exc:
            log_exception(__name__, "_zone_has_rack_payload", exc)
            return False

    def _set_zone_contour_highlight(
        self: "ModelViewWidget",
        zone_id: str,
        active: bool,
    ) -> None:
        contour_actor = self._ensure_zone_facility_contour(zone_id)
        if contour_actor is None:
            return
        try:
            prop = contour_actor.GetProperty()
            if prop is None:
                return
            if active:
                prop.SetColor(1.0, 1.0, 0.3)
                prop.SetLineWidth(3.0)
            else:
                prop.SetColor(1.0, 1.0, 1.0)
                prop.SetLineWidth(2.0)
            contour_actor.SetVisibility(1)
        except Exception as _exc:
            log_exception(__name__, "_set_zone_contour_highlight", _exc)
    def _teardown_zone_contour_if_temporary(self: "ModelViewWidget", zone_id: str) -> None:
        if self._zone_has_rack_payload(zone_id):
            self._set_zone_contour_highlight(zone_id, active=False)
            return
        self._remove_zone_facility_contour(zone_id)

    def _highlight_hover_zone(self: "ModelViewWidget", zone_id: str) -> None:
        """Подсветить зону при hover только по контурным линиям."""
        if zone_id not in self._zones:
            return
        self._set_zone_contour_highlight(zone_id, active=True)
        self._hover_highlighted_zone_id = zone_id
        self._safe_render(min_interval_s=1.0 / 60.0)

    def _unhighlight_hover_zone(self: "ModelViewWidget") -> None:
        """Снять подсветку с ранее подсвеченной зоны."""
        zone_id = getattr(self, "_hover_highlighted_zone_id", None)
        if zone_id is None:
            return
        self._teardown_zone_contour_if_temporary(str(zone_id))
        self._hover_highlighted_zone_id = None
        self._hover_zone_original_opacity = None
        self._hover_zone_original_edges = False
        self._hover_zone_original_visibility = None
        self._hover_zone_contour_original_color = None
        self._safe_render(min_interval_s=1.0 / 60.0)


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Подсветка зон при наведении и выборе.
#
# 2) Последовательность действий и вызовов:
#    A. Класс ZoneHighlightMixin: точки входа
#       Публичные методы сценария:
#       - ZoneHighlightMixin.set_selected_zone_highlight(...)
#       - ZoneHighlightMixin.clear_selected_zone_highlight(...)
#
#    B. ZoneHighlightMixin: запуск и настройка:
#       ZoneHighlightMixin.set_selected_zone_highlight(...)
#         Назначение: Закрепить подсветку выбранной зоны до явного сброса.
#         Последовательность внутренних вызовов:
#         -> ZoneHighlightMixin.clear_selected_zone_highlight(...)
#         -> ZoneHighlightMixin._set_zone_contour_highlight(...)
#         -> ZoneHighlightMixin._unhighlight_hover_zone(...)
#         -> ZoneHighlightMixin._highlight_hover_zone(...)
#       ZoneHighlightMixin._set_zone_contour_highlight(...)
#         Назначение: устанавливает zone contour highlight в рамках текущего сценария модуля.
#
#    C. ZoneHighlightMixin: основной сценарий:
#       ZoneHighlightMixin._find_zone_by_actor(...)
#         Назначение: Найти зону по VTK-актору.
#       ZoneHighlightMixin._find_zone_by_screen_ray(...)
#         Назначение: Найти ближайшую зону, пересечённую лучом камеры через точку экрана.
#       ZoneHighlightMixin._handle_zone_hover_event(...)
#         Назначение: Определить зону под курсором и подсветить/снять подсветку.
#         Последовательность внутренних вызовов:
#         -> ZoneHighlightMixin._unhighlight_hover_zone(...)
#         -> ZoneHighlightMixin._find_zone_by_screen_ray(...)
#         -> ZoneHighlightMixin._highlight_hover_zone(...)
#         -> ZoneHighlightMixin._find_zone_by_actor(...)
#       ZoneHighlightMixin._is_zone_in_contour_mode(...)
#         Назначение: Проверить, отображена ли зона в контурном режиме (solid скрыт).
#
#    D. ZoneHighlightMixin: завершение и очистка:
#       ZoneHighlightMixin.clear_selected_zone_highlight(...)
#         Назначение: Снять закреплённую подсветку выбранной зоны.
#         Последовательность внутренних вызовов:
#         -> ZoneHighlightMixin._teardown_zone_contour_if_temporary(...)
#       ZoneHighlightMixin._teardown_zone_contour_if_temporary(...)
#         Назначение: выполняет демонтаж zone contour if temporary в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> ZoneHighlightMixin._zone_has_rack_payload(...)
#         -> ZoneHighlightMixin._set_zone_contour_highlight(...)
#
#    E. ZoneHighlightMixin: вспомогательные расчёты:
#       ZoneHighlightMixin._zone_has_rack_payload(...)
#         Назначение: выполняет шаг "zone has rack payload" в рамках текущего сценария модуля.
#       ZoneHighlightMixin._highlight_hover_zone(...)
#         Назначение: Подсветить зону при hover только по контурным линиям.
#         Последовательность внутренних вызовов:
#         -> ZoneHighlightMixin._set_zone_contour_highlight(...)
#       ZoneHighlightMixin._unhighlight_hover_zone(...)
#         Назначение: Снять подсветку с ранее подсвеченной зоны.
#         Последовательность внутренних вызовов:
#         -> ZoneHighlightMixin._teardown_zone_contour_if_temporary(...)
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Все операции с актёрами и камерой выполняются только при валидном self._plotter с безопасной обработкой исключений.
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
