use std::{borrow::Cow, ops::Range};

use crate::{lsp_types::SemanticTokenType, prelude::TripleTarget, util::offset_to_position};

pub fn head() -> crate::lsp_types::Range {
    let start = crate::lsp_types::Position {
        line: 0,
        character: 0,
    };
    crate::lsp_types::Range {
        end: start.clone(),
        start,
    }
}

pub trait Lang: 'static {
    /// Type of the parsed element.
    type Element: Send + Sync;
    type ElementError: Into<crate::feature::diagnostics::SimpleDiagnostic>
        + Send
        + Sync
        + std::fmt::Debug;

    const CODE_ACTION: bool;
    const HOVER: bool;
    const LANG: &'static str;
    const TRIGGERS: &'static [&'static str];
    const LEGEND_TYPES: &'static [SemanticTokenType];
    const PATTERN: Option<&'static str>;

    /// Map a CST syntax kind to a semantic token type for highlighting.
    fn semantic_token_type(_kind: rowan::SyntaxKind) -> Option<SemanticTokenType> {
        None
    }

    /// Map a CST syntax kind + byte span to semantic token spans for highlighting.
    fn semantic_token_spans(
        kind: rowan::SyntaxKind,
        span: std::ops::Range<usize>,
        _text: &str,
    ) -> Vec<(SemanticTokenType, std::ops::Range<usize>)> {
        Self::semantic_token_type(kind)
            .map(|t| vec![(t, span)])
            .unwrap_or_default()
    }
}

pub trait LangHelper: std::fmt::Debug {
    fn keyword(&self) -> &[&'static str];
    fn default_position(&self) -> TripleTarget {
        TripleTarget::Subject
    }
    fn unquote<'a>(&self, inp: &'a str) -> &'a str {
        inp
    }
    fn quote(&self, inp: &str) -> String {
        format!("{}", inp)
    }
    /// Return `true` if this language provides its own prefix completion and
    /// the generic [`defined_prefix_completion`] system should be skipped.
    fn handles_prefix_completion(&self) -> bool {
        false
    }
    fn supports_shape_validation(&self) -> bool {
        true
    }

    fn inlay_types_hint(
        &self,
        subject: &Range<usize>,
        rope: &ropey::Rope,
        last_type: Option<&Range<usize>>,
        types: Vec<Cow<'_, str>>,
    ) -> Option<crate::lsp_types::InlayHint> {
        let (label, position) = if let Some(lt) = last_type {
            if let Some(pos) = offset_to_position(lt.end, &rope) {
                let label = format!(", {}", types.join(", "));
                (label, pos)
            } else {
                return None;
            }
        } else {
            let offset = if rope.get_char(subject.start) == Some('[') {
                subject.start + 1
            } else {
                subject.end
            };

            if let Some(pos) = offset_to_position(offset, &rope) {
                let label = format!(" a {};", types.join(", "));
                (label, pos)
            } else {
                return None;
            }
        };

        return Some(crate::lsp_types::InlayHint {
            position,
            label: crate::lsp_types::InlayHintLabel::String(label),
            kind: None,
            text_edits: None,
            tooltip: None,
            padding_left: None,
            padding_right: None,
            data: None,
        });
    }
}
