use super::*;

pub fn request_contextual_save(world: &mut World) {
    let active_dirty = world
        .resource::<AuthoredAssetDocumentStore>()
        .active
        .as_ref()
        .and_then(|key| {
            world
                .resource::<AuthoredAssetDocumentStore>()
                .documents
                .get(key)
        })
        .is_some_and(AuthoredAssetDocument::dirty);
    if active_dirty {
        world
            .resource_mut::<AuthoredAssetDocumentStore>()
            .request_save(AssetSaveRequest::Active);
    } else if world
        .get_resource::<settings::ProjectSettingsIo>()
        .is_some_and(|io| io.dirty)
    {
        match crate::settings_ui::save_project_settings_if_dirty(world) {
            Ok(_) => world
                .resource_mut::<SceneIo>()
                .set_status("Saved project settings"),
            Err(error) => world
                .resource_mut::<SceneIo>()
                .set_status(format!("Save project settings failed: {error}")),
        }
    } else {
        world.resource_mut::<SceneIo>().request = Some(SceneIoRequest::Save);
    }
}

pub fn request_save_all(world: &mut World) {
    if let Err(error) = crate::settings_ui::save_project_settings_if_dirty(world) {
        world
            .resource_mut::<SceneIo>()
            .set_status(format!("Save All project settings failed: {error}"));
        return;
    }
    world
        .resource_mut::<AuthoredAssetDocumentStore>()
        .request_save(AssetSaveRequest::All);
    crate::scene_io::request_scene_save_all(world);
}

pub fn save_all_authored_assets(world: &mut World) -> Result<usize, String> {
    if !world.contains_resource::<AuthoredAssetDocumentStore>() {
        return Ok(0);
    }
    save_documents(world, AssetSaveRequest::All)
}

pub fn discard_all_authored_assets(world: &mut World) {
    if !world.contains_resource::<AuthoredAssetDocumentStore>() {
        return;
    }
    let keys = world
        .resource::<AuthoredAssetDocumentStore>()
        .documents
        .keys()
        .cloned()
        .collect::<Vec<_>>();
    for key in &keys {
        discard_document_recovery(world, key);
    }
    world
        .resource_mut::<AuthoredAssetDocumentStore>()
        .discard_all();
    let clean_materials = world
        .resource::<AuthoredAssetDocumentStore>()
        .documents
        .iter()
        .filter_map(|(key, document)| match &document.clean {
            AuthoredAssetValue::Material(asset) => Some((
                key.asset_id.clone(),
                document.revision,
                LiveMaterialDocument::Material(asset.clone()),
            )),
            AuthoredAssetValue::MaterialInstance(instance) => Some((
                key.asset_id.clone(),
                document.revision,
                LiveMaterialDocument::Instance(instance.clone()),
            )),
            _ => None,
        })
        .collect::<Vec<_>>();
    if let Some(mut overlay) =
        world.get_resource_mut::<blacksite_surface::LiveMaterialDocumentOverlay>()
    {
        for (asset_id, revision, document) in clean_materials {
            match document {
                LiveMaterialDocument::Material(asset) => {
                    overlay.update_material(asset_id, revision, asset);
                }
                LiveMaterialDocument::Instance(instance) => {
                    overlay.update_instance(asset_id, revision, instance);
                }
            }
        }
    }
    if let Some(clean) = world
        .resource::<AuthoredAssetDocumentStore>()
        .registry_clean
        .clone()
    {
        apply_registry_to_world(world, &clean);
    }
}

pub(super) fn drive_asset_save_requests(world: &mut World) {
    let request = world
        .resource_mut::<AuthoredAssetDocumentStore>()
        .pending_save
        .take();
    let Some(request) = request else {
        return;
    };
    match save_documents(world, request) {
        Ok(0) => world
            .resource_mut::<SceneIo>()
            .set_status("No dirty asset documents to save"),
        Ok(count) => world
            .resource_mut::<SceneIo>()
            .set_status(format!("Saved {count} asset document(s)")),
        Err(error) => world.resource_mut::<SceneIo>().set_status(error),
    }
}

pub(super) fn save_documents(
    world: &mut World,
    request: AssetSaveRequest,
) -> Result<usize, String> {
    let keys = {
        let store = world.resource::<AuthoredAssetDocumentStore>();
        match request {
            AssetSaveRequest::Active => store
                .active
                .iter()
                .filter(|key| {
                    store
                        .documents
                        .get(*key)
                        .is_some_and(AuthoredAssetDocument::dirty)
                })
                .cloned()
                .collect::<Vec<_>>(),
            AssetSaveRequest::All => store
                .documents
                .iter()
                .filter(|(_, document)| document.dirty())
                .map(|(key, _)| key.clone())
                .collect::<Vec<_>>(),
        }
    };
    if keys.is_empty() {
        return Ok(0);
    }

    let mut registry_keys = Vec::new();
    let mut saved = 0;
    for key in keys {
        if matches!(
            key.kind,
            AuthoredAssetDocumentKind::ModelImportSettings
                | AuthoredAssetDocumentKind::TextureImportSettings
                | AuthoredAssetDocumentKind::ProjectContentDefaults
        ) {
            registry_keys.push(key);
            continue;
        }
        save_file_document(world, &key)?;
        saved += 1;
    }
    if !registry_keys.is_empty() {
        saved += save_registry_documents(world, &registry_keys)?;
    }
    Ok(saved)
}

pub(super) fn save_file_document(
    world: &mut World,
    key: &AuthoredAssetDocumentKey,
) -> Result<(), String> {
    let (path, clean, value, snapshot, revision) = {
        let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
        let document = store
            .documents
            .get_mut(key)
            .ok_or_else(|| "authored asset document disappeared before save".to_string())?;
        document.state = AuthoredDocumentState::Saving;
        (
            document.path.clone(),
            document.clean.clone(),
            document.current.clone(),
            document.disk_snapshot.clone(),
            document.revision,
        )
    };
    let processing_impact = derived_processing_impact(&clean, &value);
    let (bytes, intent) = match &value {
        AuthoredAssetValue::Material(asset) => (
            ron::ser::to_string_pretty(asset, ron::ser::PrettyConfig::default())
                .map_err(|error| format!("could not serialize Material {path}: {error}"))?
                .into_bytes(),
            FileWriteIntent::Material,
        ),
        AuthoredAssetValue::MaterialInstance(asset) => (
            ron::ser::to_string_pretty(asset, ron::ser::PrettyConfig::default())
                .map_err(|error| format!("could not serialize Material Instance {path}: {error}"))?
                .into_bytes(),
            FileWriteIntent::MaterialInstance,
        ),
        _ => return Err("registry-backed document used the file-document save path".into()),
    };
    crate::assets::suppress_content_watch_path(world, Path::new(&path));
    match publish_authored_file(world, Path::new(&path), &bytes, &snapshot, intent) {
        Ok(disk_snapshot) => {
            let label = match &value {
                AuthoredAssetValue::Material(asset) => asset.label.as_str(),
                AuthoredAssetValue::MaterialInstance(asset) => asset.label.as_str(),
                _ => "",
            }
            .to_string();
            {
                let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
                let document = store.documents.get_mut(key).expect("saved document exists");
                document.clean = value;
                document.disk_snapshot = disk_snapshot;
                document.state = AuthoredDocumentState::Clean;
                document.processing = if processing_impact == DerivedProcessingImpact::None {
                    DerivedProcessingState::Idle
                } else {
                    DerivedProcessingState::Queued
                };
                document.error = None;
                document.changed_at = None;
            }
            if let Some(asset) = world
                .resource_mut::<EditorAssets>()
                .assets
                .iter_mut()
                .find(|asset| asset.path.as_deref() == Some(path.as_str()))
            {
                asset.label = label;
            }
            discard_document_recovery(world, key);
            if processing_impact != DerivedProcessingImpact::None {
                enqueue_processing(world, key, revision);
            }
            Ok(())
        }
        Err(error) => {
            let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
            let document = store
                .documents
                .get_mut(key)
                .expect("failed document exists");
            document.state = if error.contains("changed outside") || error.contains("read-only") {
                AuthoredDocumentState::ExternalConflict
            } else {
                AuthoredDocumentState::SaveFailed
            };
            document.error = Some(error.clone());
            Err(error)
        }
    }
}

pub(super) fn save_registry_documents(
    world: &mut World,
    keys: &[AuthoredAssetDocumentKey],
) -> Result<usize, String> {
    let root = project_root(world);
    let path = root.join(content_pipeline::REGISTRY_PATH);
    let (mut document, snapshot) = {
        let store = world.resource::<AuthoredAssetDocumentStore>();
        (
            store
                .registry_clean
                .clone()
                .unwrap_or_else(|| world.resource::<AssetRegistry>().document()),
            store
                .registry_snapshot
                .clone()
                .unwrap_or_else(FileSnapshot::missing),
        )
    };
    for key in keys {
        let store = world.resource::<AuthoredAssetDocumentStore>();
        let authored = store
            .documents
            .get(key)
            .ok_or_else(|| "registry-backed document disappeared before save".to_string())?;
        apply_registry_value(&mut document, key, &authored.current)?;
    }
    let bytes = content_pipeline::serialize_registry(&document)?;
    crate::assets::suppress_content_watch_path(world, &path);
    let intent = FileWriteIntent::AssetRegistry;
    let disk_snapshot = match publish_authored_file(world, &path, &bytes, &snapshot, intent) {
        Ok(snapshot) => snapshot,
        Err(error) => {
            let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
            for key in keys {
                if let Some(authored) = store.documents.get_mut(key) {
                    authored.state = AuthoredDocumentState::ExternalConflict;
                    authored.error = Some(error.clone());
                }
            }
            return Err(error);
        }
    };
    let impacts = {
        let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
        let impacts = keys
            .iter()
            .filter_map(|key| {
                store.documents.get(key).map(|document| {
                    (
                        key.clone(),
                        derived_processing_impact(&document.clean, &document.current),
                    )
                })
            })
            .collect::<HashMap<_, _>>();
        store.registry_clean = Some(document.clone());
        store.registry_snapshot = Some(disk_snapshot.clone());
        for key in keys {
            if let Some(authored) = store.documents.get_mut(key) {
                authored.clean = authored.current.clone();
                authored.disk_snapshot = disk_snapshot.clone();
                authored.state = AuthoredDocumentState::Clean;
                authored.processing = if impacts.get(key).copied().unwrap_or_default()
                    == DerivedProcessingImpact::None
                {
                    DerivedProcessingState::Idle
                } else {
                    DerivedProcessingState::Queued
                };
                authored.error = None;
                authored.changed_at = None;
            }
        }
        impacts
    };
    apply_registry_to_world(world, &document);
    reapply_unsaved_registry_overlays(world);
    for key in keys {
        discard_document_recovery(world, key);
        let revision = world
            .resource::<AuthoredAssetDocumentStore>()
            .documents
            .get(key)
            .map(|document| document.revision)
            .unwrap_or_default();
        if impacts.get(key).copied().unwrap_or_default() != DerivedProcessingImpact::None {
            enqueue_processing(world, key, revision);
        }
    }
    Ok(keys.len())
}

pub(super) fn derived_processing_impact(
    clean: &AuthoredAssetValue,
    current: &AuthoredAssetValue,
) -> DerivedProcessingImpact {
    match (clean, current) {
        (AuthoredAssetValue::Material(clean), AuthoredAssetValue::Material(current)) => {
            material_impact(clean, current)
        }
        (
            AuthoredAssetValue::MaterialInstance(clean),
            AuthoredAssetValue::MaterialInstance(current),
        ) => instance_impact(clean, current),
        (
            AuthoredAssetValue::TextureImportSettings(_),
            AuthoredAssetValue::TextureImportSettings(_),
        ) => DerivedProcessingImpact::TextureAndDependents,
        (
            AuthoredAssetValue::ModelImportSettings(_),
            AuthoredAssetValue::ModelImportSettings(_),
        ) => DerivedProcessingImpact::ModelArtifacts,
        (
            AuthoredAssetValue::ProjectContentDefaults(_),
            AuthoredAssetValue::ProjectContentDefaults(_),
        ) => DerivedProcessingImpact::CatalogDefaults,
        _ => DerivedProcessingImpact::None,
    }
}

pub(super) fn apply_registry_value(
    document: &mut AssetRegistryDocument,
    key: &AuthoredAssetDocumentKey,
    value: &AuthoredAssetValue,
) -> Result<(), String> {
    match value {
        AuthoredAssetValue::ModelImportSettings(settings) => {
            let record = document
                .records
                .iter_mut()
                .find(|record| record.id.as_string() == key.asset_id)
                .ok_or_else(|| format!("registry is missing model {}", key.asset_id))?;
            record.import_settings = settings.clone().into();
        }
        AuthoredAssetValue::TextureImportSettings(settings) => {
            let record = document
                .records
                .iter_mut()
                .find(|record| record.id.as_string() == key.asset_id)
                .ok_or_else(|| format!("registry is missing Texture {}", key.asset_id))?;
            record.import_settings = settings.clone().into();
        }
        AuthoredAssetValue::ProjectContentDefaults(defaults) => {
            document.defaults = defaults.clone();
        }
        _ => return Err("file-backed document used the registry save path".into()),
    }
    Ok(())
}

pub(super) fn apply_registry_to_world(world: &mut World, document: &AssetRegistryDocument) {
    {
        let mut registry = world.resource_mut::<AssetRegistry>();
        registry.schema_version = document.schema_version;
        registry.defaults = document.defaults.clone();
        registry.records = document.records.clone();
        registry.index_dirty = false;
        registry.migration_required = false;
    }
    *world.resource_mut::<ProjectContentDefaults>() = document.defaults.clone();
}

pub(super) fn reapply_unsaved_registry_overlays(world: &mut World) {
    let overlays = world
        .resource::<AuthoredAssetDocumentStore>()
        .documents
        .iter()
        .filter(|(key, document)| {
            document.dirty()
                && matches!(
                    key.kind,
                    AuthoredAssetDocumentKind::ModelImportSettings
                        | AuthoredAssetDocumentKind::TextureImportSettings
                        | AuthoredAssetDocumentKind::ProjectContentDefaults
                )
        })
        .map(|(key, document)| (key.clone(), document.current.clone()))
        .collect::<Vec<_>>();
    let mut effective = world.resource::<AssetRegistry>().document();
    for (key, value) in overlays {
        let _ = apply_registry_value(&mut effective, &key, &value);
    }
    apply_registry_to_world(world, &effective);
}
