use crate::server::{
    Server,
    lsp::{
        base_types::LSPAny,
        diagnostic::{Diagnostic, DiagnosticCode, DiagnosticSeverity},
        textdocument::{Range, TextDocumentItem},
    },
};
use ll_sparql_parser::ast::{AstNode, PrefixedName, QueryUnit};
use std::{collections::HashSet, sync::LazyLock};

pub static CODE: LazyLock<DiagnosticCode> =
    LazyLock::new(|| DiagnosticCode::String("undeclared-prefix".to_string()));

pub(super) fn diagnostics(
    document: &TextDocumentItem,
    query_unit: &QueryUnit,
    _server: &Server,
) -> Option<Vec<Diagnostic>> {
    let prefixed_names = query_unit
        .select_query()?
        .collect_decendants(&PrefixedName::can_cast)
        .into_iter()
        .map(|node| PrefixedName::cast(node).unwrap());

    let declared_prefixes: HashSet<String> = HashSet::from_iter(
        query_unit
            .prologue()
            .map_or(Vec::new(), |prologue| prologue.prefix_declarations())
            .iter()
            .filter_map(|prefix_decl| prefix_decl.prefix()),
    );

    Some(
        prefixed_names
            .into_iter()
            .filter_map(|prefixed_name| {
                (!declared_prefixes.contains(&prefixed_name.prefix())).then(|| Diagnostic {
                    range: Range::from_byte_offset_range(
                        prefixed_name.syntax().text_range(),
                        &document.text,
                    )
                    .expect("prefix declaration text range should be in text"),
                    severity: DiagnosticSeverity::Error,
                    source: Some("qlue-ls".to_string()),
                    code: Some((*CODE).clone()),
                    message: format!(
                        "'{}' is used here, but was never declared\n",
                        prefixed_name.prefix()
                    ),
                    data: Some(LSPAny::String(prefixed_name.prefix())),
                })
            })
            .collect(),
    )
}
