"""Natürliche Bewegung: Profil-Gang, Schritte, Atmen – nicht ständig drehen."""

from __future__ import annotations

import math
from dataclasses import dataclass


def _lerp_angle(current: float, target: float, t: float) -> float:
    diff = (target - current + 180) % 360 - 180
    return current + diff * t


def _step_phase(bob_phase: float) -> float:
    """Zwei Schritte pro Gang-Zyklus."""
    return bob_phase * 2.0


@dataclass
class Motion3D:
    """Pose für Jam: seitlich wie eine Figur, mit menschlichem Gang."""

    yaw: float = 15.0
    pitch: float = 0.0
    roll: float = 0.0
    depth: float = 0.92
    sway: float = 0.0
    bob: float = 0.0
    squash: float = 1.0
    _step: float = 0.0

    def tick(
        self,
        *,
        vx: float,
        vy: float,
        bob_phase: float,
        moving: bool,
        dancing: bool,
        face_left: bool,
    ) -> None:
        self.bob = bob_phase
        speed = math.hypot(vx, vy)
        phase = _step_phase(bob_phase)
        self._step = math.sin(phase)
        step_cos = math.cos(phase)

        # Seitenprofil: nicht um die eigene Achse drehen
        profile = 168.0 if face_left else 12.0
        lean = 0.0
        if moving and speed > 0.12:
            lean = max(-7.0, min(7.0, vx * 1.4))
            lean += max(-4.0, min(4.0, vy * 0.5))
        elif not moving and not dancing:
            lean = math.sin(bob_phase * 0.35) * 1.2

        if dancing:
            target_yaw = profile + math.sin(bob_phase * 0.65) * 3.0
        else:
            target_yaw = profile + lean

        self.yaw = _lerp_angle(self.yaw, target_yaw, 0.05 if dancing else 0.09)

        active = moving or dancing
        if active:
            # Zwei kleine Schritte: leichtes Absenken beim Aufsetzen
            foot_down = 0.5 + 0.5 * step_cos
            lift = abs(self._step)
            hop_core = 0.35 * lift + 0.2 * (1.0 - foot_down)
            if dancing:
                self.squash = 1.0 - hop_core * 0.035
                self.pitch = 2.0 + self._step * 1.6
                self.roll = self._step * 2.0 + vx * 0.12
                self.sway = self._step * 0.045
            else:
                self.squash = 1.0 - hop_core * 0.06
                self.pitch = (5.5 if moving else 3.0) + self._step * 2.2
                self.pitch += max(-5.0, min(5.0, vy * 0.35))
                self.roll = self._step * 3.8 + vx * 0.25
                self.sway = self._step * 0.09
        else:
            breath = math.sin(bob_phase * 0.4)
            self.squash = 1.0 + breath * 0.012
            self.pitch = breath * 1.8
            self.roll = breath * 0.6
            self.sway = math.sin(bob_phase * 0.25) * 0.03

        # Leichte Tiefe im Profil, kein Kreisel-Effekt
        side = abs(math.sin(math.radians(self.yaw - (90 if face_left else -90))))
        self.depth = 0.88 + side * 0.1 + (0.04 if active else 0.0)

    def depth_scale(self) -> float:
        base = 0.72 + self.depth * 0.3
        return base * self.squash

    def hop_offset(self, *, moving: bool, dancing: bool, size_scale: float) -> float:
        if not moving and not dancing:
            return math.sin(self.bob * 0.4) * (1.2 + size_scale * 0.4)
        amp = 2.2 + size_scale * 3.2
        if dancing:
            amp *= 0.72
        phase = _step_phase(self.bob)
        lift = abs(math.sin(phase))
        dip = 0.5 * (1.0 - math.cos(phase))
        return (lift * 0.65 + dip * 0.35) * amp

    def skew(self) -> float:
        return self.sway * 0.12

    def shadow_offset(self) -> float:
        return self.sway * 0.22
