# -*- coding: utf-8 -*-
"""CRUD-миксин стеллажей — вспомогательные методы добавления / удаления / видимости для 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 RackCrudMixin:
    """Операции создания-чтения-обновления-удаления стеллажей, выделенные из _mv_racks."""

    def _clear_removed_rack_visuals(self: "ModelViewWidget", rack_id: str) -> None:
        rid = str(rack_id or "")
        if not rid:
            return
        try:
            self._clear_slot_shelf_actors(rid, clear_slot_id=None)
        except Exception as _exc:
            log_exception(__name__, "_clear_removed_rack_visuals", _exc)
        try:
            if hasattr(self, "clear_cell_grid_visualization"):
                self.clear_cell_grid_visualization(rid)
        except Exception as _exc:
            log_exception(__name__, "_clear_removed_rack_visuals", _exc)
        try:
            params_storage = getattr(self, "_rack_shelf_params", {}) or {}
            keys = [k for k in list(params_storage.keys()) if isinstance(k, tuple) and str(k[0]) == rid]
            for key in keys:
                params_storage.pop(key, None)
        except Exception as _exc:
            log_exception(__name__, "_clear_removed_rack_visuals", _exc)
    def remove_last_rack(self: "ModelViewWidget", zone_id: str | None) -> bool:
        if not zone_id:
            return False
        for idx in range(len(self._rack_entries) - 1, -1, -1):
            entry = self._rack_entries[idx]
            if entry["zone_id"] != zone_id:
                continue
            rid = str(entry.get("rack_id") or "")
            for actor in entry.get("actors", []):
                try:
                    self._plotter.remove_actor(actor)
                except Exception as _exc:
                    log_exception(__name__, "remove_last_rack", _exc)
            if rid:
                self._clear_removed_rack_visuals(rid)
            self._rack_entries.pop(idx)
            if self._plotter:
                self._plotter.update()
            self.rack_layout_changed.emit(str(zone_id))
            if str(self._hover_rack_id or "") == rid:
                self._clear_hover_rack_highlight()
            if str(self._selected_rack_id or "") == rid:
                self._set_selected_rack(None)
            if str(getattr(self, "_rack_isolation_rack_id", "") or "") == rid:
                self._rack_isolation_active = False
                self._rack_isolation_rack_id = None
                self._clear_rack_isolation_visual()
            if str(getattr(self, "_shelf_target_rack_id", "") or "") == rid:
                self.stop_shelf_placement_ext(clear_selection=True, clear_rendered_shelves=True)
            if str(getattr(self, "_selected_shelf_bbox_rack_id", "") or "") == rid:
                self._clear_selected_rendered_shelf(render=False)
            return True
        return False

    def remove_selected_rack(self: "ModelViewWidget") -> bool:
        rid = str(self._selected_rack_id or "")
        if not rid:
            return False
        for idx in range(len(self._rack_entries) - 1, -1, -1):
            entry = self._rack_entries[idx]
            if str(entry.get("rack_id", "")) != rid:
                continue
            zone_id = str(entry.get("zone_id", ""))
            for actor in entry.get("actors", []):
                try:
                    self._plotter.remove_actor(actor)
                except Exception as _exc:
                    log_exception(__name__, "remove_selected_rack", _exc)
            self._clear_removed_rack_visuals(rid)
            self._rack_entries.pop(idx)
            if str(self._hover_rack_id or "") == rid:
                self._clear_hover_rack_highlight()
            self._set_selected_rack(None)
            if str(getattr(self, "_rack_isolation_rack_id", "") or "") == rid:
                self._rack_isolation_active = False
                self._rack_isolation_rack_id = None
                self._clear_rack_isolation_visual()
            if str(getattr(self, "_shelf_target_rack_id", "") or "") == rid:
                self.stop_shelf_placement_ext(clear_selection=True, clear_rendered_shelves=True)
            if str(getattr(self, "_selected_shelf_bbox_rack_id", "") or "") == rid:
                self._clear_selected_rendered_shelf(render=False)
            if self._plotter:
                self._plotter.update()
            self.rack_layout_changed.emit(zone_id)
            return True
        return False

    def clear_all_racks(self: "ModelViewWidget") -> None:
        self._rack_isolation_active = False
        self._rack_isolation_rack_id = None
        self._clear_rack_isolation_visual()
        self._clear_rack_preview()
        self._clear_hover_rack_highlight()
        if hasattr(self, "_clear_selected_rendered_shelf"):
            self._clear_selected_rendered_shelf(render=False)
        self._set_selected_rack(None)
        self._last_moved_rack_id = None
        for entry in self._rack_entries:
            for actor in entry.get("actors", []):
                try:
                    self._plotter.remove_actor(actor)
                except Exception as _exc:
                    log_exception(__name__, "clear_all_racks", _exc)
            rack_id = str(entry.get("rack_id") or "")
            if rack_id:
                self._clear_slot_shelf_actors(rack_id, clear_slot_id=None)
        self._rack_entries = []
        if self._plotter:
            self._plotter.update()

    def show_only_zone_racks(self: "ModelViewWidget", zone_id: str | None) -> None:
        self._rack_isolation_active = False
        self._rack_isolation_rack_id = None
        self._clear_rack_isolation_visual()
        if hasattr(self, "_clear_selected_rendered_shelf"):
            self._clear_selected_rendered_shelf(render=False)
        if str(zone_id or "") != str(self._rack_preview_zone_id or ""):
            self._clear_hover_rack_highlight()
        for entry in self._rack_entries:
            is_imported = self._is_imported_model_entry(entry)
            if is_imported:
                visible = bool(self._imported_models_enabled)
            else:
                visible = (zone_id is not None) and (entry["zone_id"] == zone_id)
            for actor in entry.get("actors", []):
                try:
                    actor.SetVisibility(1 if visible else 0)
                except Exception as _exc:
                    log_exception(__name__, "show_only_zone_racks", _exc)
            self._set_rack_shelf_actors_visibility(str(entry.get("rack_id") or ""), visible)
            self._apply_rack_helper_visibility_policy(entry, visible)
        if self._selected_rack_id:
            selected = self.get_selected_rack_entry()
            if selected is None or str(selected.get("zone_id", "")) != str(zone_id or ""):
                self._set_selected_rack(None)
            else:
                self._apply_selected_rack_visual(str(self._selected_rack_id))
        if self._plotter:
            self._plotter.update()

    def show_only_rack(self: "ModelViewWidget", rack_id: str | None, zone_id: str | None = None) -> bool:
        rid = str(rack_id or "")
        zid = str(zone_id or "")
        if not rid:
            return False
        if str(getattr(self, "_selected_shelf_bbox_rack_id", "") or "") != rid:
            if hasattr(self, "_clear_selected_rendered_shelf"):
                self._clear_selected_rendered_shelf(render=False)
        prev_isolation_id = str(getattr(self, "_rack_isolation_rack_id", "") or "")
        if prev_isolation_id and prev_isolation_id != rid:
            # Переключение изолированного стеллажа: сначала восстанавливаем свойства предыдущего актёра.
            self._clear_rack_isolation_visual()
        if str(self._hover_rack_id or "") != rid:
            self._clear_hover_rack_highlight()

        found = False
        for entry in self._rack_entries:
            same_rack = str(entry.get("rack_id", "")) == rid
            same_zone = True if not zid else str(entry.get("zone_id", "")) == zid
            is_imported = self._is_imported_model_entry(entry)
            if is_imported:
                visible = bool(self._imported_models_enabled)
            else:
                visible = same_rack and same_zone
            if same_rack and same_zone:
                found = True
            for actor in entry.get("actors", []):
                try:
                    actor.SetVisibility(1 if visible else 0)
                except Exception as _exc:
                    log_exception(__name__, "show_only_rack", _exc)
            self._set_rack_shelf_actors_visibility(str(entry.get("rack_id") or ""), visible)
            self._apply_rack_helper_visibility_policy(entry, visible)

        if not found:
            self._rack_isolation_active = False
            self._rack_isolation_rack_id = None
            self._clear_rack_isolation_visual()
            if zid:
                self.show_only_zone_racks(zid)
            return False

        self._rack_isolation_active = True
        self._rack_isolation_rack_id = rid
        self._apply_rack_isolation_visual(rid)
        # Применяем стиль выделения только после активации изоляции, чтобы вспомогательные
        # объёмы сохраняли исходную непрозрачность при возврате на уровень зоны.
        self._set_selected_rack(rid)
        if self._plotter:
            self._plotter.update()
        return True

    def show_only_shelf_in_rack(
        self: "ModelViewWidget",
        rack_id: str | None,
        slot_id: str | None,
        shelf_index: int | None = None,
        zone_id: str | None = None,
    ) -> bool:
        """Показывает только одну полку (доску) внутри изолированного стеллажа; стойки стеллажа остаются видимыми."""
        rid = str(rack_id or "")
        sid = str(slot_id or "")
        if not rid or not sid:
            return False
        if not self.show_only_rack(rid, zone_id):
            return False

        requested_index = int(1 if shelf_index is None else shelf_index)
        rack_type = ""
        found_target_slot = False
        entry = self._get_rack_entry(rid, str(zone_id or "")) if zone_id else self._get_rack_entry(rid)
        if entry is not None:
            rack_type = str((entry.get("params") or {}).get("rack_type") or "").strip().upper()
        # Единый контракт индекса: внешне (tree/ui) используется tree-index.
        # Для PALLET tree-index=1 соответствует напольному уровню (под первой полкой),
        # а физические полки рендерятся актёрами с индексами 1..N для tree-index=2..N+1.
        if rack_type == "PALLET":
            is_floor_level = requested_index <= 1
            target_index = max(1, requested_index - 1)
        else:
            is_floor_level = requested_index <= 0
            target_index = max(1, requested_index)
        if entry is not None and sid in (entry.get("slot_actors") or {}):
            found_target_slot = True
        shown_any = False

        for (entry_rid, entry_sid), actors in (self._rack_shelf_actors or {}).items():
            same_rack = str(entry_rid) == rid
            same_slot = str(entry_sid) == sid
            if same_rack and same_slot:
                found_target_slot = True
            for actor_index, actor in enumerate(list(actors or []), start=1):
                if actor is None:
                    continue
                visible = bool(same_rack and same_slot and actor_index == target_index and not is_floor_level)
                if visible:
                    shown_any = True
                try:
                    actor.SetVisibility(1 if visible else 0)
                except Exception as _exc:
                    log_exception(__name__, "show_only_shelf_in_rack", _exc)
        # Запасной вариант: если запрошенный индекс не существует, показываем все полки целевого слота.
        if found_target_slot and not shown_any and not is_floor_level:
            for (entry_rid, entry_sid), actors in (self._rack_shelf_actors or {}).items():
                same_slot = str(entry_rid) == rid and str(entry_sid) == sid
                for actor in list(actors or []):
                    if actor is None:
                        continue
                    try:
                        actor.SetVisibility(1 if same_slot else 0)
                    except Exception as _exc:
                        log_exception(__name__, "show_only_shelf_in_rack", _exc)
            target_index = 1

        if found_target_slot:
            if is_floor_level and hasattr(self, "_highlight_floor_shelf_polygon"):
                self._highlight_floor_shelf_polygon(rid, sid)
            elif hasattr(self, "_highlight_selected_rendered_shelf"):
                self._highlight_selected_rendered_shelf(rid, sid, target_index)

        if self._plotter:
            self._plotter.update()
        return found_target_slot

    def show_all_racks(self: "ModelViewWidget") -> None:
        self._rack_isolation_active = False
        self._rack_isolation_rack_id = None
        self._clear_rack_isolation_visual()
        self._clear_hover_rack_highlight()
        if hasattr(self, "_clear_selected_rendered_shelf"):
            self._clear_selected_rendered_shelf(render=False)
        for entry in self._rack_entries:
            visible = not self._is_imported_model_entry(entry) or bool(self._imported_models_enabled)
            for actor in entry.get("actors", []):
                try:
                    actor.SetVisibility(1 if visible else 0)
                except Exception as _exc:
                    log_exception(__name__, "show_all_racks", _exc)
            self._set_rack_shelf_actors_visibility(str(entry.get("rack_id") or ""), visible)
            self._apply_rack_helper_visibility_policy(entry, visible)
        if self._selected_rack_id:
            self._apply_selected_rack_visual(str(self._selected_rack_id))
        if self._plotter:
            self._plotter.update()

    def _set_rack_shelf_actors_visibility(self: "ModelViewWidget", rack_id: str, visible: bool) -> None:
        rid = str(rack_id or "")
        if not rid:
            return
        shelf_actors = getattr(self, "_rack_shelf_actors", {}) or {}
        for (entry_rid, _slot_id), actors in shelf_actors.items():
            if str(entry_rid) != rid:
                continue
            for actor in actors or []:
                try:
                    actor.SetVisibility(1 if visible else 0)
                except Exception as _exc:
                    log_exception(__name__, "_set_rack_shelf_actors_visibility", _exc)
        # Сетка ячеек (preview/final/dim) должна следовать политике видимости стеллажа.
        for storage_name in ("_cell_preview_actors", "_cell_final_actors", "_cell_dim_actors"):
            storage = getattr(self, storage_name, {}) or {}
            for key, actors in storage.items():
                if not isinstance(key, tuple) or not key:
                    continue
                if str(key[0]) != rid:
                    continue
                for actor in actors or []:
                    try:
                        actor.SetVisibility(1 if visible else 0)
                    except Exception as _exc:
                        log_exception(__name__, "_set_rack_shelf_actors_visibility", _exc)
        if hasattr(self, "_apply_cell_grid_edit_isolation_visibility"):
            self._apply_cell_grid_edit_isolation_visibility()

    def _apply_rack_helper_visibility_policy(
        self: "ModelViewWidget",
        entry: dict[str, Any],
        base_visible: bool,
    ) -> None:
        if not bool(entry.get("shelf_helpers_hidden")):
            return
        slot_actor_ids = {
            id(actor)
            for actor in (entry.get("slot_actors") or {}).values()
            if actor is not None
        }
        for idx, actor in enumerate(entry.get("actors", [])):
            if actor is None:
                continue
            try:
                if idx == 0:
                    actor.SetVisibility(1 if base_visible else 0)
                else:
                    actor.SetVisibility(0)
                if id(actor) in slot_actor_ids:
                    actor.SetVisibility(0)
            except Exception as _exc:
                log_exception(__name__, "_apply_rack_helper_visibility_policy", _exc)
    def has_racks_in_zone(self: "ModelViewWidget", zone_id: str | None) -> bool:
        zid = str(zone_id or "")
        if not zid:
            return False
        return any(str(e.get("zone_id", "")) == zid for e in self._rack_entries)


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    CRUD-операции стеллажей и управление их видимостью.
#
# 2) Последовательность действий и вызовов:
#    A. Класс RackCrudMixin: точки входа
#       Публичные методы сценария:
#       - RackCrudMixin.remove_last_rack(...)
#       - RackCrudMixin.remove_selected_rack(...)
#       - RackCrudMixin.clear_all_racks(...)
#       - RackCrudMixin.show_only_zone_racks(...)
#       - RackCrudMixin.show_only_rack(...)
#       - RackCrudMixin.show_only_shelf_in_rack(...)
#       - RackCrudMixin.show_all_racks(...)
#       - RackCrudMixin.has_racks_in_zone(...)
#
#    B. RackCrudMixin: запуск и настройка:
#       RackCrudMixin._set_rack_shelf_actors_visibility(...)
#         Назначение: устанавливает rack shelf actors visibility в рамках текущего сценария модуля.
#
#    C. RackCrudMixin: основной сценарий:
#       RackCrudMixin.show_only_zone_racks(...)
#         Назначение: показывает only zone racks в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackCrudMixin._set_rack_shelf_actors_visibility(...)
#         -> RackCrudMixin._apply_rack_helper_visibility_policy(...)
#       RackCrudMixin.show_only_rack(...)
#         Назначение: показывает only rack в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackCrudMixin._set_rack_shelf_actors_visibility(...)
#         -> RackCrudMixin._apply_rack_helper_visibility_policy(...)
#         -> RackCrudMixin.show_only_zone_racks(...)
#       RackCrudMixin.show_only_shelf_in_rack(...)
#         Назначение: Показывает только одну полку (доску) внутри изолированного стеллажа; стойки стеллажа остаются видимыми.
#         Последовательность внутренних вызовов:
#         -> RackCrudMixin.show_only_rack(...)
#       RackCrudMixin.show_all_racks(...)
#         Назначение: показывает all racks в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackCrudMixin._set_rack_shelf_actors_visibility(...)
#         -> RackCrudMixin._apply_rack_helper_visibility_policy(...)
#       RackCrudMixin._apply_rack_helper_visibility_policy(...)
#         Назначение: применяет rack helper visibility policy в рамках текущего сценария модуля.
#       RackCrudMixin.has_racks_in_zone(...)
#         Назначение: проверяет наличие racks in zone в рамках текущего сценария модуля.
#
#    D. RackCrudMixin: завершение и очистка:
#       RackCrudMixin.remove_last_rack(...)
#         Назначение: удаляет last rack в рамках текущего сценария модуля.
#       RackCrudMixin.remove_selected_rack(...)
#         Назначение: удаляет selected rack в рамках текущего сценария модуля.
#       RackCrudMixin.clear_all_racks(...)
#         Назначение: очищает all racks в рамках текущего сценария модуля.
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Все операции с актёрами и камерой выполняются только при валидном self._plotter с безопасной обработкой исключений.
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
