# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_dim_lines_grid.py
"""Основные методы размерных линий (инициализация, вкл/выкл, перерисовка, подсветка)."""

from __future__ import annotations

import time
from typing import List, Optional, Tuple, TYPE_CHECKING

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

try:
    import pyvista as pv
    import numpy as np
except ImportError:  # pragma: no cover
    pv = None
    np = None

from error_logger import log_exception
from gui.components.model_view._mv_dimension_lines import (    # noqa: E501
    _DIM_LINE_COLOR_ORIGIN, _DIM_LINE_COLOR_LAST,
    _DIM_TEXT_COLOR_ORIGIN, _DIM_TEXT_COLOR_LAST,
    _EXTENSION_LINE_COLOR, _LINE_WIDTH, _EXTENSION_LINE_WIDTH, _FONT_SIZE,
    _DIM_OFFSET_1, _DIM_OFFSET_2, _EXTENSION_OVERSHOOT,
    _LABEL_CLEARANCE, _LABEL_Z_LAYER_STEP, _SNAP_TRIGGER_FRACTION,
    _HIGHLIGHT_COLOR, _HIGHLIGHT_RADIUS, _HIGHLIGHT_OPACITY,
    _THROTTLE_S, _Pt3, _Seg,
)


class DimLineCoreMixin:
    """Миксин: основная отрисовка размерных линий для ModelViewWidget."""

    def init_dimension_lines(self: "ModelViewWidget") -> None:
        """Инициализировать внутреннее состояние (вызывать из __init__)."""
        self._dim_actors: list = []
        self._dim_highlight_actor = None
        self._dim_origin: Optional[Tuple[float, float, float]] = None
        self._dim_last_point: Optional[Tuple[float, float]] = None
        self._dim_current_node: Optional[Tuple[float, float]] = None
        self._dim_enabled = False
        self._dim_last_redraw_t: float = 0.0
        self._measure_nodes: list[tuple[float, float, float]] = []
        self._measure_hover_node: Optional[Tuple[float, float, float]] = None
        self._measure_point_a: Optional[Tuple[float, float, float]] = None
        self._measure_point_b: Optional[Tuple[float, float, float]] = None
        self._measure_actors: list = []
        self._measure_highlight_actor = None

    def enable_dimension_lines(self: "ModelViewWidget", origin: Tuple[float, float, float]) -> None:
        """Включить размерные линии. *origin* — базовая точка."""
        self.init_dimension_lines()
        self._dim_origin = origin
        self._dim_last_point = None
        self._dim_current_node = None
        self._dim_enabled = True

    def disable_dimension_lines(self: "ModelViewWidget") -> None:
        """Выключить и очистить размерные линии."""
        self._clear_dim_actors()
        self._clear_dim_highlight()
        self._dim_enabled = False
        self._dim_origin = None
        self._dim_last_point = None
        self._dim_current_node = None

    def set_dim_last_point(self: "ModelViewWidget", pt: Optional[Tuple[float, float]]) -> None:
        """Обновить «последнюю созданную точку» контура."""
        self._dim_last_point = pt
        if self._dim_current_node is not None:
            self._redraw_dim_lines()

    def handle_dim_hover(self: "ModelViewWidget", x: float, y: float) -> None:
        """Обработать перемещение курсора: триггерный snap к узлу сетки."""
        if bool(getattr(self, "_contour_aux_hidden", False)):
            return
        if not self._dim_enabled or not self._dim_origin:
            return

        now = time.monotonic()
        if now - self._dim_last_redraw_t < _THROTTLE_S:
            return

        step = max(1.0, float(getattr(self, "_current_zone_size", 500.0)))
        trigger_r2 = (step * _SNAP_TRIGGER_FRACTION) ** 2
        best_node: Optional[Tuple[float, float]] = None
        best_d2: Optional[float] = None
        for nx, ny in getattr(self, "_grid_nodes", []):
            d2 = (nx - x) ** 2 + (ny - y) ** 2
            if best_d2 is None or d2 < best_d2:
                best_d2 = d2
                best_node = (nx, ny)

        if best_node is None or best_d2 is None:
            return
        if best_d2 > trigger_r2:
            return
        if self._dim_current_node == best_node:
            return

        self._dim_current_node = best_node
        self._dim_last_redraw_t = now
        self._redraw_dim_lines()

    def _redraw_dim_lines(self: "ModelViewWidget") -> None:
        """Полная перерисовка всех размерных линий + подсветка узла."""
        if bool(getattr(self, "_contour_aux_hidden", False)):
            return
        if not self._dim_enabled or self._dim_origin is None:
            return
        if not self._plotter or pv is None or np is None:
            return
        node = self._dim_current_node
        if node is None:
            return

        self._clear_dim_actors()

        ox, oy, oz = self._dim_origin
        cx, cy = node
        z = oz + 1.0  # чуть выше пола

        # Контейнеры для сбора геометрии
        ext_segs: List[_Seg] = []           # намеренно не отрисовываются
        origin_segs: List[_Seg] = []        # жёлтые размерные
        origin_labels: List[Tuple[_Pt3, str]] = []

        # ── 1. От origin до узла ────────────────────────────────────
        self._collect_dimension_pair(
            ox, oy, cx, cy, z, _DIM_OFFSET_1, "",
            x_side="bottom",
            y_side="right",
            label_layer=0,
            ext_segs=ext_segs,
            dim_segs=origin_segs,
            labels=origin_labels,
        )

        # ── Создать минимум актеров ─────────────────────────────────
        if origin_segs:
            self._add_lines_actor(origin_segs, _DIM_LINE_COLOR_ORIGIN, _LINE_WIDTH)
        if origin_labels:
            self._add_labels_actor(origin_labels, _DIM_TEXT_COLOR_ORIGIN)

        # ── 3. Подсветка текущего узла ──────────────────────────────
        self._show_dim_highlight(cx, cy, z)

        if hasattr(self, "_safe_render"):
            self._safe_render(min_interval_s=0.05)
        else:
            self._plotter.render()

    @staticmethod
    def _collect_dimension_pair(
        base_x: float, base_y: float,
        target_x: float, target_y: float,
        z: float,
        offset: float,
        label_prefix: str,
        x_side: str,
        y_side: str,
        label_layer: int,
        ext_segs: List[_Seg],
        dim_segs: List[_Seg],
        labels: List[Tuple[_Pt3, str]],
    ) -> None:
        """Построить геометрию и подписи для одной пары размеров X/Y."""
        dx = target_x - base_x
        dy = target_y - base_y
        label_z = z + 10.0 + (label_layer * _LABEL_Z_LAYER_STEP)

        # Размерная линия X и сторона выноски/подписи.
        if abs(dx) > 0.5:
            if x_side == "top":
                y_dim = max(base_y, target_y) + offset
                ext_end_y = y_dim + _EXTENSION_OVERSHOOT
                label_y = y_dim + _LABEL_CLEARANCE
            else:
                y_dim = min(base_y, target_y) - offset
                ext_end_y = y_dim - _EXTENSION_OVERSHOOT
                label_y = y_dim - _LABEL_CLEARANCE
            p1 = (base_x, y_dim, z)
            p2 = (target_x, y_dim, z)
            ext_segs.append(((base_x, base_y, z), (base_x, ext_end_y, z)))
            ext_segs.append(((target_x, target_y, z), (target_x, ext_end_y, z)))
            dim_segs.append((p1, p2))
            mid = ((p1[0] + p2[0]) / 2, label_y, label_z)
            labels.append((mid, f"{label_prefix}{abs(dx):.0f} mm"))

        # Размерная линия Y и сторона выноски/подписи.
        if abs(dy) > 0.5:
            if y_side == "right":
                x_dim = max(base_x, target_x) + offset
                ext_end_x = x_dim + _EXTENSION_OVERSHOOT
                label_x = x_dim + _LABEL_CLEARANCE
            else:
                x_dim = min(base_x, target_x) - offset
                ext_end_x = x_dim - _EXTENSION_OVERSHOOT
                label_x = x_dim - _LABEL_CLEARANCE
            p1 = (x_dim, base_y, z)
            p2 = (x_dim, target_y, z)
            ext_segs.append(((base_x, base_y, z), (ext_end_x, base_y, z)))
            ext_segs.append(((target_x, target_y, z), (ext_end_x, target_y, z)))
            dim_segs.append((p1, p2))
            mid = (label_x, (p1[1] + p2[1]) / 2, label_z)
            labels.append((mid, f"{label_prefix}{abs(dy):.0f} mm"))

    def _add_lines_actor(
        self: "ModelViewWidget",
        segments: List[_Seg],
        color: str,
        width: float,
    ) -> None:
        """Объединить все отрезки в один PolyData и добавить 1 actor."""
        n = len(segments)
        pts = np.empty((n * 2, 3), dtype=np.float64)
        cells = np.empty(n * 3, dtype=np.int64)
        for i, (a, b) in enumerate(segments):
            j = i * 2
            pts[j] = a
            pts[j + 1] = b
            k = i * 3
            cells[k] = 2
            cells[k + 1] = j
            cells[k + 2] = j + 1
        poly = pv.PolyData()
        poly.points = pts
        poly.lines = cells
        actor = self._plotter.add_mesh(
            poly,
            color=color,
            line_width=width,
            pickable=False,
            reset_camera=False,
        )
        self._dim_actors.append(actor)

    def _add_labels_actor(
        self: "ModelViewWidget",
        labels: List[Tuple[_Pt3, str]],
        text_color: str,
    ) -> None:
        """Добавить все подписи одним вызовом add_point_labels."""
        positions = [lbl[0] for lbl in labels]
        texts = [lbl[1] for lbl in labels]
        actor = self._plotter.add_point_labels(
            positions,
            texts,
            font_size=_FONT_SIZE,
            text_color=text_color,
            shape=None,
            show_points=False,
            always_visible=True,
            pickable=False,
            reset_camera=False,
        )
        self._dim_actors.append(actor)

    def _show_dim_highlight(
        self: "ModelViewWidget", x: float, y: float, z: float,
    ) -> None:
        """Показать подсвечивающую сферу на узле сетки."""
        self._clear_dim_highlight()
        if not self._plotter or pv is None:
            return
        sphere = pv.Sphere(
            radius=_HIGHLIGHT_RADIUS,
            center=(x, y, z),
            theta_resolution=8,
            phi_resolution=8,
        )
        self._dim_highlight_actor = self._plotter.add_mesh(
            sphere,
            color=_HIGHLIGHT_COLOR,
            opacity=_HIGHLIGHT_OPACITY,
            pickable=False,
            reset_camera=False,
        )

    def _clear_dim_highlight(self: "ModelViewWidget") -> None:
        if self._dim_highlight_actor and self._plotter:
            try:
                self._plotter.remove_actor(self._dim_highlight_actor)
            except Exception as e:
                log_exception(__name__, "_clear_dim_highlight", e)
        self._dim_highlight_actor = None

    def _clear_dim_actors(self: "ModelViewWidget") -> None:
        """Удалить все actors размерных линий из сцены."""
        if not self._plotter:
            return
        for actor in self._dim_actors:
            try:
                self._plotter.remove_actor(actor)
            except Exception as e:
                log_exception(__name__, "_clear_dim_actors", e)
        self._dim_actors = []


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Базовая отрисовка размерных линий по сетке в режиме построения контура.
#    Модуль отслеживает ближайший узел сетки к курсору, строит размерные линии
#    от опорной точки (origin) до текущего узла и показывает подсветку узла.
#
# 2) Последовательность действий и вызовов:
#
#    A. Инициализация состояния:
#       init_dimension_lines()
#         Назначение: подготовить все поля состояния размерных линий.
#         Состояние: очищает списки актеров, сбрасывает origin/текущий узел,
#         выключает режим отображения и обнуляет параметры перерисовки.
#
#    B. Включение и выключение:
#       enable_dimension_lines(origin)
#         Назначение: включить показ линий и зафиксировать опорную точку.
#         -> init_dimension_lines()
#         -> _dim_origin = origin, _dim_enabled = True
#
#       disable_dimension_lines()
#         Назначение: полностью отключить модуль и очистить визуалы.
#         -> _clear_dim_actors()
#         -> _clear_dim_highlight()
#         -> сброс _dim_enabled/_dim_origin/_dim_last_point/_dim_current_node
#
#    C. Наведение курсора:
#       handle_dim_hover(x, y)
#         Назначение: найти ближайший узел сетки и обновить геометрию.
#         Шаги:
#         1) проверяет, что режим активен и задан origin;
#         2) применяет ограничение частоты перерисовки (_THROTTLE_S);
#         3) вычисляет ближайший узел в _grid_nodes;
#         4) проверяет порог привязки (доля шага сетки);
#         5) если узел изменился -> _redraw_dim_lines().
#
#    D. Перерисовка размерных линий:
#       _redraw_dim_lines()
#         Назначение: обновить все линии/подписи для текущего узла.
#         -> _clear_dim_actors()
#         -> _collect_dimension_pair(...)
#            Формирует сегменты X/Y и подписи размеров.
#         -> _add_lines_actor(...)
#            Добавляет сегменты в сцену одним PolyData-актором.
#         -> _add_labels_actor(...)
#            Добавляет подписи одним вызовом add_point_labels.
#         -> _show_dim_highlight(...)
#            Показывает сферу подсветки текущего узла.
#         -> _safe_render(...) или render()
#
#    E. Обновление последней точки контура:
#       set_dim_last_point(pt)
#         Назначение: синхронизировать модуль с текущим состоянием контура.
#         Если курсор уже привязан к узлу, выполняется _redraw_dim_lines().
#
# 3) Важные ограничения:
#    - Модуль работает только с узлами сетки (_grid_nodes), не с bbox объемов.
#    - Геометрия рисуется только при доступных self._plotter, pyvista и numpy.
#    - Список ext_segs собирается как часть расчета, но в текущей версии
#      намеренно не выводится отдельным актором.
