use crate::net::protocol::PlayerSnapshot;
use crate::world::{GameMap, Vec2, angle_diff};

pub const PISTOL_DAMAGE: i16 = 34;
pub const PISTOL_COOLDOWN_MS: u64 = 350;
pub const RESPAWN_MS: u64 = 3_000;
pub const HIT_RADIUS: f32 = 0.28;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HitResult {
    pub target_id: u8,
}

pub fn hitscan_target(
    map: &GameMap,
    shooter_id: u8,
    origin: Vec2,
    angle: f32,
    players: &[PlayerSnapshot],
) -> Option<HitResult> {
    let mut best: Option<(u8, f32)> = None;

    for player in players {
        if player.id == shooter_id || !player.alive {
            continue;
        }

        let dx = player.position.x - origin.x;
        let dy = player.position.y - origin.y;
        let forward_distance = dx * angle.cos() + dy * angle.sin();
        if forward_distance <= 0.0 {
            continue;
        }

        let target_angle = dy.atan2(dx);
        let miss_distance =
            angle_diff(target_angle, angle).sin().abs() * origin.distance(player.position);
        if miss_distance > HIT_RADIUS {
            continue;
        }

        let wall_hit = map.raycast(origin, angle, forward_distance);
        if wall_hit.hit && wall_hit.distance + 0.05 < forward_distance {
            continue;
        }

        match best {
            Some((_, best_distance)) if best_distance <= forward_distance => {}
            _ => best = Some((player.id, forward_distance)),
        }
    }

    best.map(|(target_id, _)| HitResult { target_id })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::world::GameMap;

    fn player(id: u8, x: f32, y: f32) -> PlayerSnapshot {
        PlayerSnapshot {
            id,
            name: format!("P{id}"),
            position: Vec2::new(x, y),
            angle: 0.0,
            health: 100,
            score: 0,
            deaths: 0,
            alive: true,
            respawn_ms: 0,
        }
    }

    #[test]
    fn hits_visible_target() {
        let map = GameMap::parse(
            "\
######
#1.2.#
#....#
#3..4#
######
",
        )
        .unwrap();
        let players = vec![player(0, 1.5, 1.5), player(1, 3.5, 1.5)];
        let hit = hitscan_target(&map, 0, players[0].position, 0.0, &players);
        assert_eq!(hit.unwrap().target_id, 1);
    }

    #[test]
    fn wall_blocks_hit() {
        let map = GameMap::parse(
            "\
######
#1#2.#
#....#
#3..4#
######
",
        )
        .unwrap();
        let players = vec![player(0, 1.5, 1.5), player(1, 3.5, 1.5)];
        assert!(hitscan_target(&map, 0, players[0].position, 0.0, &players).is_none());
    }
}
