use std::collections::HashMap;
use std::path::{Path, PathBuf};

use super::asset_uuid::AssetUuid;
use super::manifest::{AssetEntry, AssetManifest, AssetType};

#[derive(Debug, Clone)]
pub struct AssetRegistry {
    manifests: Vec<AssetManifest>,
    uuid_to_manifest: HashMap<AssetUuid, usize>,
    path_to_uuid: HashMap<String, AssetUuid>,
    name_to_uuid: HashMap<String, AssetUuid>,
    base_paths: Vec<PathBuf>,
}

impl Default for AssetRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl AssetRegistry {
    pub fn new() -> Self {
        Self {
            manifests: Vec::new(),
            uuid_to_manifest: HashMap::new(),
            path_to_uuid: HashMap::new(),
            name_to_uuid: HashMap::new(),
            base_paths: Vec::new(),
        }
    }

    pub fn add_manifest(&mut self, manifest: AssetManifest, base_path: impl Into<PathBuf>) {
        let manifest_index = self.manifests.len();
        let base_path = base_path.into();

        for entry in &manifest.assets {
            self.uuid_to_manifest.insert(entry.uuid, manifest_index);
            self.path_to_uuid.insert(entry.path.clone(), entry.uuid);
            if let Some(name) = &entry.name {
                self.name_to_uuid.insert(name.clone(), entry.uuid);
            }
        }

        self.manifests.push(manifest);
        self.base_paths.push(base_path);
    }

    pub fn resolve_uuid(&self, uuid: AssetUuid) -> Option<PathBuf> {
        let manifest_index = self.uuid_to_manifest.get(&uuid)?;
        let manifest = self.manifests.get(*manifest_index)?;
        let entry = manifest.get_by_uuid(uuid)?;
        let base_path = self.base_paths.get(*manifest_index)?;
        Some(base_path.join(&entry.path))
    }

    pub fn resolve_path(&self, path: &str) -> Option<AssetUuid> {
        self.path_to_uuid.get(path).copied()
    }

    pub fn resolve_name(&self, name: &str) -> Option<AssetUuid> {
        self.name_to_uuid.get(name).copied()
    }

    pub fn get_entry(&self, uuid: AssetUuid) -> Option<&AssetEntry> {
        let manifest_index = self.uuid_to_manifest.get(&uuid)?;
        let manifest = self.manifests.get(*manifest_index)?;
        manifest.get_by_uuid(uuid)
    }

    pub fn get_base_path(&self, uuid: AssetUuid) -> Option<&Path> {
        let manifest_index = self.uuid_to_manifest.get(&uuid)?;
        self.base_paths.get(*manifest_index).map(|p| p.as_path())
    }

    pub fn assets_of_type(&self, asset_type: AssetType) -> impl Iterator<Item = &AssetEntry> {
        self.manifests
            .iter()
            .flat_map(move |manifest| manifest.assets_of_type(asset_type))
    }

    pub fn all_assets(&self) -> impl Iterator<Item = &AssetEntry> {
        self.manifests.iter().flat_map(|manifest| &manifest.assets)
    }

    pub fn uuid_exists(&self, uuid: AssetUuid) -> bool {
        self.uuid_to_manifest.contains_key(&uuid)
    }

    pub fn path_exists(&self, path: &str) -> bool {
        self.path_to_uuid.contains_key(path)
    }

    pub fn name_exists(&self, name: &str) -> bool {
        self.name_to_uuid.contains_key(name)
    }

    pub fn clear(&mut self) {
        self.manifests.clear();
        self.uuid_to_manifest.clear();
        self.path_to_uuid.clear();
        self.name_to_uuid.clear();
        self.base_paths.clear();
    }

    pub fn manifest_count(&self) -> usize {
        self.manifests.len()
    }

    pub fn asset_count(&self) -> usize {
        self.manifests.iter().map(|m| m.len()).sum()
    }
}

#[derive(Debug, Clone)]
pub struct RuntimeAssetEntry {
    pub uuid: AssetUuid,
    pub name: String,
    pub asset_type: AssetType,
    pub loaded: bool,
}

#[derive(Debug, Clone, Default)]
pub struct RuntimeAssetRegistry {
    entries: HashMap<AssetUuid, RuntimeAssetEntry>,
    name_to_uuid: HashMap<String, AssetUuid>,
    type_to_uuids: HashMap<AssetType, Vec<AssetUuid>>,
}

impl RuntimeAssetRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn register(&mut self, uuid: AssetUuid, name: impl Into<String>, asset_type: AssetType) {
        let name = name.into();
        let entry = RuntimeAssetEntry {
            uuid,
            name: name.clone(),
            asset_type,
            loaded: false,
        };

        self.name_to_uuid.insert(name, uuid);
        self.type_to_uuids.entry(asset_type).or_default().push(uuid);
        self.entries.insert(uuid, entry);
    }

    pub fn mark_loaded(&mut self, uuid: AssetUuid) {
        if let Some(entry) = self.entries.get_mut(&uuid) {
            entry.loaded = true;
        }
    }

    pub fn mark_unloaded(&mut self, uuid: AssetUuid) {
        if let Some(entry) = self.entries.get_mut(&uuid) {
            entry.loaded = false;
        }
    }

    pub fn is_loaded(&self, uuid: AssetUuid) -> bool {
        self.entries.get(&uuid).is_some_and(|e| e.loaded)
    }

    pub fn get(&self, uuid: AssetUuid) -> Option<&RuntimeAssetEntry> {
        self.entries.get(&uuid)
    }

    pub fn get_by_name(&self, name: &str) -> Option<&RuntimeAssetEntry> {
        self.name_to_uuid
            .get(name)
            .and_then(|uuid| self.entries.get(uuid))
    }

    pub fn resolve_name(&self, name: &str) -> Option<AssetUuid> {
        self.name_to_uuid.get(name).copied()
    }

    pub fn unregister(&mut self, uuid: AssetUuid) {
        if let Some(entry) = self.entries.remove(&uuid) {
            self.name_to_uuid.remove(&entry.name);
            if let Some(uuids) = self.type_to_uuids.get_mut(&entry.asset_type) {
                uuids.retain(|u| *u != uuid);
            }
        }
    }

    pub fn clear(&mut self) {
        self.entries.clear();
        self.name_to_uuid.clear();
        self.type_to_uuids.clear();
    }
}
