from __future__ import annotations

import re
import tkinter as tk
from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class MonitorBounds:
    x: int
    y: int
    width: int
    height: int
    primary: bool = False

    @property
    def center(self) -> tuple[int, int]:
        return self.x + self.width // 2, self.y + self.height // 2

    def as_tuple(self) -> tuple[int, int, int, int]:
        return self.x, self.y, self.width, self.height


def available_monitors(root: tk.Misc | None = None) -> list[MonitorBounds]:
    try:
        from screeninfo import get_monitors

        monitors = [
            MonitorBounds(
                int(monitor.x),
                int(monitor.y),
                int(monitor.width),
                int(monitor.height),
                bool(getattr(monitor, "is_primary", False)),
            )
            for monitor in get_monitors()
        ]
        if monitors:
            return monitors
    except Exception:
        pass
    if root is None:
        return []
    return [
        MonitorBounds(
            int(root.winfo_vrootx()),
            int(root.winfo_vrooty()),
            int(root.winfo_vrootwidth()),
            int(root.winfo_vrootheight()),
            True,
        )
    ]


def preferred_monitor(
    root: tk.Misc,
    saved_bounds: tuple[int, int, int, int] | None = None,
) -> MonitorBounds:
    monitors = available_monitors(root)
    if saved_bounds:
        for monitor in monitors:
            if monitor.as_tuple() == saved_bounds:
                return monitor
    return next((monitor for monitor in monitors if monitor.primary), monitors[0])


def monitor_for_geometry(root: tk.Misc, geometry: str) -> MonitorBounds:
    monitors = available_monitors(root)
    parsed = parse_geometry(geometry)
    if not parsed:
        return next((monitor for monitor in monitors if monitor.primary), monitors[0])
    width, height, x, y = parsed
    center_x, center_y = x + width // 2, y + height // 2
    containing = [
        monitor
        for monitor in monitors
        if monitor.x <= center_x < monitor.x + monitor.width
        and monitor.y <= center_y < monitor.y + monitor.height
    ]
    if containing:
        return containing[0]
    return min(monitors, key=lambda monitor: _distance_squared(monitor.center, (center_x, center_y)))


def centered_geometry(width: int, height: int, monitor: MonitorBounds) -> str:
    fitted_width = min(width, max(640, monitor.width - 80))
    fitted_height = min(height, max(480, monitor.height - 80))
    x = monitor.x + (monitor.width - fitted_width) // 2
    y = monitor.y + (monitor.height - fitted_height) // 2
    return format_geometry(fitted_width, fitted_height, x, y)


def ensure_visible_geometry(geometry: str, monitor: MonitorBounds) -> str:
    parsed = parse_geometry(geometry)
    if not parsed:
        return centered_geometry(1500, 860, monitor)
    width, height, x, y = parsed
    width = min(width, max(640, monitor.width - 40))
    height = min(height, max(480, monitor.height - 40))
    x = min(max(x, monitor.x), monitor.x + monitor.width - width)
    y = min(max(y, monitor.y), monitor.y + monitor.height - height)
    return format_geometry(width, height, x, y)


def parse_geometry(geometry: str) -> tuple[int, int, int, int] | None:
    match = re.fullmatch(r"(\d+)x(\d+)([+-]\d+)([+-]\d+)", geometry.strip())
    if not match:
        return None
    return tuple(int(value) for value in match.groups())  # type: ignore[return-value]


def format_geometry(width: int, height: int, x: int, y: int) -> str:
    return f"{width}x{height}{x:+d}{y:+d}"


def _distance_squared(left: tuple[int, int], right: tuple[int, int]) -> int:
    return (left[0] - right[0]) ** 2 + (left[1] - right[1]) ** 2
