use crate::{
    query::{Query, ScopeKind},
    world::DocumentState,
};
use itertools::Itertools;
use jsona::rowan::TextRange;
use jsona_util::environment::Environment;
use lsp_async_stub::{
    rpc::Error,
    util::{LspExt, Position},
    Context, Params,
};
use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind};

use crate::World;

#[tracing::instrument(skip_all)]
pub(crate) async fn hover<E: Environment>(
    context: Context<World<E>>,
    params: Params<HoverParams>,
) -> Result<Option<Hover>, Error> {
    let p = params.required()?;

    let workspaces = context.workspaces.read().await;
    let document_uri = &p.text_document_position_params.text_document.uri;
    let (ws, doc) = workspaces.try_get_document(document_uri)?;
    if !ws.lsp_config.schema.enabled {
        return Ok(None);
    }

    let position = p.text_document_position_params.position;
    let offset = match doc.mapper.offset(Position::from_lsp(position)) {
        Some(ofs) => ofs,
        None => {
            tracing::error!(?position, "document position not found");
            return Ok(None);
        }
    };

    let query = Query::at(&doc.dom, offset, false);
    if query.scope == ScopeKind::Unknown || (query.key.is_none() && query.value.is_none()) {
        return Ok(None);
    }

    let (keys, _) = match Query::node_at(&doc.dom, offset) {
        Some(v) => v,
        None => return Ok(None),
    };

    let schemas = match ws.query_schemas(document_uri, &keys).await {
        Some(v) => v,
        None => return Ok(None),
    };

    if let Some(key) = query.key.as_ref() {
        let content = schemas
            .iter()
            .flat_map(|schema| schema.description.clone())
            .join("\n\n");
        return Ok(Some(create_hover(doc, content, key.text_range())));
    } else if let Some(node) = query.value.as_ref() {
        let content = schemas
            .iter()
            .flat_map(|schema| schema.description.clone())
            .join("\n\n");
        return Ok(Some(create_hover(doc, content, node.text_range())));
    }

    Ok(None)
}

fn create_hover(doc: &DocumentState, content: String, text_range: TextRange) -> Hover {
    Hover {
        contents: HoverContents::Markup(MarkupContent {
            kind: MarkupKind::Markdown,
            value: content,
        }),
        range: Some(doc.mapper.range(text_range).unwrap().into_lsp()),
    }
}
