mod cache;
mod decl;
mod generic;
mod guard;
mod infer;
mod member;
mod overload_resolve;
mod reference;
mod semantic_info;
mod type_check;
mod visibility;

use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;

pub use cache::{CacheEntry, CacheOptions, LuaAnalysisPhase, LuaInferCache};
pub use decl::{enum_variable_is_param, parse_require_module_info};
use emmylua_parser::{
    LuaCallExpr, LuaChunk, LuaExpr, LuaIndexExpr, LuaIndexKey, LuaParseError, LuaSyntaxNode,
    LuaSyntaxToken, LuaTableExpr,
};
use generic::resolve_call_self_type;
pub use infer::infer_index_expr;
use infer::{apply_assignment_target_casts, infer_bind_value_type, infer_expr_list_types};
pub use infer::{infer_table_field_value_should_be, infer_table_should_be};
use lsp_types::Uri;
pub use member::LuaMemberInfo;
pub use member::find_index_operations;
pub use member::get_member_map;
use member::{
    find_member_origin_owner, find_members_in_scope, find_members_with_key_in_scope,
    get_member_map_in_scope,
};
use reference::is_reference_to;
use rowan::{NodeOrToken, TextRange};
pub use semantic_info::SemanticInfo;
pub(crate) use semantic_info::{infer_node_semantic_decl, resolve_global_decl_id};
use semantic_info::{
    infer_node_semantic_info, infer_token_semantic_decl, infer_token_semantic_info,
};
pub(crate) use type_check::check_type_compact;
pub(crate) use type_check::is_sub_type_of;
pub use visibility::check_module_visibility;
use visibility::check_visibility;

pub use crate::semantic::member::find_members_with_key;
use crate::semantic::type_check::check_type_compact_detail;
use crate::{Emmyrc, LuaDocument, LuaSemanticDeclId, ModuleInfo, db_index::LuaTypeDeclId};
use crate::{
    FileId,
    db_index::{DbIndex, LuaType},
};
use crate::{LuaFunctionType, LuaMemberId, LuaMemberKey, LuaTypeOwner};
pub use generic::*;
pub use guard::{InferGuard, InferGuardRef};
pub use infer::InferFailReason;
pub use infer::infer_call_expr_func;
pub use infer::infer_param;
pub(crate) use infer::try_infer_expr_for_index;
pub(crate) use infer::{infer_expr, try_infer_expr_no_flow};
use overload_resolve::resolve_signature;
pub(crate) use overload_resolve::{
    callable_accepts_args, get_func_param_type, is_func_last_param_variadic,
};
pub use overload_resolve::{
    collect_callable_overload_groups, filter_callable_overloads, find_callable_overload,
};
pub use semantic_info::SemanticDeclLevel;
pub use type_check::{TypeCheckFailReason, TypeCheckResult};

pub use generic::get_keyof_members;
pub use infer::{DocTypeInferContext, infer_doc_type};

#[derive(Debug)]
pub struct SemanticModel<'a> {
    file_id: FileId,
    db: &'a DbIndex,
    infer_cache: RefCell<LuaInferCache>,
    emmyrc: Arc<Emmyrc>,
    root: LuaChunk,
}

unsafe impl<'a> Send for SemanticModel<'a> {}
unsafe impl<'a> Sync for SemanticModel<'a> {}

impl<'a> SemanticModel<'a> {
    pub fn new(
        file_id: FileId,
        db: &'a DbIndex,
        infer_config: LuaInferCache,
        emmyrc: Arc<Emmyrc>,
        root: LuaChunk,
    ) -> Self {
        Self {
            file_id,
            db,
            infer_cache: RefCell::new(infer_config),
            emmyrc,
            root,
        }
    }

    pub fn get_document(&'_ self) -> LuaDocument<'_> {
        self.db
            .get_vfs()
            .get_document(&self.file_id)
            .expect("always exists")
    }

    pub fn get_module(&self) -> Option<&ModuleInfo> {
        self.db.get_module_index().get_module(self.file_id)
    }

    pub fn get_document_by_file_id(&'_ self, file_id: FileId) -> Option<LuaDocument<'_>> {
        self.db.get_vfs().get_document(&file_id)
    }

    pub fn get_document_by_uri(&'_ self, uri: &Uri) -> Option<LuaDocument<'_>> {
        let file_id = self.db.get_vfs().get_file_id(uri)?;
        self.db.get_vfs().get_document(&file_id)
    }

    pub fn get_root_by_file_id(&self, file_id: FileId) -> Option<LuaChunk> {
        Some(
            self.db
                .get_vfs()
                .get_syntax_tree(&file_id)?
                .get_chunk_node(),
        )
    }

    pub fn get_file_parse_error(&self) -> Option<Vec<LuaParseError>> {
        self.db.get_vfs().get_file_parse_error(&self.file_id)
    }

    pub fn infer_expr(&self, expr: LuaExpr) -> Result<LuaType, InferFailReason> {
        infer_expr(self.db, &mut self.infer_cache.borrow_mut(), expr)
    }

    pub(crate) fn apply_assignment_target_casts(
        &self,
        target_expr: LuaExpr,
        source_type: LuaType,
    ) -> LuaType {
        apply_assignment_target_casts(
            self.db,
            &mut self.infer_cache.borrow_mut(),
            target_expr,
            source_type,
        )
    }

    pub fn infer_table_should_be(&self, table: LuaTableExpr) -> Option<LuaType> {
        infer_table_should_be(self.db, &mut self.infer_cache.borrow_mut(), table).ok()
    }

    pub fn get_member_infos(&self, prefix_type: &LuaType) -> Option<Vec<LuaMemberInfo>> {
        find_members_in_scope(self.db, self.file_id, prefix_type)
    }

    pub fn get_member_info_with_key(
        &self,
        prefix_type: &LuaType,
        member_key: LuaMemberKey,
        find_all: bool,
    ) -> Option<Vec<LuaMemberInfo>> {
        find_members_with_key_in_scope(self.db, self.file_id, prefix_type, member_key, find_all)
    }

    pub fn get_member_info_map(
        &self,
        prefix_type: &LuaType,
    ) -> Option<HashMap<LuaMemberKey, Vec<LuaMemberInfo>>> {
        get_member_map_in_scope(self.db, self.file_id, prefix_type)
    }

    pub fn type_check(&self, source: &LuaType, compact_type: &LuaType) -> TypeCheckResult {
        check_type_compact(self.db, source, compact_type)
    }

    pub fn type_check_detail(&self, source: &LuaType, compact_type: &LuaType) -> TypeCheckResult {
        check_type_compact_detail(self.db, source, compact_type)
    }

    pub fn infer_call_expr_func(
        &self,
        call_expr: LuaCallExpr,
        arg_count: Option<usize>,
    ) -> Option<Arc<LuaFunctionType>> {
        let prefix_expr = call_expr.get_prefix_expr()?;
        let call_expr_type =
            infer_expr(self.db, &mut self.infer_cache.borrow_mut(), prefix_expr).ok()?;
        infer_call_expr_func(
            self.db,
            &mut self.infer_cache.borrow_mut(),
            call_expr,
            call_expr_type,
            &InferGuard::new(),
            arg_count,
        )
        .ok()
    }

    pub fn callable_accepts_args(
        &self,
        func: &LuaFunctionType,
        expr_types: &[LuaType],
        is_colon_call: bool,
        arg_count: Option<usize>,
    ) -> bool {
        callable_accepts_args(self.db, func, expr_types, is_colon_call, arg_count)
    }

    /// 推断表达式列表类型, 位于最后的表达式会触发多值推断
    pub fn infer_expr_list_types(
        &self,
        exprs: &[LuaExpr],
        var_count: Option<usize>,
    ) -> Vec<(LuaType, TextRange)> {
        let cache = &mut self.infer_cache.borrow_mut();
        infer_expr_list_types(self.db, cache, exprs, var_count, |db, cache, expr| {
            Ok(infer_expr(db, cache, expr).unwrap_or(LuaType::Unknown))
        })
        .unwrap_or_default()
    }

    /// 推断值已经绑定的类型(不是推断值的类型). 例如从右值推断左值类型, 从调用参数推断函数参数类型
    pub fn infer_bind_value_type(&self, expr: LuaExpr) -> Option<LuaType> {
        infer_bind_value_type(self.db, &mut self.infer_cache.borrow_mut(), expr)
    }

    pub fn get_semantic_info(
        &self,
        node_or_token: NodeOrToken<LuaSyntaxNode, LuaSyntaxToken>,
    ) -> Option<SemanticInfo> {
        match node_or_token {
            NodeOrToken::Node(node) => {
                infer_node_semantic_info(self.db, &mut self.infer_cache.borrow_mut(), node)
            }
            NodeOrToken::Token(token) => {
                infer_token_semantic_info(self.db, &mut self.infer_cache.borrow_mut(), token)
            }
        }
    }

    pub fn find_decl(
        &self,
        node_or_token: NodeOrToken<LuaSyntaxNode, LuaSyntaxToken>,
        level: SemanticDeclLevel,
    ) -> Option<LuaSemanticDeclId> {
        match node_or_token {
            NodeOrToken::Node(node) => {
                infer_node_semantic_decl(self.db, &mut self.infer_cache.borrow_mut(), node, level)
            }
            NodeOrToken::Token(token) => {
                infer_token_semantic_decl(self.db, &mut self.infer_cache.borrow_mut(), token, level)
            }
        }
    }

    pub fn is_reference_to(
        &self,
        node: LuaSyntaxNode,
        semantic_decl_id: LuaSemanticDeclId,
        level: SemanticDeclLevel,
    ) -> bool {
        is_reference_to(
            self.db,
            &mut self.infer_cache.borrow_mut(),
            node,
            semantic_decl_id,
            level,
        )
        .unwrap_or(false)
    }

    pub fn is_semantic_visible(
        &self,
        token: LuaSyntaxToken,
        property_owner: LuaSemanticDeclId,
    ) -> bool {
        check_visibility(
            self.db,
            self.file_id,
            &self.emmyrc,
            &mut self.infer_cache.borrow_mut(),
            token,
            property_owner,
        )
        .unwrap_or(true)
    }

    pub fn is_sub_type_of(
        &self,
        sub_type_ref_id: &LuaTypeDeclId,
        super_type_ref_id: &LuaTypeDeclId,
    ) -> bool {
        is_sub_type_of(self.db, sub_type_ref_id, super_type_ref_id)
    }

    pub fn get_emmyrc(&self) -> &Emmyrc {
        &self.emmyrc
    }

    pub fn get_emmyrc_arc(&self) -> Arc<Emmyrc> {
        self.emmyrc.clone()
    }

    pub fn get_root(&self) -> &LuaChunk {
        &self.root
    }

    pub fn get_db(&self) -> &DbIndex {
        self.db
    }

    pub fn get_file_id(&self) -> FileId {
        self.file_id
    }

    pub fn get_cache(&self) -> &RefCell<LuaInferCache> {
        &self.infer_cache
    }

    pub fn get_type(&self, type_owner: LuaTypeOwner) -> LuaType {
        self.db
            .get_type_index()
            .get_type_cache(&type_owner)
            .map(|cache| cache.as_type())
            .unwrap_or(&LuaType::Unknown)
            .clone()
    }

    pub fn get_member_key(&self, index_key: &LuaIndexKey) -> Option<LuaMemberKey> {
        LuaMemberKey::from_index_key(self.db, &mut self.infer_cache.borrow_mut(), index_key).ok()
    }

    pub fn infer_member_type(
        &self,
        prefix_type: &LuaType,
        member_key: &LuaMemberKey,
    ) -> Result<LuaType, InferFailReason> {
        member::infer_raw_member_type(self.db, prefix_type, member_key)
    }

    pub fn get_member_origin_owner(&self, member_id: LuaMemberId) -> Option<LuaSemanticDeclId> {
        find_member_origin_owner(self.db, &mut self.infer_cache.borrow_mut(), member_id)
    }

    pub fn resolve_call_self_type(&self, call_expr: &LuaCallExpr) -> Option<LuaType> {
        resolve_call_self_type(self.db, &mut self.infer_cache.borrow_mut(), call_expr)
    }

    pub fn get_index_decl_type(&self, index_expr: LuaIndexExpr) -> Option<LuaType> {
        let cache = &mut self.infer_cache.borrow_mut();
        infer_index_expr(self.db, cache, index_expr, false).ok()
    }
}
