# -*- coding: utf-8 -*-
"""Миксин размещения полок стеллажа для ModelViewWidget."""

from __future__ import annotations

from typing import Any, TYPE_CHECKING
from error_logger import log_exception

if TYPE_CHECKING:
    from gui.components.model_view_widget import ModelViewWidget


class RackShelfMixin:
    """Вспомогательные методы размещения полок и секций, извлечённые из модуля стеллажей."""

    def start_select_rack_mode(self: "ModelViewWidget", zone_id: str | None) -> None:
        zid = str(zone_id or "")
        self._rack_select_mode = bool(zid)
        self._rack_preview_zone_id = zid if zid else self._rack_preview_zone_id
        self._clear_hover_rack_highlight()
        # Сценарий взаимодействия
        mgr = getattr(self, "_interaction_manager", None)
        if mgr is not None:
            mgr.pop_by_name("rack_select")
            if self._rack_select_mode:
                from gui.components.model_view._scenario_custom_handler import CustomHandlerScenario
                mgr.push(CustomHandlerScenario(
                    name="rack_select",
                    click_handler=self._on_select_rack_click,
                    hover_screen_handler=self._on_select_rack_hover_screen,
                ))

    def stop_select_rack_mode(self: "ModelViewWidget") -> None:
        # Убрать сценарий взаимодействия
        mgr = getattr(self, "_interaction_manager", None)
        if mgr is not None:
            mgr.pop_by_name("rack_select")
        self._rack_select_mode = False
        self._clear_hover_rack_highlight()

    def start_shelf_placement(self: "ModelViewWidget", rack_id: str, zone_id: str) -> bool:
        rid = str(rack_id or "")
        zid = str(zone_id or "")
        if not rid or not zid:
            return False
        if hasattr(self, "_clear_selected_rendered_shelf"):
            self._clear_selected_rendered_shelf(render=False)
        entry = self._get_rack_entry(rid, zid)
        if entry is None:
            return False
        self.stop_rack_placement(clear_preview=True)
        self.stop_select_rack_mode()
        self._clear_selected_rack_contour(render=False)
        self._clear_hover_rack_highlight(render=False)
        self._clear_shelf_slot_hover()
        self._clear_shelf_slot_active()
        self._shelf_target_zone_id = zid
        self._shelf_target_rack_id = rid
        # Сценарий взаимодействия
        mgr = getattr(self, "_interaction_manager", None)
        if mgr is not None:
            from gui.components.model_view._scenario_custom_handler import CustomHandlerScenario
            mgr.push(CustomHandlerScenario(
                name="shelf_placement",
                click_handler=self._on_select_shelf_slot_click,
                hover_screen_handler=self._on_select_shelf_slot_hover_screen,
            ))

        # Сделать объёмы секций выбираемыми, но почти прозрачными.
        for slot_actor in (entry.get("slot_actors") or {}).values():
            if slot_actor is None:
                continue
            try:
                slot_actor.SetVisibility(1)
                prop = slot_actor.GetProperty()
                if prop is not None:
                    # Прокси секций должны оставаться невидимыми; видны только контуры.
                    prop.SetOpacity(0.0)
            except Exception as _exc:
                log_exception(__name__, "start_shelf_placement", _exc)
        self._preload_shelf_model_for_entry(entry)
        if self._plotter:
            self._plotter.update()
        slot_ids = [str(sid) for sid in (entry.get("slot_actors") or {}).keys()]
        if len(slot_ids) == 1:
            self._set_active_shelf_slot(slot_ids[0])
            try:
                self.shelf_slot_selected.emit(rid, slot_ids[0])  # type: ignore[attr-defined]
            except Exception as _exc:
                log_exception(__name__, "start_shelf_placement", _exc)
        return True

    def stop_shelf_placement(self: "ModelViewWidget", clear_selection: bool = True) -> None:
        self.stop_shelf_placement_ext(clear_selection=clear_selection, clear_rendered_shelves=True)

    def stop_shelf_placement_ext(
        self: "ModelViewWidget",
        clear_selection: bool = True,
        clear_rendered_shelves: bool = True,
    ) -> None:
        # Убрать сценарий взаимодействия
        mgr = getattr(self, "_interaction_manager", None)
        if mgr is not None:
            mgr.pop_by_name("shelf_placement")
        self._clear_shelf_slot_hover()
        if clear_selection:
            self._clear_shelf_slot_active()
        if hasattr(self, "_clear_selected_rendered_shelf"):
            self._clear_selected_rendered_shelf(render=False)
        rid = str(self._shelf_target_rack_id or "")
        zid = str(self._shelf_target_zone_id or "")
        entry = self._get_rack_entry(rid, zid) if rid and zid else None
        if entry is not None:
            if clear_rendered_shelves:
                self._clear_slot_shelf_actors(rid, clear_slot_id=None)
            for slot_id, slot_actor in (entry.get("slot_actors") or {}).items():
                if slot_actor is None:
                    continue
                try:
                    # Вне режима размещения полок прокси-объёмы секций должны быть скрыты.
                    slot_actor.SetVisibility(0)
                    prop = slot_actor.GetProperty()
                    if prop is not None:
                        prop.SetOpacity(0.35)
                        prop.SetEdgeVisibility(False)
                except Exception as _exc:
                    log_exception(__name__, "stop_shelf_placement_ext", _exc)
        self._shelf_target_zone_id = None
        self._shelf_target_rack_id = None
        if self._plotter:
            self._plotter.update()

    def finalize_shelf_layout_for_rack(self: "ModelViewWidget", rack_id: str) -> bool:
        rid = str(rack_id or "")
        if not rid:
            return False
        entry = self._get_rack_entry(rid)
        if entry is None:
            return False
        for slot_id in list((entry.get("slot_actors") or {}).keys()):
            self.set_rack_slot_occupied(rid, str(slot_id), True)
        entry["shelf_helpers_hidden"] = True
        self._apply_rack_helper_visibility_policy(entry, True)
        self._set_rack_shelf_actors_visibility(rid, True)
        self._apply_rack_shelf_opacity(rid)
        if self._plotter:
            self._plotter.update()
        return True

    def get_active_shelf_slot(self: "ModelViewWidget") -> tuple[str | None, str | None]:
        return (self._shelf_target_rack_id, self._active_shelf_slot_id)

    def get_selected_shelf_visual(self: "ModelViewWidget") -> tuple[str | None, str | None, int | None]:
        return (
            self._selected_shelf_visual_rack_id,
            self._selected_shelf_visual_slot_id,
            self._selected_shelf_visual_index,
        )

    def get_rack_shelf_layout(self: "ModelViewWidget", rack_id: str) -> list[dict[str, Any]]:
        rid = str(rack_id or "")
        if not rid:
            return []
        layout: list[dict[str, Any]] = []
        for (entry_rid, slot_id), payload in self._rack_shelf_params.items():
            if str(entry_rid) != rid:
                continue
            layout.append({"slot_id": str(slot_id), "params": dict(payload or {})})
        layout.sort(key=lambda item: self._slot_index_from_id(str(item.get("slot_id") or "")))
        return layout

    def get_shelf_slot_params(self: "ModelViewWidget", rack_id: str, slot_id: str) -> dict[str, Any]:
        rid = str(rack_id or "")
        sid = str(slot_id or "")
        if not rid or not sid:
            return {}
        return dict(self._rack_shelf_params.get((rid, sid)) or {})

    def activate_shelf_slot(
        self: "ModelViewWidget",
        rack_id: str,
        slot_id: str,
        notify: bool = True,
    ) -> bool:
        rid = str(rack_id or "")
        sid = str(slot_id or "")
        if not rid or not sid:
            return False
        if str(self._shelf_target_rack_id or "") != rid:
            return False
        entry = self._get_rack_entry(rid, self._shelf_target_zone_id)
        if entry is None:
            return False
        if sid not in (entry.get("slot_actors") or {}):
            return False
        self._set_active_shelf_slot(sid)
        if notify:
            try:
                self.shelf_slot_selected.emit(rid, sid)  # type: ignore[attr-defined]
            except Exception as _exc:
                log_exception(__name__, "activate_shelf_slot", _exc)
        return True

    def restore_rack_shelves(self: "ModelViewWidget", rack_id: str, shelf_rows: list[dict[str, Any]]) -> bool:
        rid = str(rack_id or "")
        if not rid:
            return False
        entry = self._get_rack_entry(rid)
        if entry is None:
            return False
        restored = False
        for row in list(shelf_rows or []):
            slot_id = str(row.get("slot_id") or "S1")
            payload = dict(row.get("params") or {})
            if not payload:
                payload = {
                    "shelf_code_start": str(row.get("code") or "P01"),
                    "shelf_depth_mm": int(row.get("d") or 500),
                    "shelves_count": int(row.get("grid_r") or 1),
                    "bottom_shelf_height_mm": float(row.get("h") or 300.0),
                    "height_mode": "uniform",
                    "useful_height_1_mm": 400.0,
                    "useful_heights_mm": [400.0],
                }
            normalized = self._render_shelves_for_slot(rid, slot_id, payload)
            if not normalized:
                continue
            self._rack_shelf_params[(rid, slot_id)] = dict(normalized)
            self.set_rack_slot_occupied(rid, slot_id, True)
            restored = True
        if restored:
            entry["shelf_helpers_hidden"] = True
            self._apply_rack_helper_visibility_policy(entry, True)
            self._set_rack_shelf_actors_visibility(rid, True)
            self._apply_rack_shelf_opacity(rid)
            if self._plotter:
                self._plotter.update()
        return restored

    def set_active_shelf_slot_params(self: "ModelViewWidget", payload: dict[str, Any]) -> dict[str, Any] | None:
        rack_id = str(self._shelf_target_rack_id or "")
        slot_id = str(self._active_shelf_slot_id or "")
        if not rack_id or not slot_id:
            return None
        normalized = self._render_shelves_for_slot(rack_id, slot_id, dict(payload or {}))
        if not normalized:
            return None
        self._rack_shelf_params[(rack_id, slot_id)] = dict(normalized)
        # Занятая секция скрывается от дальнейшего размещения.
        self.set_rack_slot_occupied(rack_id, slot_id, True)
        self._apply_rack_shelf_opacity(rack_id)
        return dict(normalized)


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Сценарии работы с полками стеллажей.
#
# 2) Последовательность действий и вызовов:
#    A. Класс RackShelfMixin: точки входа
#       Публичные методы сценария:
#       - RackShelfMixin.start_select_rack_mode(...)
#       - RackShelfMixin.stop_select_rack_mode(...)
#       - RackShelfMixin.start_shelf_placement(...)
#       - RackShelfMixin.stop_shelf_placement(...)
#       - RackShelfMixin.stop_shelf_placement_ext(...)
#       - RackShelfMixin.finalize_shelf_layout_for_rack(...)
#       - RackShelfMixin.get_active_shelf_slot(...)
#       - RackShelfMixin.get_rack_shelf_layout(...)
#       - RackShelfMixin.get_shelf_slot_params(...)
#       - RackShelfMixin.activate_shelf_slot(...)
#       - RackShelfMixin.restore_rack_shelves(...)
#       - RackShelfMixin.set_active_shelf_slot_params(...)
#
#    B. RackShelfMixin: запуск и настройка:
#       RackShelfMixin.start_select_rack_mode(...)
#         Назначение: запускает select rack mode в рамках текущего сценария модуля.
#       RackShelfMixin.start_shelf_placement(...)
#         Назначение: запускает shelf placement в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackShelfMixin.stop_select_rack_mode(...)
#       RackShelfMixin.set_active_shelf_slot_params(...)
#         Назначение: устанавливает active shelf slot params в рамках текущего сценария модуля.
#
#    C. RackShelfMixin: завершение и очистка:
#       RackShelfMixin.stop_select_rack_mode(...)
#         Назначение: останавливает select rack mode в рамках текущего сценария модуля.
#       RackShelfMixin.stop_shelf_placement(...)
#         Назначение: останавливает shelf placement в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackShelfMixin.stop_shelf_placement_ext(...)
#       RackShelfMixin.stop_shelf_placement_ext(...)
#         Назначение: останавливает shelf placement ext в рамках текущего сценария модуля.
#       RackShelfMixin.finalize_shelf_layout_for_rack(...)
#         Назначение: финализирует shelf layout for rack в рамках текущего сценария модуля.
#       RackShelfMixin.restore_rack_shelves(...)
#         Назначение: восстанавливает rack shelves в рамках текущего сценария модуля.
#
#    D. RackShelfMixin: вспомогательные расчёты:
#       RackShelfMixin.get_active_shelf_slot(...)
#         Назначение: возвращает active shelf slot в рамках текущего сценария модуля.
#       RackShelfMixin.get_rack_shelf_layout(...)
#         Назначение: возвращает rack shelf layout в рамках текущего сценария модуля.
#       RackShelfMixin.get_shelf_slot_params(...)
#         Назначение: возвращает shelf slot params в рамках текущего сценария модуля.
#       RackShelfMixin.activate_shelf_slot(...)
#         Назначение: выполняет шаг "activate shelf slot" в рамках текущего сценария модуля.
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Все операции с актёрами и камерой выполняются только при валидном self._plotter с безопасной обработкой исключений.
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
