use bevy::prelude::*;
use lightyear::prelude::{input::native::ActionState, *};
use shared::{
    backpack::{Backpack, backpack_ui::BackpackUiState},
    camera::{CameraArmRotation, CameraTarget},
    cash::CashResource,
    character::AnimatedCharacter,
    control::{ControlState, controller_common::PlayerCharacterController},
    global_observer,
    head::ActiveHead,
    head_drop::HeadDrops,
    heads::{ActiveHeads, HeadChanged, HeadState, heads_ui::UiActiveHeads},
    heads_database::HeadsDatabase,
    hitpoints::{Hitpoints, Kill},
    npc::SpawnCharacter,
    player::{Player, PlayerBodyMesh},
    protocol::{
        PlaySound, PlayerId, channels::UnorderedReliableChannel, events::ClientHeadChanged,
    },
    tb_entities::SpawnPoint,
};

pub fn plugin(app: &mut App) {
    global_observer!(app, on_update_head_mesh);
}

pub fn spawn(
    mut commands: Commands,
    owner: Entity,
    query: Query<&Transform, With<SpawnPoint>>,
    heads_db: Res<HeadsDatabase>,
) -> Option<Entity> {
    let spawn = query.iter().next()?;

    let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));

    let mut player = commands.spawn((
        (
            Name::from("player"),
            Player,
            ActiveHead(0),
            ActiveHeads::new([
                Some(HeadState::new(0, heads_db.as_ref())),
                Some(HeadState::new(3, heads_db.as_ref())),
                Some(HeadState::new(6, heads_db.as_ref())),
                Some(HeadState::new(10, heads_db.as_ref())),
                Some(HeadState::new(9, heads_db.as_ref())),
            ]),
            Hitpoints::new(100),
            CashResource::default(),
            CameraTarget,
            transform,
            Visibility::default(),
            PlayerCharacterController,
            PlayerId { id: 0 },
        ),
        ActionState::<ControlState>::default(),
        Backpack::default(),
        BackpackUiState::default(),
        UiActiveHeads::default(),
        Replicate::to_clients(NetworkTarget::All),
        PredictionTarget::to_clients(NetworkTarget::All),
        ControlledBy {
            owner,
            lifetime: Lifetime::SessionBased,
        },
        children![(
            Name::new("player-rig"),
            PlayerBodyMesh,
            CameraArmRotation,
            children![AnimatedCharacter::new(0)],
        )],
    ));
    player.observe(on_kill);

    let id = player.id();

    commands.trigger(PlaySound::Head("angry demonstrator".to_string()));
    commands.trigger(SpawnCharacter(transform.translation));

    Some(id)
}

fn on_kill(
    trigger: Trigger<Kill>,
    mut commands: Commands,
    mut query: Query<(&Transform, &ActiveHead, &mut ActiveHeads, &mut Hitpoints)>,
) {
    let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.target()) else {
        return;
    };

    commands.trigger(HeadDrops::new(transform.translation, active.0));

    if let Some(new_head) = heads.loose_current() {
        hp.set_health(heads.current().unwrap().health);

        commands.trigger(HeadChanged(new_head));
    }
}

fn on_update_head_mesh(
    trigger: Trigger<HeadChanged>,
    mut commands: Commands,
    mesh_children: Single<&Children, With<PlayerBodyMesh>>,
    mut sender: Single<&mut TriggerSender<ClientHeadChanged>>,
    animated_characters: Query<&AnimatedCharacter>,
    mut player: Single<&mut ActiveHead, With<Player>>,
) -> Result {
    let animated_char = mesh_children
        .iter()
        .find(|child| animated_characters.contains(*child))
        .ok_or("tried to update head mesh before AnimatedCharacter was readded")?;

    player.0 = trigger.0;

    commands
        .entity(animated_char)
        .insert(AnimatedCharacter::new(trigger.0));

    sender.trigger::<UnorderedReliableChannel>(ClientHeadChanged(trigger.0 as u64));

    Ok(())
}
