mod build_hover;
mod decl_context;
mod function;
mod hover_builder;
mod humanize_type_decl;
mod humanize_types;
mod keyword_hover;

use super::RegisterCapabilities;
use crate::context::ServerContextSnapshot;
use crate::util::{find_ref_at, resolve_ref_single};
pub use build_hover::build_hover_content_for_completion;
use build_hover::build_semantic_info_hover;
pub(crate) use decl_context::{HoverDeclContext, HoverDeclInfo};
use emmylua_code_analysis::{EmmyLuaAnalysis, FileId, WorkspaceId};
use emmylua_parser::{LuaAstNode, LuaDocDescription, LuaTokenKind};
use emmylua_parser_desc::parse_ref_target;
pub use hover_builder::HoverBuilder;
pub use humanize_types::infer_prefix_global_name;
use keyword_hover::{hover_keyword, is_keyword};
use lsp_types::{
    ClientCapabilities, Hover, HoverContents, HoverParams, HoverProviderCapability, MarkupContent,
    Position, ServerCapabilities,
};
use rowan::TokenAtOffset;
use tokio_util::sync::CancellationToken;

pub async fn on_hover(
    context: ServerContextSnapshot,
    params: HoverParams,
    _: CancellationToken,
) -> Option<Hover> {
    let uri = params.text_document_position_params.text_document.uri;
    let position = params.text_document_position_params.position;
    let analysis = context.analysis().read().await;
    let file_id = analysis.get_file_id(&uri)?;
    hover(&analysis, file_id, position)
}

pub fn hover(analysis: &EmmyLuaAnalysis, file_id: FileId, position: Position) -> Option<Hover> {
    let semantic_model = analysis.compilation.get_semantic_model(file_id)?;
    if !semantic_model.get_emmyrc().hover.enable {
        return None;
    }

    let root = semantic_model.get_root();
    let position_offset = {
        let document = semantic_model.get_document();
        document.get_offset(position.line as usize, position.character as usize)?
    };

    if position_offset > root.syntax().text_range().end() {
        return None;
    }

    let token = match root.syntax().token_at_offset(position_offset) {
        TokenAtOffset::Single(token) => token,
        TokenAtOffset::Between(left, right) => {
            if matches!(
                right.kind().into(),
                LuaTokenKind::TkDot
                    | LuaTokenKind::TkColon
                    | LuaTokenKind::TkLeftBracket
                    | LuaTokenKind::TkRightBracket
            ) {
                left
            } else {
                right
            }
        }
        TokenAtOffset::None => return None,
    };
    match token {
        keywords if is_keyword(keywords.clone()) => {
            let document = semantic_model.get_document();
            Some(Hover {
                contents: HoverContents::Markup(MarkupContent {
                    kind: lsp_types::MarkupKind::Markdown,
                    value: hover_keyword(keywords.clone()),
                }),
                range: document.to_lsp_range(keywords.text_range()),
            })
        }
        detail if detail.kind() == LuaTokenKind::TkDocDetail.into() => {
            let parent = detail.parent()?;
            let description = LuaDocDescription::cast(parent)?;
            let document = semantic_model.get_document();

            let path = find_ref_at(
                semantic_model
                    .get_module()
                    .map(|m| m.workspace_id)
                    .unwrap_or(WorkspaceId::MAIN),
                semantic_model.get_emmyrc(),
                document.get_text(),
                description.clone(),
                position_offset,
            )?;

            let db = analysis.compilation.get_db();
            let semantic_info = resolve_ref_single(db, file_id, &path, &detail)?;

            build_semantic_info_hover(
                &semantic_model,
                db,
                &document,
                detail,
                semantic_info,
                path.last()?.1,
            )
        }
        doc_see if doc_see.kind() == LuaTokenKind::TkDocSeeContent.into() => {
            let document = semantic_model.get_document();

            let path =
                parse_ref_target(document.get_text(), doc_see.text_range(), position_offset)?;

            let db = analysis.compilation.get_db();
            let semantic_info = resolve_ref_single(db, file_id, &path, &doc_see)?;

            build_semantic_info_hover(
                &semantic_model,
                db,
                &document,
                doc_see,
                semantic_info,
                path.last()?.1,
            )
        }
        _ => {
            let semantic_info = semantic_model.get_semantic_info(token.clone().into())?;
            let db = semantic_model.get_db();
            let document = semantic_model.get_document();
            let range = token.text_range();

            build_semantic_info_hover(&semantic_model, db, &document, token, semantic_info, range)
        }
    }
}

pub struct HoverCapabilities;

impl RegisterCapabilities for HoverCapabilities {
    fn register_capabilities(server_capabilities: &mut ServerCapabilities, _: &ClientCapabilities) {
        server_capabilities.hover_provider = Some(HoverProviderCapability::Simple(true));
    }
}
