# -*- coding: utf-8 -*-
# gui/components/model_view/contour/contour_definition_component_part2.py

# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_grid_contour_definition.py
# Жизненный цикл определения контура + управление точками

from __future__ import annotations

from typing import TYPE_CHECKING

from error_logger import log_exception

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

class ContourDefinitionComponentPart2:
    def _append_contour_point(self: "ModelViewWidget", node: tuple[float, float]) -> bool:
        """Добавить точку в контур с предсказуемой вставкой в ближайший сегмент."""
        if node is None:
            return False
        n = (float(node[0]), float(node[1]))
        if n in self._contour_points:
            return False
        strategy = str(getattr(self, "_contour_point_insert_strategy", "append") or "append")
        if strategy != "nearest_segment":
            self._contour_points.append(n)
            return True

        # Явный алгоритм вставки:
        # 1) если точек < 2 — добавляем в конец;
        # 2) иначе вставляем между вершинами ближайшего сегмента;
        # 3) для замыкающего сегмента last->first вставляем в конец.
        points = list(self._contour_points)
        if len(points) < 2:
            self._contour_points.append(n)
            return True

        best_seg_idx = None
        best_score = None
        segments: list[tuple[int, tuple[float, float], tuple[float, float]]] = []
        for i in range(len(points) - 1):
            segments.append((i, points[i], points[i + 1]))
        if len(points) >= 3:
            segments.append((len(points) - 1, points[-1], points[0]))

        for seg_idx, a, b in segments:
            metrics = self._segment_metrics(n[0], n[1], a, b)
            if metrics is None:
                continue
            dist2, on_span, orth_to_segment, orth_to_node, node_dist2 = metrics
            score = self._segment_priority_score(
                orth_to_segment=orth_to_segment,
                orth_to_node=orth_to_node,
                on_span=on_span,
                dist2=dist2,
                node_dist2=node_dist2,
            )
            if best_score is None or score < best_score:
                best_score = score
                best_seg_idx = seg_idx

        if best_seg_idx is None or best_score is None:
            self._contour_points.append(n)
            return True

        if best_seg_idx == len(points) - 1:
            self._contour_points.append(n)
            return True

        self._contour_points.insert(best_seg_idx + 1, n)
        return True

    def _segment_priority_score(
        self: "ModelViewWidget",
        *,
        orth_to_segment: bool,
        orth_to_node: bool,
        on_span: bool,
        dist2: float,
        node_dist2: float,
    ) -> tuple[int, int, int, float, float]:
        """Оценка для вставки в сегмент.

        Приоритет:
        1) ортогональное отношение к сегменту;
        2) ортогональное отношение к одной из концевых точек сегмента;
        3) точка находится между концевыми точками сегмента;
        4) геометрическое расстояние до сегмента;
        5) расстояние до ближайшей концевой точки сегмента.
        """
        return (
            0 if orth_to_segment else 1,
            0 if orth_to_node else 1,
            0 if on_span else 1,
            float(dist2),
            float(node_dist2),
        )

    def _segment_metrics(
        self: "ModelViewWidget",
        px: float,
        py: float,
        a: tuple[float, float],
        b: tuple[float, float],
    ) -> tuple[float, bool, bool, bool, float] | None:
        """Метрики близости и ортогональности для осе-выровненного сегмента."""
        ax, ay = float(a[0]), float(a[1])
        bx, by = float(b[0]), float(b[1])
        step = max(1.0, float(getattr(self, "_current_zone_size", 1.0)))
        snap_tol = max(1e-6, step * 0.2)
        eps = max(1e-6, step * 0.05)
        if abs(ax - bx) <= eps:
            on_span = (min(ay, by) - eps) <= py <= (max(ay, by) + eps)
            y_proj = min(max(py, min(ay, by)), max(ay, by))
            dist2 = (px - ax) ** 2 + (py - y_proj) ** 2
            orth_to_segment = abs(px - ax) <= snap_tol and on_span
            orth_to_node = (
                abs(px - ax) <= snap_tol
                or abs(py - ay) <= snap_tol
                or abs(py - by) <= snap_tol
            )
            node_dist2 = min(
                (px - ax) ** 2 + (py - ay) ** 2,
                (px - bx) ** 2 + (py - by) ** 2,
            )
            return float(dist2), bool(on_span), bool(orth_to_segment), bool(orth_to_node), float(node_dist2)
        if abs(ay - by) <= eps:
            on_span = (min(ax, bx) - eps) <= px <= (max(ax, bx) + eps)
            x_proj = min(max(px, min(ax, bx)), max(ax, bx))
            dist2 = (px - x_proj) ** 2 + (py - ay) ** 2
            orth_to_segment = abs(py - ay) <= snap_tol and on_span
            orth_to_node = (
                abs(py - ay) <= snap_tol
                or abs(px - ax) <= snap_tol
                or abs(px - bx) <= snap_tol
            )
            node_dist2 = min(
                (px - ax) ** 2 + (py - ay) ** 2,
                (px - bx) ** 2 + (py - by) ** 2,
            )
            return float(dist2), bool(on_span), bool(orth_to_segment), bool(orth_to_node), float(node_dist2)
        return None

    def _nearest_contour_node_index(
        self: "ModelViewWidget",
        px: float,
        py: float,
        max_dist: float,
    ) -> int | None:
        """Вернуть индекс ближайшего узла контура в пределах max_dist."""
        points = list(getattr(self, "_contour_points", []) or [])
        if not points:
            return None
        max_dist2 = float(max_dist) * float(max_dist)
        best_idx = None
        best_dist2 = None
        for idx, (nx, ny) in enumerate(points):
            d2 = (float(px) - float(nx)) ** 2 + (float(py) - float(ny)) ** 2
            if d2 > max_dist2:
                continue
            if best_dist2 is None or d2 < best_dist2:
                best_dist2 = d2
                best_idx = idx
        return best_idx

    def _remove_nearest_contour_point(self: "ModelViewWidget", x: float, y: float) -> bool:
        if not self._contour_points:
            return False
        step = max(1.0, float(getattr(self, "_current_zone_size", 1.0)))
        max_dist2 = (step * 0.55) ** 2
        best_idx = None
        best_dist2 = None
        for idx, (px, py) in enumerate(self._contour_points):
            d2 = (px - x) ** 2 + (py - y) ** 2
            if best_dist2 is None or d2 < best_dist2:
                best_dist2 = d2
                best_idx = idx
        if best_idx is None or best_dist2 is None or best_dist2 > max_dist2:
            return False
        self._contour_points.pop(best_idx)
        if not self._contour_points:
            self._restore_overlay_parent_visual()
            self._contour_zone_mode = None
            self._contour_zone_id = None
            self._grid_plane_z_override = None
            self._clear_surface_grid()
            try:
                self._show_grid_actors()
            except Exception as e:
                log_exception(__name__, "_remove_nearest_contour_point", e)
        return True
