# -*- coding: utf-8 -*-
"""Миксин жизненного цикла стоек – инициализация, начало/остановка размещения,
параметры предпросмотра, генерация кодов и вспомогательные функции поворота,
извлечённые из _mv_racks.py."""

from __future__ import annotations

import uuid
import re
from pathlib import Path
from typing import Any, TYPE_CHECKING
from PySide6.QtCore import QTimer

try:
    import pyvista as pv

    _PV = True
except ImportError:
    _PV = False

from gui.components.model_view._mv_rack_geometry import rack_bbox, rotate_xy
from error_logger import log_exception

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


def _ray_aabb_intersect(
    origin: tuple[float, float, float],
    dx: float, dy: float, dz: float,
    min_x: float, max_x: float,
    min_y: float, max_y: float,
    min_z: float, max_z: float,
) -> float | None:
    """Пересечение луча с AABB методом слэбов. Возвращает параметрическое *t* или None."""
    t_min = 0.0
    t_max = 1e30
    for o, d, lo, hi in (
        (origin[0], dx, min_x, max_x),
        (origin[1], dy, min_y, max_y),
        (origin[2], dz, min_z, max_z),
    ):
        if abs(d) < 1e-12:
            if o < lo or o > hi:
                return None
        else:
            inv_d = 1.0 / d
            t1 = (lo - o) * inv_d
            t2 = (hi - o) * inv_d
            if t1 > t2:
                t1, t2 = t2, t1
            t_min = max(t_min, t1)
            t_max = min(t_max, t2)
            if t_min > t_max:
                return None
    return t_min


class RackLifecycleMixin:
    """Миксин с логикой жизненного цикла стоек – инициализация, начало/остановка
    размещения, обновление параметров предпросмотра, генерация кодов и поворот."""

    def init_rack_placement(self: "ModelViewWidget") -> None:
        self._rack_entries: list[dict[str, Any]] = []
        self._rack_preview_actors: list = []
        self._rack_move_mode = False
        self._rack_preview_valid = False
        self._rack_preview_center: tuple[float, float] | None = None
        self._rack_preview_shape_key: tuple[Any, ...] | None = None
        self._rack_preview_rotation = 0
        self._rack_preview_params: dict[str, Any] = {}
        self._rack_preview_zone_id: str | None = None
        self._rack_models_root: Path | None = None
        self._rack_select_mode = False
        self._selected_rack_id: str | None = None
        self._selected_rack_visual_id: str | None = None
        self._selected_rack_visual_props: list[tuple[object, float, bool, float, tuple[float, float, float]]] = []
        self._rack_isolation_active = False
        self._rack_isolation_rack_id: str | None = None
        self._rack_isolation_visual_props: list[tuple[object, int, float, bool, float, tuple[float, float, float]]] = []
        self._hover_rack_id: str | None = None
        self._hover_rack_contour_actor = None
        self._selected_rack_contour_actor = None
        self._moving_rack_entry: dict[str, Any] | None = None
        self._shelf_target_zone_id: str | None = None
        self._shelf_target_rack_id: str | None = None
        self._hover_shelf_slot_id: str | None = None
        self._active_shelf_slot_id: str | None = None
        self._hover_shelf_slot_actor = None
        self._active_shelf_slot_actor = None
        self._rack_shelf_params: dict[tuple[str, str], dict[str, Any]] = {}
        self._rack_shelf_actors: dict[tuple[str, str], list[Any]] = {}
        self._rack_shelf_cell_links: dict[tuple[str, str], bool] = {}
        self._rack_shelf_cell_links_indexed: dict[tuple[str, str, int], bool] = {}
        self._cell_preview_actors: dict[tuple[str, str], list[Any]] = {}
        self._cell_final_actors: dict[tuple[str, str], list[Any]] = {}
        self._cell_dim_actors: dict[tuple[str, str], list[Any]] = {}
        self._cell_ref_bounds: dict[tuple, tuple] = {}
        self._cell_grid_edit_isolation_active: bool = False
        self._cell_grid_edit_rack_id: str | None = None
        self._cell_grid_edit_slot_id: str | None = None
        self._cell_grid_edit_shelf_index: int | None = None
        self._selected_shelf_visual_rack_id: str | None = None
        self._selected_shelf_visual_slot_id: str | None = None
        self._selected_shelf_visual_index: int | None = None
        self._selected_shelf_bbox_actor = None
        self._selected_shelf_bbox_rack_id: str | None = None
        self._selected_shelf_bbox_slot_id: str | None = None
        self._selected_shelf_bbox_index: int | None = None
        self._selected_floor_shelf_actor = None
        self._selected_floor_shelf_rack_id: str | None = None
        self._selected_floor_shelf_slot_id: str | None = None
        self._shelf_mesh_cache: dict[tuple[str, int, int], Any] = {}
        self._pillar_mesh_cache: dict[tuple[str, int], tuple[Any, float]] = {}
        self._additional_model_mesh_cache: dict[str, tuple[Any, float, float, float]] = {}
        self._camera_anim_timer: QTimer | None = None
        self._camera_anim_seq = 0
        self._last_moved_rack_id: str | None = None
        self._imported_models_enabled = True
        try:
            self.rack_slot_visibility_changed.connect(self._on_rack_slot_visibility_changed)
        except Exception as _exc:
            log_exception(__name__, "init_rack_placement", _exc)
    def start_rack_placement(
        self: "ModelViewWidget",
        zone_id: str,
        params: dict[str, Any],
        models_root: Path | None = None,
        move_existing: bool = False,
    ) -> None:
        if not self._plotter or not self._models_loaded:
            return
        self._rack_move_mode = bool(move_existing)
        self._rack_preview_zone_id = zone_id
        self._rack_models_root = Path(models_root) if models_root else None
        if self._rack_move_mode and self._moving_rack_entry is not None:
            self._rack_preview_rotation = int(dict(self._moving_rack_entry).get("rotation", 0)) % 360
        else:
            self._rack_preview_rotation = 0
        fallback_code = None
        if self._moving_rack_entry is None:
            fallback_code = self._next_zone_rack_code(zone_id, dict(params or {}))
        else:
            fallback_code = str(dict(self._moving_rack_entry).get("code") or dict(params or {}).get("code") or "A")
        self.update_rack_preview_params(
            self._normalize_rack_params(dict(params or {}), fallback_code=fallback_code)
        )
        self._clear_hover_rack_highlight()
        self.show_only_zone_racks(zone_id)
        # Сценарий взаимодействия
        mgr = getattr(self, "_interaction_manager", None)
        if mgr is not None:
            mgr.pop_by_name("rack_select")
            from gui.components.model_view._scenario_custom_handler import CustomHandlerScenario
            from PySide6.QtCore import Qt as _Qt
            mgr.push(CustomHandlerScenario(
                name="rack_placement",
                click_handler=self._on_rack_click,
                hover_screen_handler=self._on_rack_hover_screen,
                hotkeys={
                    _Qt.Key.Key_Escape: lambda: self.stop_rack_placement(clear_preview=True),
                },
            ))
        # Гарантируем получение KeyPress в interactor (горячая клавиша R).
        try:
            if self._plotter is not None:
                self._plotter.setFocus()
                interactor = getattr(self._plotter, "interactor", None)
                if interactor is not None:
                    interactor.setFocus()
        except Exception as _exc:
            log_exception(__name__, "start_rack_placement", _exc)
    def stop_rack_placement(self: "ModelViewWidget", clear_preview: bool = True) -> None:
        # Убрать сценарий взаимодействия
        mgr = getattr(self, "_interaction_manager", None)
        if mgr is not None:
            mgr.pop_by_name("rack_placement")
        restore_moving = self._moving_rack_entry is not None
        self._rack_move_mode = False
        self._rack_preview_valid = False
        self._rack_preview_center = None
        if not self._interaction_manager.is_active("rack_select"):
            self._clear_hover_rack_highlight()
        if clear_preview:
            self._clear_rack_preview()
        if restore_moving:
            self._restore_moving_rack_entry()
        if self._plotter:
            self._plotter.update()

    def update_rack_preview_params(self: "ModelViewWidget", params: dict[str, Any]) -> None:
        fallback_code = str(dict(self._rack_preview_params or {}).get("code") or "A")
        if self._moving_rack_entry is not None:
            fallback_code = str(dict(self._moving_rack_entry).get("code") or fallback_code or "A")
        elif self._rack_preview_zone_id:
            fallback_code = str(
                self._next_zone_rack_code(
                    str(self._rack_preview_zone_id),
                    dict(params or {}),
                )
                or fallback_code
                or "A"
            )
        self._rack_preview_params = self._normalize_rack_params(
            dict(params or {}),
            fallback_code=fallback_code,
        )
        if self._rack_preview_center is not None:
            cx, cy = self._rack_preview_center
            self._rebuild_rack_preview(cx, cy)

    def get_next_rack_code(self: "ModelViewWidget", zone_id: str, params: dict[str, Any] | None = None) -> str:
        return self._next_zone_rack_code(zone_id, params)

    def rotate_rack_preview(self: "ModelViewWidget") -> None:
        if self._selected_rack_id and self._moving_rack_entry is None:
            if self.rotate_selected_rack(90):
                return
        self._rack_preview_rotation = (self._rack_preview_rotation + 90) % 360
        if self._rack_preview_center is not None:
            cx, cy = self._rack_preview_center
            self._rebuild_rack_preview(cx, cy)

    def rotate_selected_rack(self: "ModelViewWidget", delta_deg: int) -> bool:
        rid = str(self._selected_rack_id or "")
        if not rid:
            return False
        for idx, entry in enumerate(self._rack_entries):
            if str(entry.get("rack_id", "")) != rid:
                continue
            center = entry.get("center") or (0.0, 0.0)
            params = dict(entry.get("params") or {})
            zone_id = str(entry.get("zone_id") or "")
            new_rotation = (int(entry.get("rotation", 0)) + int(delta_deg)) % 360
            if not self._validate_rack_position(
                float(center[0]),
                float(center[1]),
                zone_id,
                params,
                new_rotation,
                ignore_rack_id=rid,
            ):
                return False
            for actor in entry.get("actors", []):
                try:
                    self._plotter.remove_actor(actor)
                except Exception as _exc:
                    log_exception(__name__, "rotate_selected_rack", _exc)
            color, opacity = self._resolve_rack_style(params)
            actors, slot_actors = self._spawn_rack_actors(
                float(center[0]), float(center[1]), zone_id, params, new_rotation, color, opacity,
            )
            if not actors:
                return False
            hidden_slot_ids = set(str(v) for v in (entry.get("hidden_slot_ids") or set()))
            for sid in hidden_slot_ids:
                actor = slot_actors.get(sid)
                if actor is not None:
                    try:
                        actor.SetVisibility(0)
                    except Exception as _exc:
                        log_exception(__name__, "rotate_selected_rack", _exc)
            entry["rotation"] = new_rotation
            entry["bbox"] = rack_bbox(float(center[0]), float(center[1]), params, new_rotation)
            entry["actors"] = actors
            entry["slot_actors"] = slot_actors
            entry["hidden_slot_ids"] = hidden_slot_ids
            self._apply_rack_helper_visibility_policy(entry, True)
            self._rack_entries[idx] = entry
            if self._plotter:
                self._plotter.update()
            self.rack_layout_changed.emit(zone_id)
            self._set_selected_rack(rid)
            return True
        return False


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Жизненный цикл размещения стеллажа: запуск, обновление, остановка, поворот.
#
# 2) Последовательность действий и вызовов:
#    A. Функции уровня модуля:
#       _ray_aabb_intersect(...)
#         Назначение: Пересечение луча с AABB методом слэбов. Возвращает параметрическое *t* или None.
#
#    B. Класс RackLifecycleMixin: точки входа
#       Публичные методы сценария:
#       - RackLifecycleMixin.init_rack_placement(...)
#       - RackLifecycleMixin.start_rack_placement(...)
#       - RackLifecycleMixin.stop_rack_placement(...)
#       - RackLifecycleMixin.update_rack_preview_params(...)
#       - RackLifecycleMixin.get_next_rack_code(...)
#       - RackLifecycleMixin.rotate_rack_preview(...)
#       - RackLifecycleMixin.rotate_selected_rack(...)
#
#    C. RackLifecycleMixin: запуск и настройка:
#       RackLifecycleMixin.init_rack_placement(...)
#         Назначение: инициализирует rack placement в рамках текущего сценария модуля.
#       RackLifecycleMixin.start_rack_placement(...)
#         Назначение: запускает rack placement в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackLifecycleMixin.update_rack_preview_params(...)
#
#    D. RackLifecycleMixin: основной сценарий:
#       RackLifecycleMixin.update_rack_preview_params(...)
#         Назначение: обновляет rack preview params в рамках текущего сценария модуля.
#
#    E. RackLifecycleMixin: завершение и очистка:
#       RackLifecycleMixin.stop_rack_placement(...)
#         Назначение: останавливает rack placement в рамках текущего сценария модуля.
#
#    F. RackLifecycleMixin: вспомогательные расчёты:
#       RackLifecycleMixin.get_next_rack_code(...)
#         Назначение: возвращает next rack code в рамках текущего сценария модуля.
#       RackLifecycleMixin.rotate_rack_preview(...)
#         Назначение: выполняет шаг "rotate rack preview" в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackLifecycleMixin.rotate_selected_rack(...)
#       RackLifecycleMixin.rotate_selected_rack(...)
#         Назначение: выполняет шаг "rotate selected rack" в рамках текущего сценария модуля.
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Все операции с актёрами и камерой выполняются только при валидном self._plotter с безопасной обработкой исключений.
#    - Геометрическая визуализация зависит от pyvista/vtk; при недоступности модуль обязан завершать шаг без падения сценария.
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
