use super::*;

pub(super) fn enqueue_processing(world: &mut World, key: &AuthoredAssetDocumentKey, revision: u64) {
    let root = project_root(world);
    let registry = world
        .resource::<AuthoredAssetDocumentStore>()
        .registry_clean
        .clone()
        .unwrap_or_else(|| world.resource::<AssetRegistry>().document());
    let request = match key.kind {
        AuthoredAssetDocumentKind::Material | AuthoredAssetDocumentKind::MaterialInstance => {
            let Some(record) = registry
                .records
                .iter()
                .find(|record| record.id.as_string() == key.asset_id)
                .cloned()
            else {
                return;
            };
            ProcessingRequest::Material {
                key: key.clone(),
                revision,
                root,
                record,
                registry,
            }
        }
        AuthoredAssetDocumentKind::TextureImportSettings => {
            let Some(record) = registry
                .records
                .iter()
                .find(|record| record.id.as_string() == key.asset_id)
                .cloned()
            else {
                return;
            };
            ProcessingRequest::Texture {
                key: key.clone(),
                revision,
                root,
                record,
                registry,
            }
        }
        AuthoredAssetDocumentKind::ModelImportSettings => {
            let Some(record) = registry
                .records
                .iter()
                .find(|record| record.id.as_string() == key.asset_id)
                .cloned()
            else {
                return;
            };
            ProcessingRequest::Model {
                key: key.clone(),
                revision,
                root,
                record,
                registry,
            }
        }
        AuthoredAssetDocumentKind::ProjectContentDefaults => ProcessingRequest::Defaults {
            key: key.clone(),
            revision,
            root,
            registry,
        },
    };
    let mut queue = world.resource_mut::<ContentProcessingQueue>();
    queue.pending.retain(|pending| pending.key() != key);
    queue.pending.push_back(request);
}

pub(super) fn drive_content_processing(world: &mut World) {
    let completed = {
        let mut queue = world.resource_mut::<ContentProcessingQueue>();
        if queue
            .active
            .as_ref()
            .is_some_and(|job| job.handle.is_finished())
        {
            queue.active.take()
        } else {
            None
        }
    };
    if let Some(job) = completed {
        let result = job
            .handle
            .join()
            .unwrap_or_else(|_| Err("content processing worker panicked".into()));
        finish_processing(world, job.key, job.revision, result);
    }

    let request = {
        let mut queue = world.resource_mut::<ContentProcessingQueue>();
        if queue.active.is_none() {
            queue.pending.pop_front()
        } else {
            None
        }
    };
    let Some(request) = request else {
        return;
    };
    let key = request.key().clone();
    let revision = request.revision();
    if let Some(document) = world
        .resource_mut::<AuthoredAssetDocumentStore>()
        .documents
        .get_mut(&key)
    {
        document.processing = DerivedProcessingState::Processing;
    }
    let handle = std::thread::spawn(move || process_request(request));
    world.resource_mut::<ContentProcessingQueue>().active = Some(ActiveProcessingJob {
        key,
        revision,
        handle,
    });
}

pub(super) fn process_request(request: ProcessingRequest) -> Result<ProcessingOutput, String> {
    match request {
        ProcessingRequest::Material {
            root,
            record,
            registry,
            ..
        } => {
            let mut runtime = runtime_catalog_with_previous_artifacts(&root, &registry);
            let mut affected = vec![record.clone()];
            if record.kind == AssetKind::Material {
                affected.extend(content_pipeline::direct_material_instance_dependents(
                    &root, &registry, &record,
                ));
            }
            for affected_record in affected {
                if let Some(plan) =
                    content_pipeline::plan_material_artifact(&root, &affected_record, &registry)?
                {
                    content_pipeline::publish_material_artifact(&plan)?;
                    content_pipeline::apply_material_plan_to_catalog(
                        &mut runtime,
                        &affected_record.id,
                        &plan,
                    )?;
                }
            }
            Ok(ProcessingOutput::RuntimeCatalog(runtime))
        }
        ProcessingRequest::Texture {
            root,
            record,
            registry,
            ..
        } => {
            let mut runtime = runtime_catalog_with_previous_artifacts(&root, &registry);
            let plan = content_pipeline::plan_texture_artifact(&root, &record)?;
            content_pipeline::publish_texture_artifact(&plan)?;
            content_pipeline::apply_texture_plan_to_catalog(&mut runtime, &record.id, &plan)?;
            for material in
                content_pipeline::material_dependents_of_texture(&root, &registry, &record)
            {
                if let Some(plan) =
                    content_pipeline::plan_material_artifact(&root, &material, &registry)?
                {
                    content_pipeline::publish_material_artifact(&plan)?;
                    content_pipeline::apply_material_plan_to_catalog(
                        &mut runtime,
                        &material.id,
                        &plan,
                    )?;
                }
            }
            Ok(ProcessingOutput::RuntimeCatalog(runtime))
        }
        ProcessingRequest::Model {
            root,
            record,
            mut registry,
            ..
        } => {
            let plan = content_pipeline::plan_model_artifacts_at(&root, &record)?;
            content_pipeline::publish_model_artifacts(&plan)?;
            if let Some(current) = registry
                .records
                .iter_mut()
                .find(|entry| entry.id == record.id)
            {
                *current = plan.record.clone();
            }
            let runtime = runtime_catalog_with_previous_artifacts(&root, &registry);
            Ok(ProcessingOutput::Model { registry, runtime })
        }
        ProcessingRequest::Defaults { root, registry, .. } => Ok(ProcessingOutput::RuntimeCatalog(
            runtime_catalog_with_previous_artifacts(&root, &registry),
        )),
    }
}

pub(super) fn runtime_catalog_with_previous_artifacts(
    root: &Path,
    registry: &AssetRegistryDocument,
) -> RuntimeContentCatalog {
    let mut runtime = RuntimeContentCatalog::from(registry);
    let previous = fs::read_to_string(root.join(content_pipeline::RUNTIME_CATALOG_PATH))
        .ok()
        .and_then(|source| ron::from_str::<RuntimeContentCatalog>(&source).ok());
    if let Some(previous) = previous {
        for record in &mut runtime.records {
            if let Some(old) = previous.records.iter().find(|old| old.id == record.id) {
                if old.texture.is_some() {
                    record.texture.clone_from(&old.texture);
                }
                if old.material.is_some() {
                    record.material.clone_from(&old.material);
                }
            }
        }
    }
    runtime
}

pub(super) fn finish_processing(
    world: &mut World,
    key: AuthoredAssetDocumentKey,
    revision: u64,
    result: Result<ProcessingOutput, String>,
) {
    let current_revision = world
        .resource::<AuthoredAssetDocumentStore>()
        .documents
        .get(&key)
        .map(|document| document.revision);
    if current_revision != Some(revision) {
        return;
    }
    match result {
        Ok(output) => {
            let root = project_root(world);
            let (runtime, processed_registry) = match output {
                ProcessingOutput::RuntimeCatalog(runtime) => (runtime, None),
                ProcessingOutput::Model { registry, runtime } => (runtime, Some(registry)),
            };
            let catalog_path = root.join(content_pipeline::RUNTIME_CATALOG_PATH);
            crate::assets::suppress_content_watch_path(world, &catalog_path);
            if processed_registry.is_some() {
                crate::assets::suppress_content_watch_path(
                    world,
                    &root.join(content_pipeline::REGISTRY_PATH),
                );
            }
            let publication = (|| {
                if let Some(registry) = processed_registry.as_ref() {
                    let registry_path = root.join(content_pipeline::REGISTRY_PATH);
                    let bytes = content_pipeline::serialize_registry(registry)?;
                    content_pipeline::write_if_changed(&registry_path, &bytes)?;
                }
                let bytes = content_pipeline::serialize_runtime_catalog(&runtime)?;
                content_pipeline::write_if_changed(&catalog_path, &bytes).map(|_| ())
            })();
            if publication.is_ok() {
                if let Some(registry) = processed_registry.as_ref() {
                    let registry_path = root.join(content_pipeline::REGISTRY_PATH);
                    let snapshot = FileSnapshot::capture(&registry_path)
                        .unwrap_or_else(|_| FileSnapshot::missing());
                    {
                        let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
                        store.registry_clean = Some(registry.clone());
                        store.registry_snapshot = Some(snapshot);
                    }
                    apply_registry_to_world(world, registry);
                    reapply_unsaved_registry_overlays(world);
                }
            }
            let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
            let document = store
                .documents
                .get_mut(&key)
                .expect("processing document exists");
            match publication {
                Ok(()) => {
                    document.processing = DerivedProcessingState::Idle;
                    document.error = None;
                }
                Err(error) => {
                    document.processing = DerivedProcessingState::Failed;
                    document.error = Some(error);
                }
            }
        }
        Err(error) => {
            let mut store = world.resource_mut::<AuthoredAssetDocumentStore>();
            if let Some(document) = store.documents.get_mut(&key) {
                document.processing = DerivedProcessingState::Failed;
                document.error = Some(error.clone());
            }
            world
                .resource_mut::<SceneIo>()
                .set_status(format!("Derived asset processing failed: {error}"));
        }
    }
}
