use crate::ecs::animation::components::AnimationClip;
use crate::ecs::asset_id::MeshId;
use crate::ecs::generational_registry::GenerationalRegistry;
use crate::ecs::mesh::components::Mesh;
use crate::ecs::prefab::components::Prefab;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};

pub struct MeshCache {
    pub registry: GenerationalRegistry<Mesh>,
    dirty_meshes: RefCell<HashSet<String>>,
    dirty_skinned_meshes: RefCell<HashSet<String>>,
}

#[derive(Default)]
pub struct AnimationCache {
    pub clips: HashMap<String, AnimationClip>,
}

#[derive(Default)]
pub struct PrefabCache {
    pub prefabs: HashMap<String, CachedPrefab>,
}

pub struct CachedPrefab {
    pub prefab: Prefab,
    pub animations: Vec<AnimationClip>,
    pub skins: Vec<crate::ecs::prefab::GltfSkin>,
    pub source_path: Option<String>,
}

pub fn animation_cache_insert(cache: &mut AnimationCache, name: String, clip: AnimationClip) {
    cache.clips.insert(name, clip);
}

pub fn animation_cache_get<'a>(cache: &'a AnimationCache, name: &str) -> Option<&'a AnimationClip> {
    cache.clips.get(name)
}

pub fn animation_cache_remove(cache: &mut AnimationCache, name: &str) -> Option<AnimationClip> {
    cache.clips.remove(name)
}

pub fn animation_cache_iter(
    cache: &AnimationCache,
) -> impl Iterator<Item = (&String, &AnimationClip)> + '_ {
    cache.clips.iter()
}

pub fn animation_cache_names(cache: &AnimationCache) -> impl Iterator<Item = &String> + '_ {
    cache.clips.keys()
}

pub fn animation_cache_add_to_player(
    cache: &AnimationCache,
    player: &mut crate::ecs::animation::components::AnimationPlayer,
    clip_name: &str,
) -> Option<usize> {
    if let Some(clip) = cache.clips.get(clip_name) {
        let index = player.clips.len();
        player.clips.push(clip.clone());
        Some(index)
    } else {
        None
    }
}

pub fn prefab_cache_insert(cache: &mut PrefabCache, name: String, cached: CachedPrefab) {
    cache.prefabs.insert(name, cached);
}

pub fn prefab_cache_get<'a>(cache: &'a PrefabCache, name: &str) -> Option<&'a CachedPrefab> {
    cache.prefabs.get(name)
}

pub fn prefab_cache_remove(cache: &mut PrefabCache, name: &str) -> Option<CachedPrefab> {
    cache.prefabs.remove(name)
}

pub fn prefab_cache_names(cache: &PrefabCache) -> impl Iterator<Item = &String> + '_ {
    cache.prefabs.keys()
}

impl Default for MeshCache {
    fn default() -> Self {
        Self {
            registry: GenerationalRegistry::new(),
            dirty_meshes: RefCell::new(HashSet::new()),
            dirty_skinned_meshes: RefCell::new(HashSet::new()),
        }
    }
}

pub fn mesh_cache_insert(cache: &mut MeshCache, name: String, mesh: Mesh) -> MeshId {
    if let Some((index, generation)) = cache.registry.lookup_index(&name) {
        if let Some(entry) = cache.registry.entries.get_mut(index as usize) {
            *entry = Some(mesh);
        }
        cache.dirty_meshes.borrow_mut().insert(name);
        return MeshId { index, generation };
    }
    let (index, generation) = cache.registry.insert(name.clone(), mesh);
    cache.registry.add_reference(index);
    cache.dirty_meshes.borrow_mut().insert(name);
    MeshId { index, generation }
}

pub fn mesh_cache_take_dirty(cache: &MeshCache) -> HashSet<String> {
    std::mem::take(&mut *cache.dirty_meshes.borrow_mut())
}

pub fn mesh_cache_mark_dirty(cache: &MeshCache, name: String) {
    cache.dirty_meshes.borrow_mut().insert(name);
}

pub fn mesh_cache_take_dirty_skinned(cache: &MeshCache) -> HashSet<String> {
    std::mem::take(&mut *cache.dirty_skinned_meshes.borrow_mut())
}

pub fn mesh_cache_mark_dirty_skinned(cache: &MeshCache, name: String) {
    cache.dirty_skinned_meshes.borrow_mut().insert(name);
}

pub fn mesh_cache_lookup_id(cache: &MeshCache, name: &str) -> Option<MeshId> {
    cache
        .registry
        .lookup_index(name)
        .map(|(index, generation)| MeshId { index, generation })
}

pub fn mesh_cache_remove(cache: &mut MeshCache, name: &str) -> Option<Mesh> {
    if let Some((index, generation)) = cache.registry.lookup_index(name) {
        cache.registry.remove(index, generation)
    } else {
        None
    }
}

pub fn mesh_cache_remove_by_id(cache: &mut MeshCache, id: MeshId) -> Option<Mesh> {
    cache.registry.remove(id.index, id.generation)
}

pub fn mesh_cache_iter(cache: &MeshCache) -> impl Iterator<Item = (&String, &Mesh)> {
    cache
        .registry
        .index_to_name
        .iter()
        .enumerate()
        .filter_map(|(index, name_opt)| {
            name_opt.as_ref().and_then(|name| {
                cache.registry.entries[index]
                    .as_ref()
                    .map(|mesh| (name, mesh))
            })
        })
}
