//! This crate defines the core datastructure representing IDE state -- `RootDatabase`.
//!
//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.

#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]

mod apply_change;

pub mod active_parameter;
pub mod assists;
pub mod defs;
pub mod famous_defs;
pub mod helpers;
pub mod items_locator;
pub mod label;
pub mod path_transform;
pub mod rename;
pub mod rust_doc;
pub mod search;
pub mod source_change;
pub mod symbol_index;
pub mod traits;
pub mod ty_filter;
pub mod use_trivial_constructor;
pub mod documentation;

pub mod imports {
    pub mod import_assets;
    pub mod insert_use;
    pub mod merge_imports;
}

pub mod generated {
    pub mod lints;
}

pub mod syntax_helpers {
    pub mod node_ext;
    pub mod insert_whitespace_into_node;
    pub mod format_string;
    pub mod format_string_exprs;

    pub use parser::LexedStr;
}

use std::{fmt, mem::ManuallyDrop};

use base_db::{
    salsa::{self, Durability},
    AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast,
};
use hir::db::{DefDatabase, ExpandDatabase, HirDatabase};
use triomphe::Arc;

use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher};

pub use ::line_index;

/// `base_db` is normally also needed in places where `ide_db` is used, so this re-export is for convenience.
pub use base_db;

pub type FxIndexSet<T> = indexmap::IndexSet<T, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
pub type FxIndexMap<K, V> =
    indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;

#[salsa::database(
    base_db::SourceDatabaseExtStorage,
    base_db::SourceDatabaseStorage,
    hir::db::ExpandDatabaseStorage,
    hir::db::DefDatabaseStorage,
    hir::db::HirDatabaseStorage,
    hir::db::InternDatabaseStorage,
    LineIndexDatabaseStorage,
    symbol_index::SymbolsDatabaseStorage
)]
pub struct RootDatabase {
    // We use `ManuallyDrop` here because every codegen unit that contains a
    // `&RootDatabase -> &dyn OtherDatabase` cast will instantiate its drop glue in the vtable,
    // which duplicates `Weak::drop` and `Arc::drop` tens of thousands of times, which makes
    // compile times of all `ide_*` and downstream crates suffer greatly.
    storage: ManuallyDrop<salsa::Storage<RootDatabase>>,
}

impl Drop for RootDatabase {
    fn drop(&mut self) {
        unsafe { ManuallyDrop::drop(&mut self.storage) };
    }
}

impl fmt::Debug for RootDatabase {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("RootDatabase").finish()
    }
}

impl Upcast<dyn ExpandDatabase> for RootDatabase {
    #[inline]
    fn upcast(&self) -> &(dyn ExpandDatabase + 'static) {
        &*self
    }
}

impl Upcast<dyn DefDatabase> for RootDatabase {
    #[inline]
    fn upcast(&self) -> &(dyn DefDatabase + 'static) {
        &*self
    }
}

impl Upcast<dyn HirDatabase> for RootDatabase {
    #[inline]
    fn upcast(&self) -> &(dyn HirDatabase + 'static) {
        &*self
    }
}

impl FileLoader for RootDatabase {
    fn file_text(&self, file_id: FileId) -> Arc<str> {
        FileLoaderDelegate(self).file_text(file_id)
    }
    fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
        FileLoaderDelegate(self).resolve_path(path)
    }
    fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
        FileLoaderDelegate(self).relevant_crates(file_id)
    }
}

impl salsa::Database for RootDatabase {}

impl Default for RootDatabase {
    fn default() -> RootDatabase {
        RootDatabase::new(None)
    }
}

impl RootDatabase {
    pub fn new(lru_capacity: Option<usize>) -> RootDatabase {
        let mut db = RootDatabase { storage: ManuallyDrop::new(salsa::Storage::default()) };
        db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
        db.set_proc_macros_with_durability(Default::default(), Durability::HIGH);
        db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
        db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
        db.set_expand_proc_attr_macros_with_durability(false, Durability::HIGH);
        db.update_parse_query_lru_capacity(lru_capacity);
        db
    }

    pub fn enable_proc_attr_macros(&mut self) {
        self.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH);
    }

    pub fn update_parse_query_lru_capacity(&mut self, lru_capacity: Option<usize>) {
        let lru_capacity = lru_capacity.unwrap_or(base_db::DEFAULT_PARSE_LRU_CAP);
        base_db::ParseQuery.in_db_mut(self).set_lru_capacity(lru_capacity);
        // macro expansions are usually rather small, so we can afford to keep more of them alive
        hir::db::ParseMacroExpansionQuery.in_db_mut(self).set_lru_capacity(4 * lru_capacity);
        hir::db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(4 * lru_capacity);
    }

    pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap<Box<str>, usize>) {
        use hir::db as hir_db;

        base_db::ParseQuery.in_db_mut(self).set_lru_capacity(
            lru_capacities
                .get(stringify!(ParseQuery))
                .copied()
                .unwrap_or(base_db::DEFAULT_PARSE_LRU_CAP),
        );
        hir_db::ParseMacroExpansionQuery.in_db_mut(self).set_lru_capacity(
            lru_capacities
                .get(stringify!(ParseMacroExpansionQuery))
                .copied()
                .unwrap_or(4 * base_db::DEFAULT_PARSE_LRU_CAP),
        );
        hir_db::MacroExpandQuery.in_db_mut(self).set_lru_capacity(
            lru_capacities
                .get(stringify!(MacroExpandQuery))
                .copied()
                .unwrap_or(4 * base_db::DEFAULT_PARSE_LRU_CAP),
        );

        macro_rules! update_lru_capacity_per_query {
            ($( $module:ident :: $query:ident )*) => {$(
                if let Some(&cap) = lru_capacities.get(stringify!($query)) {
                    $module::$query.in_db_mut(self).set_lru_capacity(cap);
                }
            )*}
        }
        update_lru_capacity_per_query![
            // SourceDatabase
            // base_db::ParseQuery
            // base_db::CrateGraphQuery
            // base_db::ProcMacrosQuery

            // SourceDatabaseExt
            // base_db::FileTextQuery
            // base_db::FileSourceRootQuery
            // base_db::SourceRootQuery
            base_db::SourceRootCratesQuery

            // ExpandDatabase
            hir_db::AstIdMapQuery
            // hir_db::ParseMacroExpansionQuery
            // hir_db::InternMacroCallQuery
            hir_db::MacroArgNodeQuery
            hir_db::DeclMacroExpanderQuery
            // hir_db::MacroExpandQuery
            hir_db::ExpandProcMacroQuery
            hir_db::HygieneFrameQuery
            hir_db::ParseMacroExpansionErrorQuery

            // DefDatabase
            hir_db::FileItemTreeQuery
            hir_db::CrateDefMapQueryQuery
            hir_db::BlockDefMapQuery
            hir_db::StructDataQuery
            hir_db::StructDataWithDiagnosticsQuery
            hir_db::UnionDataQuery
            hir_db::UnionDataWithDiagnosticsQuery
            hir_db::EnumDataQuery
            hir_db::EnumDataWithDiagnosticsQuery
            hir_db::ImplDataQuery
            hir_db::ImplDataWithDiagnosticsQuery
            hir_db::TraitDataQuery
            hir_db::TraitDataWithDiagnosticsQuery
            hir_db::TraitAliasDataQuery
            hir_db::TypeAliasDataQuery
            hir_db::FunctionDataQuery
            hir_db::ConstDataQuery
            hir_db::StaticDataQuery
            hir_db::Macro2DataQuery
            hir_db::MacroRulesDataQuery
            hir_db::ProcMacroDataQuery
            hir_db::BodyWithSourceMapQuery
            hir_db::BodyQuery
            hir_db::ExprScopesQuery
            hir_db::GenericParamsQuery
            hir_db::VariantsAttrsQuery
            hir_db::FieldsAttrsQuery
            hir_db::VariantsAttrsSourceMapQuery
            hir_db::FieldsAttrsSourceMapQuery
            hir_db::AttrsQuery
            hir_db::CrateLangItemsQuery
            hir_db::LangItemQuery
            hir_db::ImportMapQuery
            hir_db::FieldVisibilitiesQuery
            hir_db::FunctionVisibilityQuery
            hir_db::ConstVisibilityQuery
            hir_db::CrateSupportsNoStdQuery

            // HirDatabase
            hir_db::InferQueryQuery
            hir_db::MirBodyQuery
            hir_db::BorrowckQuery
            hir_db::TyQuery
            hir_db::ValueTyQuery
            hir_db::ImplSelfTyQuery
            hir_db::ConstParamTyQuery
            hir_db::ConstEvalQuery
            hir_db::ConstEvalDiscriminantQuery
            hir_db::ImplTraitQuery
            hir_db::FieldTypesQuery
            hir_db::LayoutOfAdtQuery
            hir_db::TargetDataLayoutQuery
            hir_db::CallableItemSignatureQuery
            hir_db::ReturnTypeImplTraitsQuery
            hir_db::GenericPredicatesForParamQuery
            hir_db::GenericPredicatesQuery
            hir_db::TraitEnvironmentQuery
            hir_db::GenericDefaultsQuery
            hir_db::InherentImplsInCrateQuery
            hir_db::InherentImplsInBlockQuery
            hir_db::IncoherentInherentImplCratesQuery
            hir_db::TraitImplsInCrateQuery
            hir_db::TraitImplsInBlockQuery
            hir_db::TraitImplsInDepsQuery
            // hir_db::InternCallableDefQuery
            // hir_db::InternLifetimeParamIdQuery
            // hir_db::InternImplTraitIdQuery
            // hir_db::InternTypeOrConstParamIdQuery
            // hir_db::InternClosureQuery
            // hir_db::InternGeneratorQuery
            hir_db::AssociatedTyDataQuery
            hir_db::TraitDatumQuery
            hir_db::StructDatumQuery
            hir_db::ImplDatumQuery
            hir_db::FnDefDatumQuery
            hir_db::FnDefVarianceQuery
            hir_db::AdtVarianceQuery
            hir_db::AssociatedTyValueQuery
            hir_db::TraitSolveQueryQuery
            hir_db::ProgramClausesForChalkEnvQuery

            // SymbolsDatabase
            symbol_index::ModuleSymbolsQuery
            symbol_index::LibrarySymbolsQuery
            // symbol_index::LocalRootsQuery
            // symbol_index::LibraryRootsQuery

            // LineIndexDatabase
            crate::LineIndexQuery

            // InternDatabase
            // hir_db::InternFunctionQuery
            // hir_db::InternStructQuery
            // hir_db::InternUnionQuery
            // hir_db::InternEnumQuery
            // hir_db::InternConstQuery
            // hir_db::InternStaticQuery
            // hir_db::InternTraitQuery
            // hir_db::InternTraitAliasQuery
            // hir_db::InternTypeAliasQuery
            // hir_db::InternImplQuery
            // hir_db::InternExternBlockQuery
            // hir_db::InternBlockQuery
            // hir_db::InternMacro2Query
            // hir_db::InternProcMacroQuery
            // hir_db::InternMacroRulesQuery
        ];
    }
}

impl salsa::ParallelDatabase for RootDatabase {
    fn snapshot(&self) -> salsa::Snapshot<RootDatabase> {
        salsa::Snapshot::new(RootDatabase { storage: ManuallyDrop::new(self.storage.snapshot()) })
    }
}

#[salsa::query_group(LineIndexDatabaseStorage)]
pub trait LineIndexDatabase: base_db::SourceDatabase {
    fn line_index(&self, file_id: FileId) -> Arc<LineIndex>;
}

fn line_index(db: &dyn LineIndexDatabase, file_id: FileId) -> Arc<LineIndex> {
    let text = db.file_text(file_id);
    Arc::new(LineIndex::new(&text))
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SymbolKind {
    Attribute,
    BuiltinAttr,
    Const,
    ConstParam,
    Derive,
    DeriveHelper,
    Enum,
    Field,
    Function,
    Impl,
    Label,
    LifetimeParam,
    Local,
    Macro,
    Module,
    SelfParam,
    SelfType,
    Static,
    Struct,
    ToolModule,
    Trait,
    TraitAlias,
    TypeAlias,
    TypeParam,
    Union,
    ValueParam,
    Variant,
}

impl From<hir::MacroKind> for SymbolKind {
    fn from(it: hir::MacroKind) -> Self {
        match it {
            hir::MacroKind::Declarative | hir::MacroKind::BuiltIn | hir::MacroKind::ProcMacro => {
                SymbolKind::Macro
            }
            hir::MacroKind::Derive => SymbolKind::Derive,
            hir::MacroKind::Attr => SymbolKind::Attribute,
        }
    }
}

impl From<hir::ModuleDefId> for SymbolKind {
    fn from(it: hir::ModuleDefId) -> Self {
        match it {
            hir::ModuleDefId::ConstId(..) => SymbolKind::Const,
            hir::ModuleDefId::EnumVariantId(..) => SymbolKind::Variant,
            hir::ModuleDefId::FunctionId(..) => SymbolKind::Function,
            hir::ModuleDefId::MacroId(..) => SymbolKind::Macro,
            hir::ModuleDefId::ModuleId(..) => SymbolKind::Module,
            hir::ModuleDefId::StaticId(..) => SymbolKind::Static,
            hir::ModuleDefId::AdtId(hir::AdtId::StructId(..)) => SymbolKind::Struct,
            hir::ModuleDefId::AdtId(hir::AdtId::EnumId(..)) => SymbolKind::Enum,
            hir::ModuleDefId::AdtId(hir::AdtId::UnionId(..)) => SymbolKind::Union,
            hir::ModuleDefId::TraitId(..) => SymbolKind::Trait,
            hir::ModuleDefId::TraitAliasId(..) => SymbolKind::TraitAlias,
            hir::ModuleDefId::TypeAliasId(..) => SymbolKind::TypeAlias,
            hir::ModuleDefId::BuiltinType(..) => SymbolKind::TypeAlias,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SnippetCap {
    _private: (),
}

impl SnippetCap {
    pub const fn new(allow_snippets: bool) -> Option<SnippetCap> {
        if allow_snippets {
            Some(SnippetCap { _private: () })
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    mod sourcegen_lints;
    mod line_index;
}
