use tree_sitter::Node as TsNode;

pub(crate) fn generic_code_intent_aliases(
    name: &str,
    modpath: &str,
    span_node: Option<TsNode>,
    src: &str,
) -> Vec<String> {
    let mut aliases = Vec::new();
    let tokens = evidence_tokens(name, modpath);
    let body = span_node.map(|node| &src[node.byte_range()]);
    let words = identifier_words(name);

    if let Some(words) = words.as_deref() {
        push(&mut aliases, &format!("{words} implementation"));
        push(&mut aliases, &format!("{words} code"));
        push(&mut aliases, &format!("{words} source"));
    }

    if has_any(
        &tokens,
        &["command", "commands", "cmd", "cli", "subcommand"],
    ) || body_contains_any(body, &["registerCommand"])
    {
        push(&mut aliases, "command");
        push(&mut aliases, "command handler");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} command"));
            push(&mut aliases, &format!("{words} command handler"));
        }
    }

    if has_any(
        &tokens,
        &["cli", "cmd", "command", "commands", "subcommand"],
    ) {
        push(&mut aliases, "cli command");
        push(&mut aliases, "cli handler");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} cli command"));
        }
    }

    if has_any(&tokens, &["handler", "handlers"]) {
        push(&mut aliases, "handler");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} handler"));
        }
    }

    if body_contains_any(body, &["registerCommand"]) {
        push(&mut aliases, "registered command");
        push(&mut aliases, "command registration");
    }

    if has_any(&tokens, &["tool", "tools"]) || body_contains_any(body, &["registerTool"]) {
        push(&mut aliases, "tool");
        push(&mut aliases, "tool handler");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} tool handler"));
        }
    }

    if body_contains_any(body, &["registerTool"]) {
        push(&mut aliases, "registered tool");
        push(&mut aliases, "tool registration");
    }

    if has_any(&tokens, &["api", "endpoint", "endpoints"]) {
        push(&mut aliases, "api");
        push(&mut aliases, "endpoint");
        push(&mut aliases, "api endpoint");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} endpoint"));
            push(&mut aliases, &format!("{words} api endpoint"));
        }
    }

    if has_any(&tokens, &["route", "routes", "router"]) {
        push(&mut aliases, "route");
        push(&mut aliases, "route handler");
        push(&mut aliases, "api route");
        push(&mut aliases, "endpoint");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} route"));
            push(&mut aliases, &format!("{words} route handler"));
            push(&mut aliases, &format!("{words} endpoint"));
        }
    }

    if has_any(&tokens, &["service", "services"]) {
        push(&mut aliases, "service");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} service"));
        }
    }

    if has_any(&tokens, &["model", "models", "schema", "schemas"]) {
        push(&mut aliases, "model");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} model"));
        }
    }

    if has_any(&tokens, &["config", "configs", "configuration", "settings"]) {
        push(&mut aliases, "config");
        push(&mut aliases, "configuration");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} config"));
        }
    }

    if has_any(&tokens, &["test", "tests", "spec", "fixture"]) {
        push(&mut aliases, "test");
        push(&mut aliases, "tests");
        if let Some(words) = words.as_deref() {
            push(&mut aliases, &format!("{words} test"));
        }
    }

    aliases.sort();
    aliases.dedup();
    aliases
}

pub(crate) fn identifier_words(name: &str) -> Option<String> {
    let words = split_identifier(name);
    (words.len() >= 2).then(|| words.join(" "))
}

fn evidence_tokens(name: &str, modpath: &str) -> Vec<String> {
    let mut tokens = Vec::new();
    for part in std::iter::once(name).chain(modpath.split("::")) {
        for token in split_identifier(part) {
            if !tokens.iter().any(|seen| seen == &token) {
                tokens.push(token);
            }
        }
    }
    tokens
}

fn split_identifier(value: &str) -> Vec<String> {
    let mut words = Vec::new();
    let mut current = String::new();
    let mut prev_lower_or_digit = false;
    for ch in value.chars() {
        if !ch.is_alphanumeric() {
            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
}

fn has_any(tokens: &[String], wanted: &[&str]) -> bool {
    tokens
        .iter()
        .any(|token| wanted.iter().any(|want| token == want))
}

fn body_contains_any(body: Option<&str>, wanted: &[&str]) -> bool {
    body.is_some_and(|body| wanted.iter().any(|needle| body.contains(needle)))
}

fn push(aliases: &mut Vec<String>, alias: &str) {
    if !aliases.iter().any(|existing| existing == alias) {
        aliases.push(alias.to_string());
    }
}
