from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass, field
from typing import Protocol

from grim.geom import Vec2

from ...effects import FxQueue
from ...sim.state_types import GameplayState, PlayerState


class CreatureForPerks(Protocol):
    active: bool
    pos: Vec2
    hp: float
    hitbox_size: float
    collision_timer: float
    reward_value: float
    size: float


def creature_find_in_radius(creatures: Sequence[CreatureForPerks], *, pos: Vec2, radius: float, start_index: int) -> int:
    """Find the first active creature intersecting an aim radius.

    Port of `creature_find_in_radius` (0x004206a0).
    """

    start_index = max(0, int(start_index))
    max_index = min(len(creatures), 0x180)
    if start_index >= max_index:
        return -1

    radius = float(radius)

    for idx in range(start_index, max_index):
        creature = creatures[idx]
        if not creature.active:
            continue

        dist = (creature.pos - pos).length() - radius
        threshold = float(creature.size) * 0.14285715 + 3.0
        if threshold < dist:
            continue
        if float(creature.hitbox_size) < 5.0:
            continue
        return idx
    return -1


@dataclass(slots=True)
class PerksUpdateEffectsCtx:
    state: GameplayState
    players: list[PlayerState]
    dt: float
    creatures: Sequence[CreatureForPerks] | None
    fx_queue: FxQueue | None
    _aim_target_by_player_index: dict[int, int] = field(default_factory=dict)

    def aim_target_for_player(self, player_index: int) -> int:
        player_index = int(player_index)
        if player_index in self._aim_target_by_player_index:
            return int(self._aim_target_by_player_index[player_index])

        target = -1
        if self.creatures is None:
            self._aim_target_by_player_index[player_index] = int(target)
            return int(target)

        if bool(self.state.preserve_bugs) and player_index != 0:
            self._aim_target_by_player_index[player_index] = int(target)
            return int(target)

        if not (0 <= player_index < len(self.players)):
            self._aim_target_by_player_index[player_index] = int(target)
            return int(target)

        player = self.players[player_index]
        if not bool(self.state.preserve_bugs) and float(player.health) <= 0.0:
            self._aim_target_by_player_index[player_index] = int(target)
            return int(target)

        target = creature_find_in_radius(
            self.creatures,
            pos=player.aim,
            radius=12.0,
            start_index=0,
        )
        self._aim_target_by_player_index[player_index] = int(target)
        return int(target)

    # Backward-compatible alias for player 1 targeting.
    def aim_target(self) -> int:
        return self.aim_target_for_player(0)
