# -*- coding: utf-8 -*-
"""Миксин вспомогательных функций стоек для мезонинов.

Обеспечивает обнаружение опор колонн, проверку зазоров и логику
совместного использования XY для мезонинных конструкций в *ModelViewWidget*.
"""
from __future__ import annotations

from pathlib import Path
from typing import Any, TYPE_CHECKING

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

if TYPE_CHECKING:
    from gui.components.model_view_widget import ModelViewWidget


class RackMezzanineMixin:
    """Вспомогательные функции мезонинных стоек, подмешиваемые в *ModelViewWidget*."""

    # ------------------------------------------------------------------
    # Вспомогательные функции мезонинов
    # ------------------------------------------------------------------

    @staticmethod
    def _is_mezzanine_params(params: dict[str, Any]) -> bool:
        additional_model = dict(params.get("additional_model") or {})
        model_path = additional_model.get("model_path")
        if not model_path:
            return False
        explicit = additional_model.get("is_mezzanine")
        if isinstance(explicit, bool):
            return explicit
        model_kind = str(additional_model.get("model_kind") or "").strip().lower()
        if model_kind in {"mezo", "mezz", "mezzanine"}:
            return True
        haystack = " ".join(
            str(v)
            for v in (
                additional_model.get("model_name"),
                additional_model.get("model_path"),
                params.get("rack_type"),
            )
            if v
        ).lower()
        return any(tag in haystack for tag in ("mezo", "mezz", "Р СР ВµР В·", "mezan", "mezon"))

    def _mezzanine_clearance_mm(self: "ModelViewWidget", params: dict[str, Any]) -> float:
        additional_model = dict(params.get("additional_model") or {})
        value = additional_model.get("clearance_height_mm", params.get("footprint_height_mm", 0.0))
        try:
            return max(0.0, float(value))
        except Exception as exc:
            log_exception(__name__, "_mezzanine_clearance_mm", exc)
            return 0.0

    def _ensure_model_footings(self: "ModelViewWidget", params: dict[str, Any]) -> None:
        additional_model = params.setdefault("additional_model", {})
        model_path = additional_model.get("model_path")
        if not model_path or additional_model.get("column_footings"):
            return
        base_mesh, _w, _d, _h = self._get_additional_model_base_mesh(Path(model_path))
        if base_mesh is None:
            return
        footings, clearance_h = self._detect_column_footings(base_mesh)
        if footings:
            additional_model["column_footings"] = footings
        if clearance_h > 0 and not additional_model.get("clearance_height_mm"):
            additional_model["clearance_height_mm"] = float(clearance_h)

    def _mezzanine_column_bboxes(
        self: "ModelViewWidget",
        cx: float,
        cy: float,
        params: dict[str, Any],
        rotation: int,
    ) -> list[tuple[float, float, float, float]]:
        if not self._is_mezzanine_params(params):
            return []
        self._ensure_model_footings(params)
        additional_model = dict(params.get("additional_model") or {})
        footings = list(additional_model.get("column_footings") or [])
        result: list[tuple[float, float, float, float]] = []
        for ft in footings:
            fx_min = float(ft.get("x_min", 0.0))
            fy_min = float(ft.get("y_min", 0.0))
            fx_max = float(ft.get("x_max", 0.0))
            fy_max = float(ft.get("y_max", 0.0))
            width = max(0.0, fx_max - fx_min)
            depth = max(0.0, fy_max - fy_min)
            if width <= 0.0 or depth <= 0.0:
                continue
            local_cx = (fx_min + fx_max) / 2.0
            local_cy = (fy_min + fy_max) / 2.0
            world_dx, world_dy = rotate_xy(local_cx, local_cy, rotation)
            if int(rotation) % 180 == 90:
                width, depth = depth, width
            result.append(
                (
                    cx + world_dx - width / 2.0,
                    cx + world_dx + width / 2.0,
                    cy + world_dy - depth / 2.0,
                    cy + world_dy + depth / 2.0,
                )
            )
        return result

    def _can_share_xy_with_mezzanine(
        self: "ModelViewWidget",
        candidate_params: dict[str, Any],
        existing_params: dict[str, Any],
    ) -> bool:
        cand_is_mezzanine = self._is_mezzanine_params(candidate_params)
        existing_is_mezzanine = self._is_mezzanine_params(existing_params)
        if cand_is_mezzanine and existing_is_mezzanine:
            return False
        if existing_is_mezzanine:
            clearance = self._mezzanine_clearance_mm(existing_params)
            return clearance > 0.0 and self._rack_height_mm(candidate_params) <= clearance
        if cand_is_mezzanine:
            clearance = self._mezzanine_clearance_mm(candidate_params)
            return clearance > 0.0 and self._rack_height_mm(existing_params) <= clearance
        return False


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Правила размещения мезонинов и проверки совместимости по геометрии.
#
# 2) Последовательность действий и вызовов:
#    A. Класс RackMezzanineMixin: точки входа
#       Публичные методы отсутствуют; сценарий запускается через методы родительских модулей и внутренние обработчики.
#
#    B. RackMezzanineMixin: основной сценарий:
#       RackMezzanineMixin._is_mezzanine_params(...)
#         Назначение: проверяет, что mezzanine params в рамках текущего сценария модуля.
#       RackMezzanineMixin._can_share_xy_with_mezzanine(...)
#         Назначение: проверяет возможность share xy with mezzanine в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackMezzanineMixin._is_mezzanine_params(...)
#         -> RackMezzanineMixin._mezzanine_clearance_mm(...)
#
#    C. RackMezzanineMixin: вспомогательные расчёты:
#       RackMezzanineMixin._mezzanine_clearance_mm(...)
#         Назначение: выполняет шаг "mezzanine clearance mm" в рамках текущего сценария модуля.
#       RackMezzanineMixin._ensure_model_footings(...)
#         Назначение: выполняет шаг "ensure model footings" в рамках текущего сценария модуля.
#       RackMezzanineMixin._mezzanine_column_bboxes(...)
#         Назначение: выполняет шаг "mezzanine column bboxes" в рамках текущего сценария модуля.
#         Последовательность внутренних вызовов:
#         -> RackMezzanineMixin._ensure_model_footings(...)
#         -> RackMezzanineMixin._is_mezzanine_params(...)
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).
