"""Strömbom (2014) collect/drive heuristic for the shepherd dog.

When the flock is scattered (max radius > F_FACTOR · √n) the dog moves
to a point behind the furthest sheep and pushes it back toward the
flock CoM. Otherwise it drives, parking behind the CoM relative to
the pen target. Returns a unit-vector intent ``(vx, vy, mode)``.

Reference: Strömbom et al. 2014, "Solving the shepherding problem."
"""

import math

from herding.world.geometry import (
    FIELD_ROUND_R, FIELD_SHAPE,
    PEN_ENTRY, GATE_Y, in_pen,
)

F_FACTOR = 4.0       # collect/drive threshold scaled by √n
DELTA_COLLECT = 1.5  # drive-position offset behind the furthest sheep
DELTA_DRIVE = 2.0    # drive-position offset behind the flock CoM


def _unit(x, y):
    d = math.hypot(x, y)
    if d < 1e-6:
        return 0.0, 0.0
    return x / d, y / d


def _is_active(x, y) -> bool:
    """A sheep still in the field counts; one south of the gate doesn't."""
    return (not in_pen(x, y)) and y > GATE_Y


def compute_action(dog_xy, sheep_positions, pen_target=PEN_ENTRY):
    """Return ``(vx, vy, mode)`` — mode in {idle, collect, drive}."""
    active = [(x, y) for (x, y) in sheep_positions.values() if _is_active(x, y)]
    if not active:
        return 0.0, 0.0, "idle"

    n = len(active)
    com_x = sum(p[0] for p in active) / n
    com_y = sum(p[1] for p in active) / n
    dists = [math.hypot(p[0] - com_x, p[1] - com_y) for p in active]
    radius = max(dists)

    if radius > F_FACTOR * math.sqrt(n):
        # Collect: aim behind the furthest sheep, opposite the CoM.
        idx = max(range(n), key=lambda i: dists[i])
        sx, sy = active[idx]
        ux, uy = _unit(sx - com_x, sy - com_y)
        tx, ty = sx + DELTA_COLLECT * ux, sy + DELTA_COLLECT * uy
        mode = "collect"
    else:
        # Drive: aim behind the CoM, opposite the pen.
        ux, uy = _unit(com_x - pen_target[0], com_y - pen_target[1])
        tx, ty = com_x + DELTA_DRIVE * ux, com_y + DELTA_DRIVE * uy
        mode = "drive"

    # Round-field wall fallback: if the drive target lies outside the
    # curved boundary, push the flock radially inward first so it
    # leaves the wall — otherwise the dog ends up tangent to the wall
    # and the flock circles indefinitely.
    if FIELD_SHAPE == "field_round" and mode == "drive":
        if math.hypot(tx, ty) > FIELD_ROUND_R - 1.0:
            r_com = math.hypot(com_x, com_y)
            if r_com > 1e-3:
                ux2, uy2 = com_x / r_com, com_y / r_com
                tx = com_x + DELTA_DRIVE * ux2
                ty = com_y + DELTA_DRIVE * uy2
                r_t = math.hypot(tx, ty)
                if r_t > FIELD_ROUND_R - 1.0:
                    scale = (FIELD_ROUND_R - 1.0) / r_t
                    tx *= scale
                    ty *= scale

    ax, ay = _unit(tx - dog_xy[0], ty - dog_xy[1])
    return ax, ay, mode
