use std::collections::HashMap;

use eidos_kernel::schema::relation::REFERENCES;
use eidos_kernel::schema::{Edge, Node};

const REFERENCE_ALIAS_CAP: usize = 32;

/// Add retrieval-only aliases from existing `references` edges.
///
/// These aliases intentionally say "reference evidence": the edges are lexical/import evidence,
/// not type-checked calls. The goal is to let queries that combine a symbol kind with a referenced
/// kind ("component uses hook", "route service") rank the connected candidates without pretending
/// Glyph proved runtime behavior.
pub(crate) fn annotate_reference_evidence(nodes: &mut [Node], edges: &[Edge]) {
    let by_id: HashMap<String, usize> = nodes
        .iter()
        .enumerate()
        .map(|(idx, node)| (node.id.clone(), idx))
        .collect();
    let mut additions: Vec<Vec<String>> = vec![Vec::new(); nodes.len()];

    for edge in edges.iter().filter(|e| e.relation == REFERENCES) {
        let (Some(&source_idx), Some(&target_idx)) = (by_id.get(&edge.from), by_id.get(&edge.to))
        else {
            continue;
        };
        let source = &nodes[source_idx];
        let target = &nodes[target_idx];

        for alias in reference_signal_aliases(target) {
            push_capped(&mut additions[source_idx], &format!("references {alias}"));
            push_capped(
                &mut additions[source_idx],
                &format!("uses reference evidence {alias}"),
            );
            push_capped(&mut additions[source_idx], &format!("uses {alias}"));
        }

        for alias in reference_signal_aliases(source) {
            push_capped(
                &mut additions[target_idx],
                &format!("referenced by {alias}"),
            );
        }
    }

    for (node, mut add) in nodes.iter_mut().zip(additions) {
        add.sort();
        add.dedup();
        node.aliases.extend(add);
        node.aliases.sort();
        node.aliases.dedup();
    }
}

fn reference_signal_aliases(node: &Node) -> Vec<String> {
    let mut out = Vec::new();
    let title_words = name_words(&node.title);

    for alias in node.aliases.iter().map(|a| a.to_lowercase()) {
        if is_reference_signal_alias(&alias) {
            push_capped(&mut out, &alias);
            if let Some(words) = title_words.as_deref()
                && should_pair_title_with_signal(&alias)
            {
                push_capped(&mut out, &format!("{words} {alias}"));
            }
        }
    }

    out.sort();
    out.dedup();
    out
}

fn is_reference_signal_alias(alias: &str) -> bool {
    matches!(
        alias,
        "react component"
            | "react hook"
            | "react wrapper"
            | "command"
            | "command handler"
            | "cli command"
            | "registered command"
            | "tool"
            | "tool handler"
            | "registered tool"
            | "fastapi route"
            | "endpoint"
            | "api endpoint"
            | "api route"
            | "route"
            | "route handler"
            | "handler"
            | "service"
            | "model"
            | "pydantic model"
            | "api client"
            | "interface"
            | "frontend"
            | "backend"
            | "shared"
            | "test"
            | "tests"
            | "fixture"
            | "test fixture"
            | "cli"
            | "api"
            | "db"
            | "database"
            | "config"
            | "configuration"
            | "config module"
    )
}

fn should_pair_title_with_signal(alias: &str) -> bool {
    !matches!(
        alias,
        "frontend"
            | "backend"
            | "shared"
            | "test"
            | "tests"
            | "cli"
            | "api"
            | "db"
            | "database"
            | "config"
            | "configuration"
    )
}

fn push_capped(out: &mut Vec<String>, value: &str) {
    if out.len() < REFERENCE_ALIAS_CAP && !out.iter().any(|v| v == value) {
        out.push(value.to_string());
    }
}

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);
    }
    if matches!(words.first().map(String::as_str), Some("use")) && words.len() > 1 {
        words.remove(0);
    }
    (words.len() >= 2).then(|| words.join(" "))
}
