use crate::{
    loading_assets::{GameAssets, HeadDropAssets},
    protocol::TbMapEntityMapping,
};
use bevy::{
    ecs::{component::HookContext, world::DeferredWorld},
    prelude::*,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Component, Reflect, Serialize, Deserialize, PartialEq)]
#[reflect(Component)]
pub struct PlayerId {
    pub id: u8,
}

/// A unique ID assigned to every entity spawned by the trenchbroom map loader. This allows synchronizing
/// them across the network even when they are spawned initially by both sides.
#[derive(Clone, Copy, Component, Debug, Reflect, Serialize, Deserialize, PartialEq)]
#[reflect(Component)]
#[component(on_insert = TbMapEntityId::insert_id, on_remove = TbMapEntityId::remove_id)]
pub struct TbMapEntityId {
    pub id: u64,
}

impl TbMapEntityId {
    fn insert_id(mut world: DeferredWorld, ctx: HookContext) {
        let id = world.get::<TbMapEntityId>(ctx.entity).unwrap().id;
        world
            .resource_mut::<TbMapEntityMapping>()
            .insert(id, ctx.entity);
    }

    fn remove_id(mut world: DeferredWorld, ctx: HookContext) {
        let id = world.get::<TbMapEntityId>(ctx.entity).unwrap().id;
        world.resource_mut::<TbMapEntityMapping>().remove(&id);
    }
}

#[derive(Component, Reflect, Serialize, Deserialize, PartialEq)]
#[reflect(Component)]
pub enum GltfSceneRoot {
    Projectile(String),
    HeadDrop(String),
    Key,
}

pub fn spawn_gltf_scene_roots(
    trigger: Trigger<OnAdd, GltfSceneRoot>,
    mut commands: Commands,
    gltf_roots: Query<&GltfSceneRoot>,
    head_drop_assets: Res<HeadDropAssets>,
    assets: Res<GameAssets>,
    gltfs: Res<Assets<Gltf>>,
) -> Result {
    let root = gltf_roots.get(trigger.target())?;

    let get_scene = |gltf: Handle<Gltf>, index: usize| {
        let gltf = gltfs.get(&gltf).unwrap();
        gltf.scenes[index].clone()
    };

    let scene = match root {
        GltfSceneRoot::Projectile(addr) => get_scene(
            assets.projectiles[format!("{addr}.glb").as_str()].clone(),
            0,
        ),
        GltfSceneRoot::HeadDrop(addr) => {
            let gltf = head_drop_assets
                .meshes
                .get(format!("{addr}.glb").as_str())
                .cloned();
            let gltf = gltf.unwrap_or_else(|| head_drop_assets.meshes["none.glb"].clone());
            get_scene(gltf, 0)
        }
        GltfSceneRoot::Key => assets.mesh_key.clone(),
    };

    commands.entity(trigger.target()).insert(SceneRoot(scene));

    Ok(())
}
