# -*- coding: utf-8 -*-
# gui/components/model_view/_mv_rack_geometry.py
"""Локальная геометрия размещения стоек (без зависимостей от hub)."""

from __future__ import annotations

from typing import Any

PLACEMENT_STEP_MM = 100
MIN_WALL_BUFFER_MM = 100
MIN_AISLE_MM = 900
MIN_BACK_TO_BACK_GAP_MM = 100


def rotate_xy(x: float, y: float, rotation: int) -> tuple[float, float]:
    rot = int(rotation) % 360
    if rot == 90:
        return -y, x
    if rot == 180:
        return -x, -y
    if rot == 270:
        return y, -x
    return x, y


def rack_bbox(
    cx: float,
    cy: float,
    params: dict[str, Any],
    rotation: int,
    wall_buffer: float = 0.0,
) -> tuple[float, float, float, float]:
    width = float(params.get("footprint_width_mm", 1000))
    depth = float(params.get("footprint_depth_mm", 500))
    if int(rotation) % 180 == 90:
        width, depth = depth, width
    width += wall_buffer * 2.0
    depth += wall_buffer * 2.0
    half_w = width / 2.0
    half_d = depth / 2.0
    return cx - half_w, cx + half_w, cy - half_d, cy + half_d


def passes_clearance_rule(
    a: tuple[float, float, float, float],
    b: tuple[float, float, float, float],
    min_gap: float,
) -> bool:
    a_min_x, a_max_x, a_min_y, a_max_y = a
    b_min_x, b_max_x, b_min_y, b_max_y = b
    overlap_x = min(a_max_x, b_max_x) - max(a_min_x, b_min_x)
    overlap_y = min(a_max_y, b_max_y) - max(a_min_y, b_min_y)
    if overlap_x > 0 and overlap_y > 0:
        return False
    gap_x = max(0.0, max(a_min_x - b_max_x, b_min_x - a_max_x))
    gap_y = max(0.0, max(a_min_y - b_max_y, b_min_y - a_max_y))
    if overlap_y > 0 and gap_x < min_gap:
        return False
    if overlap_x > 0 and gap_y < min_gap:
        return False
    # Диагональный случай: учитываем минимальную евклидову дистанцию между прямоугольниками.
    if gap_x > 0 and gap_y > 0 and (gap_x * gap_x + gap_y * gap_y) ** 0.5 < min_gap:
        return False
    return True


def bboxes_intersect(
    a: tuple[float, float, float, float],
    b: tuple[float, float, float, float],
    eps: float = 1e-6,
) -> bool:
    a_min_x, a_max_x, a_min_y, a_max_y = a
    b_min_x, b_max_x, b_min_y, b_max_y = b
    overlap_x = min(a_max_x, b_max_x) - max(a_min_x, b_min_x)
    overlap_y = min(a_max_y, b_max_y) - max(a_min_y, b_min_y)
    return overlap_x > eps and overlap_y > eps

# зона подхода к стеллажу
def rack_aisle_bboxes(
    cx: float,
    cy: float,
    params: dict[str, Any],
    rotation: int,
    aisle_mm: float = MIN_AISLE_MM,
) -> list[tuple[float, float, float, float]]:
    width = float(params.get("footprint_width_mm", 1000))
    depth = float(params.get("footprint_depth_mm", 500))
    aisle = float(aisle_mm)

    # Зона доступа представлена одним объёмом (один прямоугольник).
    strips = [
        (0.0, aisle / 2.0, width + 2.0 * aisle, depth + aisle),
    ]
    result: list[tuple[float, float, float, float]] = []
    for lx, ly, xl, yl in strips:
        wx, wy = rotate_xy(lx, ly, rotation)
        if int(rotation) % 180 == 90:
            xl, yl = yl, xl
        hxl = max(10.0, float(xl)) / 2.0
        hyl = max(10.0, float(yl)) / 2.0
        result.append((cx + wx - hxl, cx + wx + hxl, cy + wy - hyl, cy + wy + hyl))
    return result


def front_vector(rotation: int) -> tuple[int, int]:
    rot = int(rotation) % 360
    if rot == 90:
        return 1, 0
    if rot == 180:
        return 0, -1
    if rot == 270:
        return -1, 0
    return 0, 1


def is_back_to_back(rotation_a: int, rotation_b: int) -> bool:
    ax, ay = front_vector(rotation_a)
    bx, by = front_vector(rotation_b)
    return (ax * bx + ay * by) < 0


def pillar_positions(
    cx: float,
    cy: float,
    params: dict[str, Any],
    rotation: int,
) -> list[tuple[float, float]]:
    center_spans = [float(v) for v in (params.get("center_spans_mm") or [1000])]
    depth = float(params.get("footprint_depth_mm", 500))
    total_width = float(sum(center_spans))
    x_points = [-total_width / 2.0]
    for span in center_spans:
        x_points.append(x_points[-1] + span)
    y_points = (-depth / 2.0, depth / 2.0)
    positions: list[tuple[float, float]] = []
    for lx in x_points:
        for ly in y_points:
            rx, ry = rotate_xy(lx, ly, rotation)
            positions.append((cx + rx, cy + ry))
    return positions


def support_line_positions(
    cx: float,
    cy: float,
    params: dict[str, Any],
    rotation: int,
) -> list[tuple[float, float]]:
    """Позиции опорных линий стоек (для 1 секции = 2 стойки)."""
    center_spans = [float(v) for v in (params.get("center_spans_mm") or [1000])]
    total_width = float(sum(center_spans))
    x_points = [-total_width / 2.0]
    for span in center_spans:
        x_points.append(x_points[-1] + span)
    positions: list[tuple[float, float]] = []
    for lx in x_points:
        rx, ry = rotate_xy(lx, 0.0, rotation)
        positions.append((cx + rx, cy + ry))
    return positions


# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
# 1) Задача модуля:
#    Чистые функции геометрии стеллажей: габариты, пересечения, проходы, опоры и ориентация.
#
# 2) Последовательность действий и вызовов:
#    A. Функции уровня модуля:
#       rotate_xy(...)
#         Назначение: выполняет шаг "rotate xy" в рамках текущего сценария модуля.
#       rack_bbox(...)
#         Назначение: выполняет шаг "rack bbox" в рамках текущего сценария модуля.
#       passes_clearance_rule(...)
#         Назначение: выполняет шаг "passes clearance rule" в рамках текущего сценария модуля.
#       bboxes_intersect(...)
#         Назначение: выполняет шаг "bboxes intersect" в рамках текущего сценария модуля.
#       rack_aisle_bboxes(...)
#         Назначение: выполняет шаг "rack aisle bboxes" в рамках текущего сценария модуля.
#       front_vector(...)
#         Назначение: выполняет шаг "front vector" в рамках текущего сценария модуля.
#       is_back_to_back(...)
#         Назначение: проверяет, что back to back в рамках текущего сценария модуля.
#       pillar_positions(...)
#         Назначение: выполняет шаг "pillar positions" в рамках текущего сценария модуля.
#       support_line_positions(...)
#         Назначение: Позиции опорных линий стоек (для 1 секции = 2 стойки).
#
# 3) Важные ограничения и инварианты:
#    - Модуль выполняется в составе ModelViewWidget и использует согласованные поля состояния self._... .
#    - Межмодульная связность: только через фасад model_view; прямые обращения между zone, rack, shelf, cell запрещены.
#    - Очистка состояния должна быть идемпотентной: повторный вызов не меняет корректное состояние в ошибочное.
#
# 4) Правило сопровождения:
#    - Любое изменение сценария должно сопровождаться обновлением этого блока с сохранением фактического порядка вызовов.
#    - При добавлении метода указывать его место в цепочке сценария (запуск, основной шаг, завершение, вспомогательная логика).

