"""Shared action post-processing.

Every dog mode routes its action through ``modulate_speed``
so the magnitude is reduced near sheep — direction (intent) is
preserved.
"""

from __future__ import annotations

import math


SLOW_NEAR_SHEEP = 2.5  # m — distance below which action norm is scaled down
MIN_SPEED = 0.30       # action norm at zero distance


def modulate_speed(
    vx: float, vy: float,
    dog_xy: tuple[float, float],
    sheep_positions,
    slow_dist: float = SLOW_NEAR_SHEEP,
    min_scale: float = MIN_SPEED,
) -> tuple[float, float]:
    """Linearly ramp action magnitude from ``min_scale`` at distance 0
    to 1.0 at ``slow_dist``. ``sheep_positions`` may be a
    ``{name: (x, y)}`` dict or an iterable of ``(x, y)`` tuples.
    """
    if not sheep_positions:
        return vx, vy
    if hasattr(sheep_positions, "values"):
        positions = sheep_positions.values()
    else:
        positions = sheep_positions
    nearest = float("inf")
    for sx, sy in positions:
        d = math.hypot(sx - dog_xy[0], sy - dog_xy[1])
        if d < nearest:
            nearest = d
    if nearest >= slow_dist or nearest == float("inf"):
        return vx, vy
    scale = min_scale + (1.0 - min_scale) * (nearest / slow_dist)
    return vx * scale, vy * scale
