use std::ops::ControlFlow;

use base_db::{Crate as BaseCrate, LibraryRoots, LocalRoots, source_root_crates};
use rayon::prelude::*;

use super::{Query, SymbolIndex, crate_symbols};
use crate::RootDatabase;

pub fn world_symbols(db: &RootDatabase, mut query: Query) -> Vec<hir::symbols::FileSymbol<'_>> {
    let _p = tracing::info_span!("world_symbols", query = ?query.query).entered();
    let indices = if query.is_crate_search() {
        query.only_types = false;
        vec![SymbolIndex::extern_prelude_symbols(db)]
    } else if !query.path_filter.is_empty() {
        query.only_types = false;
        let modules = crate::visibility::resolve_path_to_modules(
            db,
            &query.path_filter,
            query.anchor_to_crate,
            query.case_sensitive,
        );
        if modules.is_empty() {
            return Vec::new();
        }
        modules.into_iter().map(|module| SymbolIndex::module_symbols(db, module)).collect()
    } else if query.libs {
        library_indices(db)
    } else {
        local_indices(db)
    };

    let mut symbols = Vec::new();
    query.search::<()>(db, &indices, |symbol| {
        if crate::visibility::is_symbol_visible(db, symbol) {
            symbols.push(symbol.clone());
        }
        ControlFlow::Continue(())
    });
    symbols
}

fn library_indices(db: &RootDatabase) -> Vec<&SymbolIndex<'_>> {
    let roots = LibraryRoots::get(db).roots(db);
    roots.par_iter()
        .for_each_with(db.clone(), |snap, &root| _ = SymbolIndex::library_symbols(snap, root));
    roots.iter().map(|&root| SymbolIndex::library_symbols(db, root)).collect()
}

fn local_indices(db: &RootDatabase) -> Vec<&SymbolIndex<'_>> {
    let crates = visible_local_crates(db);
    crates.par_iter().for_each_with(db.clone(), |snap, &krate| {
        _ = crate_symbols(snap, krate.into());
    });
    crates
        .into_iter()
        .flat_map(|krate| Vec::from(crate_symbols(db, krate.into())))
        .chain(std::iter::once(SymbolIndex::extern_prelude_symbols(db)))
        .collect()
}

fn visible_local_crates(db: &RootDatabase) -> Vec<BaseCrate> {
    LocalRoots::get(db)
        .roots(db)
        .iter()
        .flat_map(|&root| source_root_crates(db, root).iter().copied())
        .filter(|&krate| db.is_crate_visible(krate))
        .collect()
}
