# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_racks_projection.py
"""Проекционная привязка курсора к плоскости пола зоны для rack-placement."""

from __future__ import annotations

from typing import TYPE_CHECKING

from gui.components.model_view._mv_rack_geometry import PLACEMENT_STEP_MM
from error_logger import log_exception

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


class RackPlacementProjectionMixin:
    """Проекция курсора на плоскость зоны."""

    def _on_rack_hover_screen(self: "ModelViewWidget", sx: float, sy: float) -> None:
        if not self._interaction_manager.is_active("rack_placement"):
            return
        world = self._project_screen_to_zone_plane(sx, sy)
        if world is None:
            return
        x, y, _ = world
        gx = round(float(x) / PLACEMENT_STEP_MM) * PLACEMENT_STEP_MM
        gy = round(float(y) / PLACEMENT_STEP_MM) * PLACEMENT_STEP_MM
        if self._rack_preview_center == (gx, gy):
            return
        self._rebuild_rack_preview(gx, gy)

    def _on_rack_click(self: "ModelViewWidget", x: float, y: float, z: float) -> bool:
        if not self._interaction_manager.is_active("rack_placement"):
            return False
        world = self._project_cursor_to_zone_plane()
        if world is None:
            world = (x, y, z if z is not None else 0.0)
        wx, wy, _wz = world
        gx = round(float(wx) / PLACEMENT_STEP_MM) * PLACEMENT_STEP_MM
        gy = round(float(wy) / PLACEMENT_STEP_MM) * PLACEMENT_STEP_MM
        self._rebuild_rack_preview(gx, gy)
        if not self._rack_preview_valid:
            return True
        self._commit_rack_placement(gx, gy)
        return True

    def _project_cursor_to_zone_plane(self: "ModelViewWidget") -> tuple[float, float, float] | None:
        if not self._plotter or not self._plotter.interactor:
            return None
        try:
            sx, sy = self._plotter.interactor.GetEventPosition()
        except Exception as exc:
            log_exception(__name__, "_project_cursor_to_zone_plane", exc)
            return None
        return self._project_screen_to_zone_plane(float(sx), float(sy), screen_is_vtk=True)

    def _project_screen_to_zone_plane(
        self: "ModelViewWidget",
        sx: float,
        sy: float,
        screen_is_vtk: bool = False,
    ) -> tuple[float, float, float] | None:
        zone_id = self._rack_preview_zone_id
        if not zone_id:
            return None
        z_ref = float(self._zone_heights.get(zone_id, (0.0, 0.0))[0])
        px = float(sx)
        py = float(sy)
        if not screen_is_vtk and self._plotter and getattr(self._plotter, "ren_win", None):
            try:
                dpr = float(self._plotter.devicePixelRatio())
                if dpr <= 0.0:
                    dpr = 1.0
                px *= dpr
                py *= dpr
                _vw, vh = self._plotter.ren_win.GetSize()
                py = float(vh) - py - 1.0
            except Exception as exc:
                log_exception(__name__, "_project_screen_to_zone_plane", exc)
                px = float(sx)
                py = float(sy)
        return self.screen_to_world_on_plane(px, py, z_ref)


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Проекция курсора на плоскость зоны для сценария размещения.
#
# 2) Последовательность действий и вызовов:
#    A. Класс RackPlacementProjectionMixin: точки входа
#       Публичные методы отсутствуют; сценарий запускается через методы родительских модулей и внутренние обработчики.
#
#    B. RackPlacementProjectionMixin: основной сценарий:
#       RackPlacementProjectionMixin._on_rack_hover_screen(...)
#         Назначение: выполняет шаг "on rack hover screen" в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackPlacementProjectionMixin._project_screen_to_zone_plane(...)
#       RackPlacementProjectionMixin._on_rack_click(...)
#         Назначение: выполняет шаг "on rack click" в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackPlacementProjectionMixin._project_cursor_to_zone_plane(...)
#       RackPlacementProjectionMixin._project_cursor_to_zone_plane(...)
#         Назначение: проецирует cursor to zone plane в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackPlacementProjectionMixin._project_screen_to_zone_plane(...)
#       RackPlacementProjectionMixin._project_screen_to_zone_plane(...)
#         Назначение: проецирует screen to zone plane в рамках текущего сценария модуля.
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Все операции с актёрами и камерой выполняются только при валидном self._plotter с безопасной обработкой исключений.
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
