//! Project asset registry (stable IDs + import metadata). Phase 5 foundation.

use bevy::prelude::*;
use shared::{
    parse_asset_registry, AssetRegistryDocument, ProjectContentDefaults,
    ASSET_REGISTRY_SCHEMA_VERSION,
};
pub use shared::{
    AssetId, AssetKind, AssetRecord, ImportSettings, MaterialImportPolicy, ModelHierarchyMode,
    ModelMaterialSelection, ModelMaterialSlotSelection, ModelPlacementMode,
};
use std::path::Path;
#[cfg(test)]
use uuid::Uuid;

use crate::assets::fingerprint::fingerprint_file;

#[derive(Resource, Debug)]
pub struct AssetRegistry {
    pub schema_version: u32,
    pub defaults: ProjectContentDefaults,
    pub records: Vec<AssetRecord>,
    pub index_dirty: bool,
    pub migration_required: bool,
}

impl Default for AssetRegistry {
    fn default() -> Self {
        Self {
            schema_version: ASSET_REGISTRY_SCHEMA_VERSION,
            defaults: ProjectContentDefaults::default(),
            records: Vec::new(),
            index_dirty: false,
            migration_required: false,
        }
    }
}

impl AssetRegistry {
    pub fn document(&self) -> AssetRegistryDocument {
        AssetRegistryDocument {
            schema_version: self.schema_version,
            defaults: self.defaults.clone(),
            records: self.records.clone(),
        }
    }
}

pub struct AssetDbPlugin;

impl Plugin for AssetDbPlugin {
    fn build(&self, app: &mut App) {
        let registry = load_registry();
        app.insert_resource(registry.defaults.clone())
            .insert_resource(registry);
    }
}

fn imported_source_kind(kind: AssetKind) -> bool {
    kind.is_imported_source()
}

pub fn find_asset_by_path(registry: &AssetRegistry, path: &str) -> Option<AssetRecord> {
    registry
        .records
        .iter()
        .find(|record| record.path == path)
        .cloned()
}

pub fn find_asset_by_id(registry: &AssetRegistry, id: &str) -> Option<AssetRecord> {
    registry
        .records
        .iter()
        .find(|record| record.id.as_string() == id)
        .cloned()
}

pub fn find_asset_mut_by_path<'a>(
    registry: &'a mut AssetRegistry,
    path: &str,
) -> Option<&'a mut AssetRecord> {
    registry
        .records
        .iter_mut()
        .find(|record| record.path == path)
}

pub fn ensure_asset_record(
    registry: &mut AssetRegistry,
    path: impl Into<String>,
    label: impl Into<String>,
    kind_tag: impl Into<String>,
) -> Result<AssetRecord, String> {
    ensure_asset_record_at(registry, Path::new("."), path, label, kind_tag)
}

pub fn ensure_asset_record_at(
    registry: &mut AssetRegistry,
    project_root: &Path,
    path: impl Into<String>,
    label: impl Into<String>,
    kind_tag: impl Into<String>,
) -> Result<AssetRecord, String> {
    let path = path.into();
    if let Some(record) = find_asset_by_path(registry, &path) {
        return Ok(record);
    }
    let kind = AssetKind::from_legacy_tag(&kind_tag.into());
    let source_fingerprint = imported_source_kind(kind)
        .then(|| fingerprint_file(project_root.join(&path)).ok())
        .flatten();
    let record = AssetRecord {
        id: shared::AssetId::new(),
        path,
        label: label.into(),
        kind,
        source_fingerprint,
        import_settings: shared::AssetImportSettings::for_kind(kind),
        dependencies: Vec::new(),
    };
    registry.records.push(record.clone());
    registry.index_dirty = true;
    if let Err(error) = save_registry_at(registry, project_root) {
        registry.records.pop();
        return Err(error);
    }
    registry.index_dirty = false;
    Ok(record)
}

pub fn update_import_settings(
    registry: &mut AssetRegistry,
    path: &str,
    settings: ImportSettings,
) -> bool {
    let Some(record) = registry
        .records
        .iter_mut()
        .find(|record| record.path == path)
    else {
        return false;
    };
    if record.import_settings.model() == Some(&settings) {
        return false;
    }
    record.import_settings = settings.into();
    registry.index_dirty = true;
    true
}

pub fn registry_index_path() -> &'static str {
    "assets/.index/registry.ron"
}

pub fn save_registry(registry: &AssetRegistry) -> Result<(), String> {
    save_registry_at(registry, Path::new("."))
}

pub fn save_registry_at(registry: &AssetRegistry, project_root: &Path) -> Result<(), String> {
    content_pipeline::publish_content_documents(project_root, &registry.document())
}

pub fn load_registry() -> AssetRegistry {
    let path = registry_index_path();
    match std::fs::read_to_string(path) {
        Ok(text) => match parse_asset_registry(&text) {
            Ok(loaded) => AssetRegistry {
                schema_version: loaded.document.schema_version,
                defaults: loaded.document.defaults,
                records: loaded.document.records,
                index_dirty: false,
                migration_required: loaded.migration_required,
            },
            Err(error) => {
                warn!("Asset registry load failed: {error}");
                AssetRegistry::default()
            }
        },
        Err(_) => AssetRegistry::default(),
    }
}

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

    #[test]
    fn imported_source_policy_covers_model_texture_and_audio_only() {
        for kind in [AssetKind::Model, AssetKind::Texture, AssetKind::AudioClip] {
            assert!(imported_source_kind(kind), "missing imported kind {kind:?}");
        }
        for kind in [
            AssetKind::Material,
            AssetKind::Level,
            AssetKind::Prefab,
            AssetKind::PostProcessEffect,
        ] {
            assert!(
                !imported_source_kind(kind),
                "authored kind {kind:?} was hashed"
            );
        }
    }

    #[test]
    fn shared_classification_preserves_schema_classified_material_instance_kind() {
        let root = std::env::temp_dir().join(format!(
            "blacksite-browser-material-instance-{}",
            uuid::Uuid::new_v4()
        ));
        std::fs::create_dir_all(&root).unwrap();
        let path = root.join("user_named_variant.ron");
        let instance = shared::MaterialInstanceAsset {
            schema_version: shared::MATERIAL_INSTANCE_SCHEMA_VERSION,
            label: "User Named Variant".into(),
            base: shared::MaterialRef::new(shared::EditorAssetRef::new(
                "base-id",
                "material:source",
                "Base",
            )),
            overrides: shared::MaterialInputSet::default(),
        };
        std::fs::write(&path, ron::ser::to_string(&instance).unwrap()).unwrap();
        assert_eq!(
            content_pipeline::classify_asset_file(&path),
            AssetKind::MaterialInstance
        );
        std::fs::remove_dir_all(root).unwrap();
    }

    #[test]
    fn legacy_registry_record_loads_without_forcing_a_new_identity() {
        let source = r#"[(id:("00000000-0000-0000-0000-000000000000"),path:"assets/textures/legacy.png",label:"Legacy",kind_tag:"Texture")]"#;
        let loaded = parse_asset_registry(source).unwrap();
        let record = &loaded.document.records[0];

        assert_eq!(record.id, AssetId(Uuid::nil()));
        assert!(record.source_fingerprint.is_none());
        assert_eq!(
            record.import_settings,
            shared::AssetImportSettings::for_kind(AssetKind::Texture)
        );
        assert_eq!(record.kind, AssetKind::Texture);
        assert!(loaded.migration_required);
    }
}
