# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_zones_visual.py
# Визуальное управление зонами (предпросмотр, изоляция, видимость, контуры)

from __future__ import annotations

from typing import TYPE_CHECKING

from PySide6.QtGui import QColor
from error_logger import log_exception

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

try:
    import pyvista as pv
    _PV = True
except ImportError:
    _PV = False


class ZoneVisualMixin:
    """Визуальное управление зонами: изоляция, видимость, контуры."""

    def isolate_zone(self: "ModelViewWidget", zone_id: str) -> None:
        """Скрыть все зоны кроме указанной (изоляция вида)."""
        self.clear_selected_zone_highlight()
        self._unhighlight_hover_zone()
        for zid, actor in self._zones.items():
            if actor is None:
                continue
            try:
                actor.SetVisibility(1 if zid == zone_id else 0)
            except Exception as _exc:
                log_exception(__name__, "isolate_zone", _exc)
        if self._plotter:
            self._plotter.update()

    def show_all_zones(self: "ModelViewWidget") -> None:
        """Показать все зоны (снять изоляцию вида) и убрать контур."""
        if getattr(self, "_selected_zone_highlight_id", None):
            self.clear_selected_zone_highlight()
        else:
            self._unhighlight_hover_zone()
        self._remove_zone_contour()
        self._set_facility_zone_contours_visible(True)
        for actor in self._zones.values():
            if actor is None:
                continue
            try:
                actor.SetVisibility(1)
            except Exception as _exc:
                log_exception(__name__, "show_all_zones", _exc)
        if self._plotter:
            self._plotter.update()

    def set_zone_visibility(self: "ModelViewWidget", zone_id: str, visible: bool) -> None:
        """Установить видимость конкретной зоны."""
        actor = self._zones.get(zone_id)
        if actor is None:
            return
        try:
            actor.SetVisibility(1 if visible else 0)
            if self._plotter:
                self._plotter.update()
        except Exception as _exc:
            log_exception(__name__, "set_zone_visibility", _exc)
    def show_zone_contour(self: "ModelViewWidget", zone_id: str) -> None:
        """Скрыть все зоны и показать контурную линию (wireframe) указанной зоны.

        Сам объём (solid mesh) скрывается. Вместо него отображается каркасная
        линия рёбер (wireframe), чтобы обозначить границы зоны.
        """
        self.clear_selected_zone_highlight()
        self._unhighlight_hover_zone()
        # Скрываем все зоны (solid)
        self._set_facility_zone_contours_visible(False)
        for zid, actor in self._zones.items():
            if actor is None:
                continue
            try:
                actor.SetVisibility(0)
            except Exception as _exc:
                log_exception(__name__, "show_zone_contour", _exc)
        # Удаляем предыдущий контур, если был
        self._remove_zone_contour()

        # Строим wireframe-контур выбранной зоны
        polygon = self._zone_polygons.get(zone_id)
        heights = self._zone_heights.get(zone_id)
        if not polygon or not heights or not self._plotter:
            if self._plotter:
                self._plotter.update()
            return

        start_h, height = heights
        try:
            edges = self._build_zone_outline_edges(polygon, float(start_h), float(height))
            if edges is None:
                return
            contour_actor = self._plotter.add_mesh(
                edges,
                color=(1.0, 1.0, 1.0),
                line_width=2,
                style="wireframe",
                name="_zone_contour_outline",
            )
            self._zone_contour_actor = contour_actor
        except Exception as e:
            log_exception(__name__, "show_zone_contour", e)
        if self._plotter:
            self._plotter.update()

    def _get_facility_zone_contour_actors(self: "ModelViewWidget") -> dict[str, object]:
        actors = getattr(self, "_zone_facility_contour_actors", None)
        if actors is None:
            actors = {}
            self._zone_facility_contour_actors = actors
        return actors

    def _set_facility_zone_contours_visible(self: "ModelViewWidget", visible: bool) -> None:
        actors = self._get_facility_zone_contour_actors()
        for actor in actors.values():
            if actor is None:
                continue
            try:
                actor.SetVisibility(1 if visible else 0)
            except Exception as _exc:
                log_exception(__name__, "_set_facility_zone_contours_visible", _exc)
    def _ensure_zone_facility_contour(self: "ModelViewWidget", zone_id: str) -> object | None:
        actors = self._get_facility_zone_contour_actors()
        existing = actors.get(zone_id)
        if existing is not None:
            return existing
        polygon = self._zone_polygons.get(zone_id)
        heights = self._zone_heights.get(zone_id)
        if not polygon or not heights or not self._plotter:
            return None
        start_h, height = heights
        try:
            edges = self._build_zone_outline_edges(polygon, float(start_h), float(height))
            if edges is None:
                return None
            actor = self._plotter.add_mesh(
                edges,
                color=(1.0, 1.0, 1.0),
                line_width=2,
                style="wireframe",
                name=f"_zone_facility_contour_{zone_id}",
            )
            actors[zone_id] = actor
            return actor
        except Exception as exc:
            log_exception(__name__, "_ensure_zone_facility_contour", exc)
            return None

    def _remove_zone_facility_contour(self: "ModelViewWidget", zone_id: str) -> None:
        actors = self._get_facility_zone_contour_actors()
        actor = actors.pop(zone_id, None)
        if actor is None or self._plotter is None:
            return
        try:
            self._plotter.remove_actor(actor)
        except Exception as _exc:
            log_exception(__name__, "_remove_zone_facility_contour", _exc)
    def set_zone_facility_contour_mode(self: "ModelViewWidget", zone_id: str, enabled: bool) -> None:
        """Установить представление зоны для уровня объекта.

        enabled=True  -> скрыть solid-меш и показать контурную линию.
        enabled=False -> удалить контурную линию и показать solid-меш.
        """
        actor = self._zones.get(zone_id)
        if actor is None:
            return
        if enabled:
            contour_actor = self._ensure_zone_facility_contour(zone_id)
            try:
                actor.SetVisibility(0)
            except Exception as _exc:
                log_exception(__name__, "set_zone_facility_contour_mode", _exc)
            if contour_actor is not None:
                try:
                    c_prop = contour_actor.GetProperty()
                    if c_prop is not None:
                        c_prop.SetColor(1.0, 1.0, 1.0)
                        c_prop.SetLineWidth(2.0)
                    contour_actor.SetVisibility(1)
                except Exception as _exc:
                    log_exception(__name__, "set_zone_facility_contour_mode", _exc)
        else:
            self._remove_zone_facility_contour(zone_id)
            try:
                actor.SetVisibility(1)
            except Exception as _exc:
                log_exception(__name__, "set_zone_facility_contour_mode", _exc)
    def _build_zone_outline_edges(
        self: "ModelViewWidget",
        polygon: list[tuple[float, float]],
        start_height: float,
        height: float,
    ):
        """Построить только рёбра периметра для контура зоны."""
        if not _PV or not polygon or len(polygon) < 3:
            return None

        n = len(polygon)
        z0 = float(start_height)
        z1 = float(start_height) + float(height)

        points: list[list[float]] = []
        for x, y in polygon:
            points.append([float(x), float(y), z0])
        for x, y in polygon:
            points.append([float(x), float(y), z1])

        line_cells: list[int] = []

        def _add_segment(i0: int, i1: int) -> None:
            line_cells.extend([2, i0, i1])

        for i in range(n):
            j = (i + 1) % n
            _add_segment(i, j)
            _add_segment(i + n, j + n)
            _add_segment(i, i + n)

        outline = pv.PolyData(points)
        outline.lines = line_cells
        return outline

    def _remove_zone_contour(self: "ModelViewWidget") -> None:
        """Удалить контурную линию зоны из 3D-сцены."""
        actor = getattr(self, "_zone_contour_actor", None)
        if actor is not None and self._plotter is not None:
            try:
                self._plotter.remove_actor(actor)
            except Exception as _exc:
                log_exception(__name__, "_remove_zone_contour", _exc)
        self._zone_contour_actor = None


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Визуализация зон: предпросмотр, контуры, изоляция и видимость.
#
# 2) Последовательность действий и вызовов:
#    A. Класс ZoneVisualMixin: точки входа
#       Публичные методы сценария:
#       - ZoneVisualMixin.isolate_zone(...)
#       - ZoneVisualMixin.show_all_zones(...)
#       - ZoneVisualMixin.set_zone_visibility(...)
#       - ZoneVisualMixin.show_zone_contour(...)
#       - ZoneVisualMixin.set_zone_facility_contour_mode(...)
#
#    B. ZoneVisualMixin: запуск и настройка:
#       ZoneVisualMixin.set_zone_visibility(...)
#         Назначение: Установить видимость конкретной зоны.
#       ZoneVisualMixin._set_facility_zone_contours_visible(...)
#         Назначение: устанавливает facility zone contours visible в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> ZoneVisualMixin._get_facility_zone_contour_actors(...)
#       ZoneVisualMixin.set_zone_facility_contour_mode(...)
#         Назначение: Установить представление зоны для уровня объекта.
#         Последовательность внутренних вызовов:
#         -> ZoneVisualMixin._ensure_zone_facility_contour(...)
#         -> ZoneVisualMixin._remove_zone_facility_contour(...)
#
#    C. ZoneVisualMixin: основной сценарий:
#       ZoneVisualMixin.show_preview_zone(...)
#         Назначение: Показать простой куб предпросмотра (устаревший).
#       ZoneVisualMixin.show_all_zones(...)
#         Назначение: Показать все зоны (снять изоляцию вида) и убрать контур.
#         Последовательность внутренних вызовов:
#         -> ZoneVisualMixin._remove_zone_contour(...)
#         -> ZoneVisualMixin._set_facility_zone_contours_visible(...)
#       ZoneVisualMixin.show_zone_contour(...)
#         Назначение: Скрыть все зоны и показать контурную линию (wireframe) указанной зоны.
#         Последовательность внутренних вызовов:
#         -> ZoneVisualMixin._set_facility_zone_contours_visible(...)
#         -> ZoneVisualMixin._remove_zone_contour(...)
#         -> ZoneVisualMixin._build_zone_outline_edges(...)
#
#    D. ZoneVisualMixin: завершение и очистка:
#       ZoneVisualMixin._remove_zone_facility_contour(...)
#         Назначение: удаляет zone facility contour в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> ZoneVisualMixin._get_facility_zone_contour_actors(...)
#       ZoneVisualMixin._remove_zone_contour(...)
#         Назначение: Удалить контурную линию зоны из 3D-сцены.
#
#    E. ZoneVisualMixin: вспомогательные расчёты:
#       ZoneVisualMixin.isolate_zone(...)
#         Назначение: Скрыть все зоны кроме указанной (изоляция вида).
#       ZoneVisualMixin._get_facility_zone_contour_actors(...)
#         Назначение: возвращает facility zone contour actors в рамках текущего сценария модуля.
#       ZoneVisualMixin._ensure_zone_facility_contour(...)
#         Назначение: выполняет шаг "ensure zone facility contour" в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> ZoneVisualMixin._get_facility_zone_contour_actors(...)
#         -> ZoneVisualMixin._build_zone_outline_edges(...)
#       ZoneVisualMixin._build_zone_outline_edges(...)
#         Назначение: Построить только рёбра периметра для контура зоны.
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Все операции с актёрами и камерой выполняются только при валидном self._plotter с безопасной обработкой исключений.
#    - Геометрическая визуализация зависит от pyvista/vtk; при недоступности модуль обязан завершать шаг без падения сценария.
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
