use std::borrow::Cow;
use std::sync::LazyLock;

use tower_lsp::Client;
use tower_lsp::lsp_types::{SemanticToken, SemanticTokenTypes, SemanticTokens};

use crate::consts::TREESITTER_CMAKE_LANGUAGE;
use crate::utils::query::{AstNode, AstNodeContainer};

use tree_sitter::{Point, Query, QueryCursor, StreamingIterator};

const NONE_TYPE: &str = "none";

const NONE_TYPE_COW: Cow<'static, str> = Cow::Borrowed(NONE_TYPE);

const NONE_SEMANTIC_TOKEN: SemanticTokenTypes = SemanticTokenTypes::Custom(NONE_TYPE_COW);

pub const LEGEND_TYPE: &[SemanticTokenTypes] = &[
    SemanticTokenTypes::Function,   // index 0
    SemanticTokenTypes::Method,     // index 1
    SemanticTokenTypes::Variable,   // index 2
    SemanticTokenTypes::String,     // index 3
    SemanticTokenTypes::Comment,    // index 4
    SemanticTokenTypes::Number,     // index 5
    SemanticTokenTypes::Keyword,    // index 6
    SemanticTokenTypes::Operator,   // index 7
    SemanticTokenTypes::Modifier,   // index 8
    SemanticTokenTypes::Parameter,  // index 9
    SemanticTokenTypes::EnumMember, // index 10
    NONE_SEMANTIC_TOKEN,            // index 11
];

fn get_token_position(tokentype: SemanticTokenTypes) -> u32 {
    LEGEND_TYPE
        .iter()
        .position(|data| *data == tokentype)
        .unwrap() as u32
}

static NUMBERREGEX: LazyLock<regex::Regex> =
    LazyLock::new(|| regex::Regex::new(r"^\d+(?:\.+\d*)?$").unwrap());

static KEYWORDREGEX: LazyLock<regex::Regex> =
    LazyLock::new(|| regex::Regex::new(r"^([A-Z-_]+)$").unwrap());

trait NodeGetToken {
    fn hl_token(&self, source: &str) -> SemanticTokenTypes;
    fn hl_token_index(&self, source: &str) -> u32 {
        get_token_position(self.hl_token(source))
    }
    fn get_semantic_token(&self, cursor: &mut Point, source: &str) -> SemanticToken;
}

trait ContainerGetTokens {
    fn get_semantic_tokens(&self, source: &str) -> Vec<SemanticToken>;
}

impl<'a> NodeGetToken for AstNode<'a> {
    fn hl_token_index(&self, source: &str) -> u32 {
        get_token_position(self.hl_token(source))
    }
    fn hl_token(&self, source: &str) -> SemanticTokenTypes {
        if self.names.contains(&"function") {
            return SemanticTokenTypes::Function;
        }
        if self.names.contains(&"string") {
            return SemanticTokenTypes::String;
        }
        if self.names.contains(&"comment") && self.names.contains(&"spell") {
            return SemanticTokenTypes::Comment;
        }
        if self.names.contains(&"constant") {
            match self.node.utf8_text(source.as_bytes()) {
                Ok(txt) if NUMBERREGEX.is_match(txt) => {
                    return SemanticTokenTypes::Number;
                }
                Ok(txt)
                    if KEYWORDREGEX.is_match(txt)
                        // NOTE: exclude the "-D" and "CMAKE_CXX" like
                        && !txt.starts_with("-")
                        && !txt.starts_with("CMAKE_") =>
                {
                    return SemanticTokenTypes::EnumMember;
                }
                _ => {}
            }
        }
        if self
            .names
            .iter()
            .any(|hl| hl.starts_with("punctuation") || hl.ends_with("operator"))
        {
            return SemanticTokenTypes::Operator;
        }
        if self.names.contains(&"keyword.modifier") {
            return SemanticTokenTypes::Modifier;
        }
        if self.names.iter().any(|hl| hl.starts_with("keyword")) {
            return SemanticTokenTypes::Keyword;
        }
        if self.names.contains(&"variable.parameter") {
            return SemanticTokenTypes::Parameter;
        }
        if self.names.contains(&"variable") {
            return SemanticTokenTypes::Variable;
        }
        NONE_SEMANTIC_TOKEN
    }

    fn get_semantic_token(&self, cursor: &mut Point, source: &str) -> SemanticToken {
        let otoken = self.hl_token_index(source);
        let range = self.range();
        let start_byte = range.start_byte;
        let end_byte = range.end_byte;
        let start_point = range.start_point;
        if start_point.row > cursor.row {
            cursor.column = 0;
        }

        let token = SemanticToken {
            delta_line: (start_point.row - cursor.row) as u32,
            delta_start: (start_point.column - cursor.column) as u32,
            length: (end_byte - start_byte) as u32,
            token_type: otoken,
            token_modifiers_bitset: 0,
        };
        *cursor = start_point;
        token
    }
}

impl<'a> ContainerGetTokens for AstNodeContainer<'a> {
    fn get_semantic_tokens(&self, source: &str) -> Vec<SemanticToken> {
        assert!(self.nodes.is_sorted());
        let mut cursor = Point::new(0, 0);
        let mut tokens = vec![];
        for node in &self.nodes {
            tokens.push(node.get_semantic_token(&mut cursor, source));
        }
        tokens
    }
}

trait HlContainerInsert<'a> {
    fn hl_insert_node(&mut self, ast_node: AstNode<'a>);

    fn sort_hlnode(&mut self);
}

impl<'a> HlContainerInsert<'a> for AstNodeContainer<'a> {
    fn sort_hlnode(&mut self) {
        self.nodes.sort();
    }
    fn hl_insert_node(&mut self, ast_node: AstNode<'a>) {
        self.nodes.push(ast_node);
    }
}

pub async fn semantic_token(_client: &Client, context: &str) -> Option<SemanticTokens> {
    let mut parse = tree_sitter::Parser::new();
    parse.set_language(&TREESITTER_CMAKE_LANGUAGE).unwrap();
    let thetree = parse.parse(context, None);
    let tree = thetree?;
    Some(SemanticTokens {
        result_id: None,
        data: get_tokens(tree.root_node(), context),
    })
}

fn get_tokens(node: tree_sitter::Node, source: &str) -> Vec<SemanticToken> {
    let query_source = tree_sitter_cmake::HIGHLIGHTS_QUERY;
    let query = Query::new(&TREESITTER_CMAKE_LANGUAGE, query_source).unwrap();

    let mut cursor = QueryCursor::new();
    let mut matches = cursor.matches(&query, node, source.as_bytes());

    let mut container = AstNodeContainer::new();
    let names = query.capture_names();
    let mut ranges = vec![];
    while let Some(m) = matches.next() {
        for e in m.captures {
            if ranges.contains(&e.node.range()) {
                continue;
            }
            ranges.push(e.node.range());
            let highlight = names[e.index as usize];
            if highlight == "none" {
                continue;
            }
            let ast_node = AstNode::new(e.node, names[e.index as usize]);
            container.hl_insert_node(ast_node);
        }
    }
    container.sort_hlnode();
    container.get_semantic_tokens(source)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_number() {
        assert!(NUMBERREGEX.is_match("1.1"));
        assert!(NUMBERREGEX.is_match("222"));
        assert!(!NUMBERREGEX.is_match("222abcd"));
    }
    #[test]
    fn test_keywordr() {
        assert!(!NUMBERREGEX.is_match("abcd"));
        assert!(!NUMBERREGEX.is_match("abcd_WORLD"));
        assert!(KEYWORDREGEX.is_match("HELLO_WORLD"));
        assert!(!KEYWORDREGEX.is_match("Qt6::WaylandClient"));
    }

    fn semantic_token_test(context: &str) -> Option<SemanticTokens> {
        let mut parse = tree_sitter::Parser::new();
        parse.set_language(&TREESITTER_CMAKE_LANGUAGE).unwrap();
        let thetree = parse.parse(context, None);
        let tree = thetree?;
        Some(SemanticTokens {
            result_id: None,
            data: get_tokens(tree.root_node(), context),
        })
    }

    #[test]
    fn test_hl() {
        let tokens = semantic_token_test(include_str!(
            "../assets_for_test/highlight/bracket_argument.cmake"
        ))
        .unwrap()
        .data;
        assert_eq!(
            tokens,
            vec![
                SemanticToken {
                    delta_line: 0,
                    delta_start: 0,
                    length: 3,
                    token_type: get_token_position(SemanticTokenTypes::Function),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 3,
                    length: 1,
                    token_type: get_token_position(SemanticTokenTypes::Operator),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 1,
                    length: 1,
                    token_type: get_token_position(SemanticTokenTypes::Variable),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 2,
                    length: 17,
                    token_type: get_token_position(SemanticTokenTypes::String),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 5,
                    length: 1,
                    token_type: get_token_position(SemanticTokenTypes::Operator),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 1,
                    length: 1,
                    token_type: get_token_position(SemanticTokenTypes::Operator),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 1,
                    length: 4,
                    token_type: get_token_position(SemanticTokenTypes::Variable),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 4,
                    length: 1,
                    token_type: get_token_position(SemanticTokenTypes::Operator),
                    token_modifiers_bitset: 0
                },
                SemanticToken {
                    delta_line: 0,
                    delta_start: 6,
                    length: 1,
                    token_type: get_token_position(SemanticTokenTypes::Operator),
                    token_modifiers_bitset: 0
                }
            ]
        );
    }
}
