use super::*;

pub(super) fn static_mesh_asset_ref_candidates(
    world: &mut World,
    kind: AssetRefCandidateKind,
) -> Vec<AssetRefCandidate> {
    let mesh_thumbnails = mesh_thumbnail_texture_by_asset_ref(world);
    let Some(registry) = world.get_resource::<AssetRegistry>() else {
        return Vec::new();
    };
    let catalog = world.get_resource::<EditorAssets>();
    let mut seen = HashSet::new();
    let mut candidates = Vec::new();

    for record in &registry.records {
        let Some(settings) = record.import_settings.model() else {
            continue;
        };
        let Some(manifest_path) = settings.static_mesh_manifest_path.as_deref() else {
            continue;
        };
        let Ok(manifest) = load_static_mesh_manifest(manifest_path) else {
            continue;
        };
        let selection = AssetSelection::File(manifest.source.path.clone());
        let folder_path = catalog
            .and_then(|assets| {
                assets
                    .assets
                    .iter()
                    .find(|asset| asset.path.as_deref() == Some(manifest.source.path.as_str()))
                    .map(|asset| asset.folder_path.clone())
            })
            .unwrap_or_else(|| fallback_asset_folder(&manifest.source.path));

        for part in &manifest.parts {
            let source_material_ref = source_material_ref_for_part(&manifest.asset_id, part);
            let candidate = match kind {
                AssetRefCandidateKind::Mesh => {
                    let sub_asset_id = if part.id.trim().is_empty() {
                        part_id_from_label(&part.mesh_label)
                    } else {
                        part.id.clone()
                    };
                    AssetRefCandidate {
                        reference: EditorAssetRef::new(
                            manifest.asset_id.clone(),
                            sub_asset_id.clone(),
                            part.name.clone(),
                        ),
                        label: part.name.clone(),
                        detail: format!("{} | {}", manifest.label, manifest.source.path),
                        selection: selection.clone(),
                        folder_path: folder_path.clone(),
                        texture_id: mesh_thumbnails
                            .get(&(manifest.asset_id.clone(), sub_asset_id.clone()))
                            .copied(),
                    }
                }
                AssetRefCandidateKind::Material => {
                    let Some(material_ref) = source_material_ref else {
                        continue;
                    };
                    AssetRefCandidate {
                        reference: material_ref,
                        label: part.material_slot_name.clone(),
                        detail: format!("{} | {}", manifest.label, manifest.source.path),
                        selection: selection.clone(),
                        folder_path: folder_path.clone(),
                        texture_id: None,
                    }
                }
                AssetRefCandidateKind::Texture => continue,
            };

            let key = (
                candidate.reference.asset_id.clone(),
                candidate.reference.sub_asset_id.clone(),
            );
            if seen.insert(key) {
                candidates.push(candidate);
            }
        }
    }

    candidates.sort_by(|a, b| a.detail.cmp(&b.detail).then(a.label.cmp(&b.label)));
    candidates
}

pub(super) fn texture_asset_candidates(world: &World) -> Vec<TextureAssetCandidate> {
    let Some(catalog) = world.get_resource::<EditorAssets>() else {
        return Vec::new();
    };
    let snapshot = world
        .get_resource::<AssetThumbnailCache>()
        .map(AssetThumbnailCache::snapshot);
    let mut candidates: Vec<_> = catalog
        .assets
        .iter()
        .filter(|asset| matches!(asset.kind, EditorAssetKind::Texture))
        .filter_map(|asset| {
            let path = asset.path.clone()?;
            let texture_id = snapshot
                .as_ref()
                .and_then(|snapshot| snapshot.texture_for(asset));
            Some(TextureAssetCandidate {
                label: asset.label.clone(),
                path: path.clone(),
                folder_path: asset.folder_path.clone(),
                selection: AssetSelection::File(path),
                texture_id,
            })
        })
        .collect();
    candidates.sort_by(|a, b| a.label.cmp(&b.label).then(a.path.cmp(&b.path)));
    candidates
}

pub(super) fn texture_asset_ref_candidates(world: &World) -> Vec<AssetRefCandidate> {
    let Some(catalog) = world.get_resource::<EditorAssets>() else {
        return Vec::new();
    };
    let Some(registry) = world.get_resource::<AssetRegistry>() else {
        return Vec::new();
    };
    let snapshot = world
        .get_resource::<AssetThumbnailCache>()
        .map(AssetThumbnailCache::snapshot);
    let mut candidates: Vec<_> = catalog
        .assets
        .iter()
        .filter(|asset| matches!(asset.kind, EditorAssetKind::Texture))
        .filter_map(|asset| {
            let path = asset.path.as_deref()?;
            let record = find_asset_by_path(registry, path)?;
            Some(AssetRefCandidate {
                reference: EditorAssetRef::new(
                    record.id.as_string(),
                    "texture:source",
                    asset.label.clone(),
                )
                .with_source_path(path),
                label: asset.label.clone(),
                detail: path.to_string(),
                selection: AssetSelection::File(path.to_string()),
                folder_path: asset.folder_path.clone(),
                texture_id: snapshot
                    .as_ref()
                    .and_then(|snapshot| snapshot.texture_for(asset)),
            })
        })
        .collect();
    candidates.sort_by(|a, b| a.label.cmp(&b.label).then(a.detail.cmp(&b.detail)));
    candidates
}

pub(super) fn brush_face_material_ref_candidates(world: &mut World) -> Vec<AssetRefCandidate> {
    let mut candidates = static_mesh_asset_ref_candidates(world, AssetRefCandidateKind::Material);
    let material_assets = world
        .get_resource::<EditorAssets>()
        .map(|catalog| {
            catalog
                .assets
                .iter()
                .filter(|asset| matches!(asset.kind, EditorAssetKind::Material))
                .cloned()
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    prefetch_asset_thumbnails(world, &material_assets);
    let thumbnail_snapshot = world
        .get_resource::<AssetThumbnailCache>()
        .map(AssetThumbnailCache::snapshot);
    let Some(registry) = world.get_resource::<AssetRegistry>() else {
        return candidates;
    };
    let mut seen: HashSet<_> = candidates
        .iter()
        .map(|candidate| {
            (
                candidate.reference.asset_id.clone(),
                candidate.reference.sub_asset_id.clone(),
            )
        })
        .collect();
    for asset in &material_assets {
        let Some(path) = asset.path.as_deref() else {
            continue;
        };
        let Some(record) = find_asset_by_path(registry, path) else {
            continue;
        };
        let sub_asset_id = if shared::MaterialInstanceAsset::load_from_path(path).is_ok() {
            "material:instance"
        } else {
            "material:source"
        };
        let reference = EditorAssetRef::new(record.id.as_string(), sub_asset_id, &asset.label)
            .with_source_path(path);
        let key = (reference.asset_id.clone(), reference.sub_asset_id.clone());
        if !seen.insert(key) {
            continue;
        }
        candidates.push(AssetRefCandidate {
            reference,
            label: asset.label.clone(),
            detail: path.to_string(),
            selection: AssetSelection::File(path.to_string()),
            folder_path: asset.folder_path.clone(),
            texture_id: thumbnail_snapshot
                .as_ref()
                .and_then(|snapshot| snapshot.texture_for(asset)),
        });
    }
    candidates.sort_by(|a, b| a.label.cmp(&b.label).then(a.detail.cmp(&b.detail)));
    candidates
}

pub(super) fn asset_ref_candidate_from_selection(
    world: &World,
    selection: &AssetSelection,
    kind: AssetRefCandidateKind,
) -> Option<AssetRefCandidate> {
    let registry = world.get_resource::<AssetRegistry>()?;
    match (kind, selection) {
        (AssetRefCandidateKind::Material, AssetSelection::File(path)) => {
            let asset = world
                .get_resource::<EditorAssets>()?
                .assets
                .iter()
                .find(|asset| {
                    matches!(asset.kind, EditorAssetKind::Material)
                        && asset.path.as_deref() == Some(path.as_str())
                })?;
            let record = find_asset_by_path(registry, path)?;
            let sub_asset_id = if shared::MaterialInstanceAsset::load_from_path(path).is_ok() {
                "material:instance"
            } else {
                "material:source"
            };
            Some(AssetRefCandidate {
                reference: EditorAssetRef::new(
                    record.id.as_string(),
                    sub_asset_id,
                    asset.label.clone(),
                )
                .with_source_path(path),
                label: asset.label.clone(),
                detail: path.clone(),
                selection: selection.clone(),
                folder_path: asset.folder_path.clone(),
                texture_id: None,
            })
        }
        (AssetRefCandidateKind::Texture, AssetSelection::File(path)) => {
            let asset = world
                .get_resource::<EditorAssets>()?
                .assets
                .iter()
                .find(|asset| {
                    matches!(asset.kind, EditorAssetKind::Texture)
                        && asset.path.as_deref() == Some(path.as_str())
                })?;
            let record = find_asset_by_path(registry, path)?;
            Some(AssetRefCandidate {
                reference: EditorAssetRef::new(
                    record.id.as_string(),
                    "texture:source",
                    asset.label.clone(),
                )
                .with_source_path(path),
                label: asset.label.clone(),
                detail: path.clone(),
                selection: selection.clone(),
                folder_path: asset.folder_path.clone(),
                texture_id: None,
            })
        }
        (
            AssetRefCandidateKind::Material,
            AssetSelection::SubAsset {
                parent_path,
                sub_asset_id,
                label,
                kind: AssetSubAssetKind::Material,
                ..
            },
        ) => {
            let record = find_asset_by_path(registry, parent_path)?;
            Some(AssetRefCandidate {
                reference: EditorAssetRef::new(
                    record.id.as_string(),
                    sub_asset_id.clone(),
                    label.clone(),
                ),
                label: label.clone(),
                detail: parent_path.clone(),
                selection: selection.clone(),
                folder_path: fallback_asset_folder(parent_path),
                texture_id: None,
            })
        }
        (
            AssetRefCandidateKind::Texture,
            AssetSelection::SubAsset {
                parent_path,
                sub_asset_id,
                label,
                kind: AssetSubAssetKind::Texture,
                source_path,
            },
        ) => {
            let record = find_asset_by_path(registry, parent_path)?;
            Some(AssetRefCandidate {
                reference: EditorAssetRef::new(
                    record.id.as_string(),
                    sub_asset_id.clone(),
                    label.clone(),
                )
                .with_source_path(source_path.clone().unwrap_or_else(|| parent_path.clone())),
                label: label.clone(),
                detail: source_path.clone().unwrap_or_else(|| parent_path.clone()),
                selection: selection.clone(),
                folder_path: fallback_asset_folder(parent_path),
                texture_id: None,
            })
        }
        _ => None,
    }
}

pub(super) fn request_texture_asset_thumbnails(world: &mut World) {
    let requests: Vec<_> = world
        .get_resource::<EditorAssets>()
        .map(|assets| {
            assets
                .assets
                .iter()
                .filter(|asset| matches!(asset.kind, EditorAssetKind::Texture))
                .filter_map(|asset| Some((asset_cache_key(asset), asset.path.clone()?)))
                .collect()
        })
        .unwrap_or_default();
    if requests.is_empty() || world.get_resource::<AssetThumbnailCache>().is_none() {
        return;
    }
    let asset_server = world.resource::<AssetServer>().clone();
    world.resource_scope(|_world, mut cache: Mut<AssetThumbnailCache>| {
        for (key, path) in requests {
            cache.request_texture(key, path, &asset_server);
        }
    });
}

pub(super) fn texture_path_from_selection(
    world: &World,
    selection: &AssetSelection,
) -> Option<TextureAssetCandidate> {
    match selection {
        AssetSelection::File(path) => {
            let asset = world
                .get_resource::<EditorAssets>()?
                .assets
                .iter()
                .find(|asset| {
                    matches!(asset.kind, EditorAssetKind::Texture)
                        && asset.path.as_deref() == Some(path.as_str())
                })?;
            Some(TextureAssetCandidate {
                label: asset.label.clone(),
                path: path.clone(),
                folder_path: asset.folder_path.clone(),
                selection: selection.clone(),
                texture_id: None,
            })
        }
        AssetSelection::SubAsset {
            label,
            kind: AssetSubAssetKind::Texture,
            source_path: Some(path),
            ..
        } => {
            if let Some(asset) = world.get_resource::<EditorAssets>().and_then(|assets| {
                assets
                    .assets
                    .iter()
                    .find(|asset| asset.path.as_deref() == Some(path.as_str()))
            }) {
                return Some(TextureAssetCandidate {
                    label: asset.label.clone(),
                    path: path.clone(),
                    folder_path: asset.folder_path.clone(),
                    selection: selection.clone(),
                    texture_id: None,
                });
            }
            Some(TextureAssetCandidate {
                label: label.clone(),
                path: path.clone(),
                folder_path: fallback_asset_folder(path),
                selection: selection.clone(),
                texture_id: None,
            })
        }
        _ => None,
    }
}

pub(super) fn source_material_ref_for_part(
    asset_id: &str,
    part: &crate::assets::static_mesh::StaticMeshPart,
) -> Option<EditorAssetRef> {
    let material_label = part.material_label.as_deref()?;
    let sub_asset_id = part
        .material_id
        .clone()
        .filter(|id| !id.trim().is_empty())
        .unwrap_or_else(|| material_id_from_label(material_label));
    Some(EditorAssetRef::new(
        asset_id.to_string(),
        sub_asset_id,
        part.material_slot_name.clone(),
    ))
}

pub(super) fn mesh_thumbnail_texture_by_asset_ref(
    world: &mut World,
) -> HashMap<(String, String), egui::TextureId> {
    let requests = world
        .get_resource::<AssetRegistry>()
        .map(|registry| {
            registry
                .records
                .iter()
                .filter_map(|record| {
                    let manifest_path = record
                        .import_settings
                        .model()?
                        .static_mesh_manifest_path
                        .as_deref()?;
                    load_static_mesh_manifest(manifest_path).ok()
                })
                .flat_map(|manifest| {
                    let legacy_skinned = if manifest.schema_version < 2 {
                        crate::assets::gltf_skinned_primitive_labels(&manifest.source.path)
                    } else {
                        HashSet::new()
                    };
                    manifest.parts.into_iter().map(move |part| {
                        let sub_asset_id = if part.id.trim().is_empty() {
                            part_id_from_label(&part.mesh_label)
                        } else {
                            part.id.clone()
                        };
                        let key =
                            format!("subasset:{}#mesh:{}", manifest.source.path, sub_asset_id);
                        let requires_scene_instance = manifest.metadata.animation_count > 0
                            || part.skinned
                            || legacy_skinned.contains(&part.mesh_label);
                        (
                            manifest.asset_id.clone(),
                            sub_asset_id,
                            key,
                            manifest.source.path.clone(),
                            part.mesh_label,
                            part.material_label,
                            requires_scene_instance,
                        )
                    })
                })
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    if requests.is_empty() || world.get_resource::<AssetThumbnailCache>().is_none() {
        return HashMap::new();
    }
    world.resource_scope(|world, mut cache: Mut<AssetThumbnailCache>| {
        if let Some(mut studio) = world.get_resource_mut::<ThumbnailStudio>() {
            for (_, _, key, path, mesh_label, material_label, requires_scene) in &requests {
                cache.request_mesh_subasset(
                    key.clone(),
                    path.clone(),
                    mesh_label.clone(),
                    material_label.clone(),
                    *requires_scene,
                    &mut studio,
                );
            }
        }
    });
    let snapshot = world.resource::<AssetThumbnailCache>().snapshot();
    requests
        .into_iter()
        .filter_map(|(asset_id, sub_asset_id, key, ..)| {
            snapshot
                .texture_ids
                .get(&key)
                .copied()
                .map(|texture| ((asset_id, sub_asset_id), texture))
        })
        .collect()
}

pub(super) fn fallback_asset_folder(path: &str) -> String {
    Path::new(path)
        .parent()
        .and_then(|parent| parent.to_str())
        .filter(|folder| !folder.trim().is_empty())
        .unwrap_or(crate::assets::ASSETS_ROOT)
        .replace('\\', "/")
}

pub(super) fn locate_asset_ref(
    world: &mut World,
    asset: Option<&EditorAssetRef>,
    candidates: &[AssetRefCandidate],
) {
    let Some(asset) = asset.filter(|asset| asset.is_resolved()) else {
        return;
    };
    let Some(candidate) = candidates.iter().find(|candidate| {
        candidate.reference.asset_id == asset.asset_id
            && candidate.reference.sub_asset_id == asset.sub_asset_id
    }) else {
        if let Some(mut scene_io) = world.get_resource_mut::<crate::scene_io::SceneIo>() {
            scene_io.status = format!("Could not locate imported asset {}", asset.label);
        }
        return;
    };

    reveal_asset_in_browser(world, &candidate.folder_path, &candidate.selection);
}

pub(super) fn reveal_asset_in_browser(
    world: &mut World,
    folder_path: &str,
    selection: &AssetSelection,
) {
    if let Some(mut assets) = world.get_resource_mut::<EditorAssets>() {
        assets.current_folder = folder_path.to_string();
        assets.select(selection.clone());
    }
    crate::ui::request_editor_tab(world, EditorTab::AssetBrowser);
}
