"""Selbst programmierte Jam-Shows (Feuerwerk, Sterne, Konfetti, Tanz, Spin …)."""

from __future__ import annotations

import math
import random
from dataclasses import dataclass

from PyQt6.QtCore import QPoint, QRect, Qt, QTimer
from PyQt6.QtGui import QColor, QPainter, QPen
from PyQt6.QtWidgets import QApplication, QWidget

# Mindest-Ruhezeit bis zur ersten Show; Abstand zwischen Shows
IDLE_BEFORE_SHOW_SEC = 360.0
SHOW_COOLDOWN_SEC = 300.0
SHOW_CHECK_MS = 45_000

SHOW_DURATIONS = {
    "fireworks": 10.0,
    "dance_burst": 8.0,
    "spin_party": 6.5,
    "bounce_loop": 5.5,
    "star_parade": 7.0,
    "confetti_rain": 7.5,
    "rainbow_pulse": 6.0,
}

SHOW_IDS = tuple(SHOW_DURATIONS.keys())

THOUGHTS = {
    "fireworks": "💭 Langeweile… Feuerwerk!",
    "dance_burst": "💭 Ich tanz allein – Showtime!",
    "spin_party": "💭 Kreisel-Jam dreht sich!",
    "bounce_loop": "💭 Hüpf-Show! Boing boing!",
    "star_parade": "💭 Sterne um mich herum!",
    "confetti_rain": "💭 Konfetti-Regen!",
    "rainbow_pulse": "💭 Regenbogen-Puls!",
}

BUBBLE_LINES = (
    "Kleine Show nur für mich!",
    "Ich mach mal eine Animation!",
    "Langeweile? Hier kommt Jam-TV!",
    "Selbst programmiert – cool oder?",
)


@dataclass
class _Dot:
    x: float
    y: float
    vx: float
    vy: float
    life: float
    max_life: float
    color: QColor
    size: float
    spin: float = 0.0


class JamParticleShow(QWidget):
    """Partikel-Overlay um Jam (Sterne, Konfetti, Regenbogen-Funken)."""

    def __init__(self, center_global: QPoint, kind: str, *, radius: int = 220) -> None:
        super().__init__()
        self._kind = kind
        self._dots: list[_Dot] = []
        self._tick_n = 0
        self._max_ticks = 220
        screen = QApplication.primaryScreen()
        if screen is None:
            self._invalid = True
            return
        geo = screen.availableGeometry()
        half = radius
        rect = QRect(
            center_global.x() - half,
            center_global.y() - half,
            half * 2,
            half * 2,
        )
        rect = rect.intersected(geo)
        if rect.width() < 80 or rect.height() < 80:
            self._invalid = True
            return
        self.setGeometry(rect)
        self._invalid = False
        self.setWindowFlags(
            Qt.WindowType.FramelessWindowHint
            | Qt.WindowType.WindowStaysOnTopHint
            | Qt.WindowType.Tool
            | Qt.WindowType.NoDropShadowWindowHint
        )
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
        self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating, True)
        self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
        self._cx = rect.width() / 2
        self._cy = rect.height() / 2
        self._timer = QTimer(self)
        self._timer.timeout.connect(self._on_tick)
        self._timer.start(33)
        self._seed_particles()

    def _seed_particles(self) -> None:
        w, h = self.width(), self.height()
        cx, cy = self._cx, self._cy
        if self._kind == "star_parade":
            for i in range(48):
                angle = (i / 48) * math.tau + random.uniform(-0.2, 0.2)
                dist = random.uniform(40, min(w, h) * 0.42)
                self._dots.append(
                    _Dot(
                        x=cx + math.cos(angle) * dist,
                        y=cy + math.sin(angle) * dist,
                        vx=math.cos(angle + 0.5) * 0.4,
                        vy=math.sin(angle + 0.5) * 0.4,
                        life=random.uniform(0.6, 1.0),
                        max_life=1.0,
                        color=QColor(255, 255, 200),
                        size=random.uniform(1.5, 3.5),
                    )
                )
        elif self._kind == "confetti_rain":
            palette = (
                QColor(255, 90, 120),
                QColor(90, 200, 255),
                QColor(255, 220, 80),
                QColor(140, 255, 160),
                QColor(200, 120, 255),
            )
            for _ in range(55):
                self._dots.append(
                    _Dot(
                        x=random.uniform(0, w),
                        y=random.uniform(-40, h * 0.3),
                        vx=random.uniform(-0.8, 0.8),
                        vy=random.uniform(1.2, 3.8),
                        life=random.uniform(0.7, 1.2),
                        max_life=1.2,
                        color=random.choice(palette),
                        size=random.uniform(2, 5),
                        spin=random.uniform(-0.3, 0.3),
                    )
                )
        else:  # rainbow_pulse sparks
            for i in range(36):
                angle = (i / 36) * math.tau
                self._dots.append(
                    _Dot(
                        x=cx,
                        y=cy,
                        vx=math.cos(angle) * random.uniform(1.5, 4.5),
                        vy=math.sin(angle) * random.uniform(1.5, 4.5),
                        life=random.uniform(0.5, 1.0),
                        max_life=1.0,
                        color=QColor.fromHsv(int((i * 18) % 360), 200, 255),
                        size=random.uniform(2, 4),
                    )
                )

    def _on_tick(self) -> None:
        self._tick_n += 1
        w, h = self.width(), self.height()
        cx, cy = self._cx, self._cy
        alive: list[_Dot] = []
        for d in self._dots:
            d.life -= 0.022
            if d.life <= 0:
                if self._tick_n < self._max_ticks - 30 and self._kind == "confetti_rain":
                    d.y = random.uniform(-20, 0)
                    d.x = random.uniform(0, w)
                    d.life = d.max_life
                else:
                    continue
            if self._kind == "star_parade":
                angle = math.atan2(d.y - cy, d.x - cx) + 0.04
                dist = math.hypot(d.x - cx, d.y - cy)
                dist = max(30, min(dist + 0.15, min(w, h) * 0.45))
                d.x = cx + math.cos(angle) * dist
                d.y = cy + math.sin(angle) * dist
            else:
                d.x += d.vx
                d.y += d.vy
                if self._kind == "confetti_rain":
                    d.vy += 0.06
                    d.x += d.spin * 8
                elif d.life < d.max_life * 0.5:
                    d.vx *= 0.96
                    d.vy *= 0.96
            alive.append(d)
        self._dots = alive
        if self._tick_n >= self._max_ticks and len(self._dots) < 8:
            self._timer.stop()
            self.close()
        self.update()

    def paintEvent(self, _event) -> None:
        p = QPainter(self)
        p.setRenderHint(QPainter.RenderHint.Antialiasing)
        p.fillRect(self.rect(), Qt.GlobalColor.transparent)
        for d in self._dots:
            alpha = max(0, min(255, int(255 * (d.life / d.max_life))))
            c = QColor(d.color)
            c.setAlpha(alpha)
            if self._kind == "confetti_rain":
                p.setPen(Qt.PenStyle.NoPen)
                p.setBrush(c)
                p.drawRect(int(d.x), int(d.y), int(d.size * 2), int(d.size))
            else:
                p.setPen(Qt.PenStyle.NoPen)
                p.setBrush(c)
                r = max(1, int(d.size))
                p.drawEllipse(QPoint(int(d.x), int(d.y)), r, r)
        p.end()


def launch_particle_show(jam_window: QWidget, kind: str) -> JamParticleShow | None:
    center = jam_window.frameGeometry().center()
    show = JamParticleShow(center, kind)
    if getattr(show, "_invalid", False):
        show.deleteLater()
        return None
    show.show()
    show.raise_()
    return show


def pick_show(state) -> str:
    """Zufällige Show; Feuerwerk etwas seltener."""
    weights = {
        "dance_burst": 3,
        "spin_party": 2,
        "bounce_loop": 2,
        "star_parade": 3,
        "confetti_rain": 3,
        "rainbow_pulse": 2,
        "fireworks": 1,
    }
    pool: list[str] = []
    for sid, w in weights.items():
        pool.extend([sid] * w)
    last = getattr(state, "last_idle_show_kind", "")
    choices = [s for s in pool if s != last] or pool
    return random.choice(choices)


def thought_for(show_id: str) -> str:
    return THOUGHTS.get(show_id, "💭 Jam-Show!")


def duration_for(show_id: str) -> float:
    return SHOW_DURATIONS.get(show_id, 6.0)


def bubble_line() -> str:
    return random.choice(BUBBLE_LINES)


def should_run_idle_show(
    state,
    *,
    last_show_at: float,
    now: float,
    blocked: bool,
) -> bool:
    if blocked:
        return False
    idle = now - getattr(state, "last_interaction", now)
    if idle < IDLE_BEFORE_SHOW_SEC:
        return False
    if now - last_show_at < SHOW_COOLDOWN_SEC:
        return False
    return True
