use tree_sitter::Node as TsNode;

use crate::glyph::language::SourceLanguage;

use super::intent::{generic_code_intent_aliases, identifier_words};

fn ts_function_aliases(
    name: &str,
    node: TsNode,
    src: &str,
    exported: bool,
    language: SourceLanguage,
) -> Vec<String> {
    let mut aliases = vec!["function".to_string(), language.label().to_string()];
    if let Some(words) = identifier_words(name) {
        aliases.push(format!("{words} function"));
    }
    if exported {
        aliases.push("exported".to_string());
        aliases.push("exported function".to_string());
    }
    if name_contains_word(name, "fixture") {
        aliases.push("fixture".to_string());
        aliases.push("test fixture".to_string());
    }
    if name.contains("Config") || name_contains_word(name, "config") {
        aliases.push("config".to_string());
        aliases.push("configuration".to_string());
    }
    if is_hook_name(name) {
        aliases.push("hook".to_string());
        aliases.push("react hook".to_string());
        if let Some(words) = identifier_words(name) {
            aliases.push(format!("{words} hook"));
            aliases.push(format!("react {words} hook"));
        }
    }
    if is_component_name(name) && has_jsx(node) {
        aliases.push("component".to_string());
        aliases.push("react component".to_string());
        if let Some(words) = identifier_words(name) {
            aliases.push(format!("{words} component"));
            aliases.push(format!("react {words} component"));
        }
    }
    aliases.extend(generic_code_intent_aliases(name, "", Some(node), src));
    aliases
}

pub(crate) fn ts_function_aliases_in_mod(
    name: &str,
    node: TsNode,
    src: &str,
    exported: bool,
    language: SourceLanguage,
    modpath: &str,
) -> Vec<String> {
    let mut aliases = ts_function_aliases(name, node, src, exported, language);
    if is_hook_name(name) && !modpath.contains("::hooks::") {
        aliases.retain(|a| a != "hook" && a != "react hook" && !a.ends_with(" hook"));
    }
    if is_service_mod(modpath) {
        aliases.push("service".to_string());
    }
    if is_api_client(name, modpath) {
        aliases.push("api client".to_string());
        aliases.push("endpoint".to_string());
        aliases.push("api endpoint".to_string());
        if let Some(words) = name_words(name) {
            aliases.push(format!("{words} api client"));
            aliases.push(format!("{words} endpoint"));
        }
    }
    aliases.extend(generic_code_intent_aliases(name, modpath, Some(node), src));
    aliases.sort();
    aliases.dedup();
    aliases
}

fn is_hook_name(name: &str) -> bool {
    name.strip_prefix("use")
        .and_then(|rest| rest.chars().next())
        .is_some_and(char::is_uppercase)
}

pub(crate) fn is_component_name(name: &str) -> bool {
    name.chars().next().is_some_and(char::is_uppercase)
}

pub(crate) fn ts_react_wrapper_name(value: TsNode, src: &str) -> Option<String> {
    if value.kind() != "call_expression" {
        return None;
    }
    let function = value.child_by_field_name("function")?;
    let text = &src[function.byte_range()];
    let name = text.rsplit('.').next().unwrap_or(text);
    matches!(name, "memo" | "forwardRef" | "observer" | "lazy").then(|| name.to_string())
}

pub(crate) fn ts_type_aliases_in_mod(
    name: &str,
    keyword: &str,
    exported: bool,
    language: SourceLanguage,
    modpath: &str,
) -> Vec<String> {
    let mut aliases = vec![keyword.to_string(), language.label().to_string()];
    if let Some(words) = identifier_words(name) {
        aliases.push(format!("{words} {keyword}"));
        if keyword != "type" {
            aliases.push(format!("{words} type"));
        }
        if is_shared_mod(modpath) {
            aliases.push(format!("shared {words} {keyword}"));
            aliases.push(format!("shared {words} type"));
            aliases.push(format!("shared {keyword} {words}"));
            aliases.push(format!("shared {keyword} for {words}"));
            aliases.push(format!("shared type {words}"));
            aliases.push(format!("shared type for {words}"));
        }
    }
    if exported {
        aliases.push("exported".to_string());
        aliases.push(format!("exported {keyword}"));
    }
    if is_model_mod(modpath) {
        aliases.push("model".to_string());
    }
    aliases.extend(generic_code_intent_aliases(name, modpath, None, ""));
    aliases.sort();
    aliases.dedup();
    aliases
}

fn is_service_mod(modpath: &str) -> bool {
    modpath.split("::").any(|part| {
        matches!(part, "service" | "services") || part_has_token(part, &["service", "services"])
    })
}

fn is_model_mod(modpath: &str) -> bool {
    modpath.split("::").any(|part| {
        matches!(part, "model" | "models") || part_has_token(part, &["model", "models"])
    })
}

fn is_shared_mod(modpath: &str) -> bool {
    modpath
        .split("::")
        .any(|part| matches!(part, "shared" | "common"))
}

fn is_api_client(name: &str, modpath: &str) -> bool {
    let parts: Vec<&str> = modpath.split("::").collect();
    let in_api = parts.contains(&"api");
    let client_file = parts
        .last()
        .is_some_and(|part| matches!(*part, "client" | "api_client"));
    let client_name = name.ends_with("Client") || name.starts_with("fetch");
    in_api && (client_file || client_name)
}

fn part_has_token(part: &str, wanted: &[&str]) -> bool {
    part.split(['_', '-', '.'])
        .any(|token| wanted.contains(&token))
}

fn name_contains_word(name: &str, want: &str) -> bool {
    name_words(name).is_some_and(|words| words.split_whitespace().any(|word| word == want))
}

fn name_words(name: &str) -> Option<String> {
    let mut words = Vec::new();
    let mut current = String::new();
    let mut prev_lower_or_digit = false;
    for ch in name.chars() {
        if ch == '_' || ch == '-' {
            if !current.is_empty() {
                words.push(std::mem::take(&mut current));
            }
            prev_lower_or_digit = false;
            continue;
        }
        if ch.is_uppercase() && prev_lower_or_digit && !current.is_empty() {
            words.push(std::mem::take(&mut current));
        }
        current.push(ch.to_ascii_lowercase());
        prev_lower_or_digit = ch.is_lowercase() || ch.is_ascii_digit();
    }
    if !current.is_empty() {
        words.push(current);
    }
    (words.len() >= 2).then(|| words.join(" "))
}

fn has_jsx(node: TsNode) -> bool {
    if matches!(
        node.kind(),
        "jsx_element" | "jsx_self_closing_element" | "jsx_fragment"
    ) {
        return true;
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if has_jsx(child) {
            return true;
        }
    }
    false
}
