# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_rack_transition.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 RackCameraTransitionMixin:
    """Анимация фокусировки камеры на выбранной стойке в изометрическом виде."""

    def focus_on_rack_isometric(
        self: "ModelViewWidget",
        rack_id: str | None,
        zone_id: str | None = None,
        *,
        duration_ms: int = 320,
    ) -> bool:
        if not self._plotter or not rack_id:
            return False
        if not hasattr(self, "_get_rack_entry"):
            return False
        if not hasattr(self, "_animate_camera_transition"):
            return False
        cam = getattr(self._plotter, "camera", None)
        if cam is None:
            return False
        rid = str(rack_id or "")
        zid = str(zone_id or "")
        entry = self._get_rack_entry(rid, zid) if zid else self._get_rack_entry(rid)
        if entry is None:
            return False

        bounds = entry.get("bbox")
        if not bounds and hasattr(self, "_rack_container_bbox"):
            try:
                bounds = self._rack_container_bbox(entry)
            except Exception as exc:
                log_exception(__name__, "focus_on_rack_isometric._rack_container_bbox", exc)
                bounds = None
        if not bounds:
            return False

        try:
            min_x, max_x, min_y, max_y = [float(v) for v in bounds[:4]]
            # Оценка диапазона Z по базовой линии стойки/зоны, когда полный bbox недоступен.
            min_z = 0.0
            max_z = 2000.0
            if len(bounds) >= 6:
                min_z = float(bounds[4])
                max_z = float(bounds[5])
            else:
                params = dict(entry.get("params") or {})
                rack_height = 2000.0
                if hasattr(self, "_rack_height_mm"):
                    rack_height = float(self._rack_height_mm(params))
                entry_zone_id = str(entry.get("zone_id") or "")
                zone_bounds = (self._zone_data or {}).get(entry_zone_id)
                if zone_bounds and len(zone_bounds) >= 6:
                    try:
                        min_z = float(zone_bounds[4])
                    except Exception as exc:
                        log_exception(__name__, "focus_on_rack_isometric.zone_bounds", exc)
                        min_z = 0.0
                max_z = min_z + max(1.0, rack_height)

            cx = (min_x + max_x) * 0.5
            cy = (min_y + max_y) * 0.5
            cz = min_z + (max(1.0, max_z - min_z) * 0.54)
            sx = max(1.0, max_x - min_x)
            sy = max(1.0, max_y - min_y)
            sz = max(1.0, max_z - min_z)
            span = max(sx, sy, sz)

            start_pos = tuple(float(v) for v in cam.GetPosition())
            start_focal = tuple(float(v) for v in cam.GetFocalPoint())
            start_up = tuple(float(v) for v in cam.GetViewUp())

            vx, vy, vz = 1.0, -1.0, 0.78
            norm = (vx * vx + vy * vy + vz * vz) ** 0.5
            vx, vy, vz = vx / norm, vy / norm, vz / norm
            ux, uy, uz = 0.0, 0.0, 1.0
            fx, fy, fz = -vx, -vy, -vz
            rx = fy * uz - fz * uy
            ry = fz * ux - fx * uz
            rz = fx * uy - fy * ux
            r_norm = (rx * rx + ry * ry + rz * rz) ** 0.5
            if r_norm <= 1e-6:
                rx, ry, rz = 1.0, 0.0, 0.0
            else:
                rx, ry, rz = rx / r_norm, ry / r_norm, rz / r_norm

            rack_margin = max(1.01, float(getattr(self, "_rack_iso_margin_factor", 1.10)))
            rack_min_distance = max(800.0, float(getattr(self, "_rack_iso_min_distance", 1700.0)))
            rack_side_shift = max(0.0, float(getattr(self, "_rack_iso_side_shift_factor", 0.10)))
            rack_z_lift = max(0.0, float(getattr(self, "_rack_iso_z_lift_factor", 0.08)))
            rack_distance_scale = max(1.0, float(getattr(self, "_rack_iso_distance_scale", 1.40)))
            if hasattr(self, "_compute_isometric_fit_distance"):
                distance = float(
                    self._compute_isometric_fit_distance(
                        (min_x, max_x, min_y, max_y, min_z, max_z),
                        view_dir=(vx, vy, vz),
                        up_dir=(ux, uy, uz),
                        margin_factor=rack_margin,
                        min_distance=rack_min_distance,
                    )
                )
            else:
                distance = max(rack_min_distance, span * 2.9)
            distance *= rack_distance_scale
            target_focal = (cx, cy, cz)
            target_pos = (
                cx + vx * distance + rx * (span * rack_side_shift),
                cy + vy * distance + ry * (span * rack_side_shift),
                cz + vz * distance + (sz * rack_z_lift),
            )
            self._animate_camera_transition(
                start_pos=start_pos,
                start_focal=start_focal,
                start_up=start_up,
                target_pos=target_pos,
                target_focal=target_focal,
                target_up=(ux, uy, uz),
                duration_ms=int(duration_ms),
                steps=16,
            )
            return True
        except Exception as exc:
            log_exception(__name__, "focus_on_rack_isometric", exc)
            return False


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