//! Rust language adapter: symbol extraction, impl-block heuristics, and navigation hints.

#[cfg(test)]
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::path::Path;

use tree_sitter::Node;

use crate::indexer::{SymbolDefinition, SymbolKind, byte_offset_for_line_column};

use super::registry::{SymbolLanguage, node_field_text, node_name_text, parser_for_path};

/// Candidate implementation relation found without SCIP precise coverage.
#[derive(Debug, Clone)]
pub(crate) struct RustImplementationMatchCandidate<'a> {
    pub(crate) source_symbol: &'a SymbolDefinition,
    pub(crate) symbol: String,
    pub(crate) relation: &'static str,
    pub(crate) fallback_reason: &'static str,
}

/// Parsed Rust query context used to bias navigation toward same-file methods and modules.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct RustNavigationQueryHint {
    pub(crate) symbol_query: String,
    pub(crate) prefer_same_file: bool,
    pub(crate) prefer_method: bool,
    pub(crate) module_path_segments: Vec<String>,
    pub(crate) enclosing_trait: Option<String>,
    pub(crate) enclosing_impl_type: Option<String>,
}

/// Enclosing trait and impl metadata for one Rust query anchor.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct RustEnclosingSymbolContext {
    pub(crate) trait_name: Option<String>,
    pub(crate) impl_type: Option<String>,
    pub(crate) impl_trait: Option<String>,
}

/// Test-context flags used to keep navigation and implementation heuristics from over-selecting tests.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct RustSymbolContext {
    pub(crate) inside_test_module: bool,
    pub(crate) inside_cfg_test: bool,
    pub(crate) inside_test_fn: bool,
}

impl RustSymbolContext {
    pub(crate) fn is_test_context(&self) -> bool {
        self.inside_test_module || self.inside_cfg_test || self.inside_test_fn
    }
}

/// Kind of Rust implementation relation inferred from an `impl` block signature.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RustImplementationKind {
    ConcreteTrait,
    BlanketTrait,
    Inherent,
}

/// Indexed Rust impl-block fact used by heuristic implementation and reference fallback.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RustImplementationFact {
    pub(crate) source_symbol_index: usize,
    pub(crate) trait_name: Option<String>,
    pub(crate) self_type: String,
    pub(crate) kind: RustImplementationKind,
}

/// Rust source analysis attached to indexed symbols for navigation fallback.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct RustIndexedSourceAnalysis {
    pub(crate) symbol_contexts_by_index: Vec<(usize, RustSymbolContext)>,
    pub(crate) implementation_facts: Vec<RustImplementationFact>,
}

#[cfg(test)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct RustSourceAnalysis {
    pub(crate) symbol_contexts_by_stable_id: BTreeMap<String, RustSymbolContext>,
    pub(crate) implementation_facts: Vec<RustImplementationFact>,
}

pub(super) fn symbol_from_node(source: &str, node: Node<'_>) -> Option<(SymbolKind, String)> {
    match node.kind() {
        "mod_item" => node_name_text(node, source).map(|name| (SymbolKind::Module, name)),
        "struct_item" => node_name_text(node, source).map(|name| (SymbolKind::Struct, name)),
        "enum_item" => node_name_text(node, source).map(|name| (SymbolKind::Enum, name)),
        "trait_item" => node_name_text(node, source).map(|name| (SymbolKind::Trait, name)),
        "type_item" => node_name_text(node, source).map(|name| (SymbolKind::TypeAlias, name)),
        "const_item" => node_name_text(node, source).map(|name| (SymbolKind::Const, name)),
        "static_item" => node_name_text(node, source).map(|name| (SymbolKind::Static, name)),
        "function_item" | "function_signature_item" => {
            let kind = if rust_has_ancestor_kind(node, &["impl_item", "trait_item"]) {
                SymbolKind::Method
            } else {
                SymbolKind::Function
            };
            node_name_text(node, source).map(|name| (kind, name))
        }
        "impl_item" => {
            let implemented_type = node_field_text(node, source, "type");
            let implemented_trait = node_field_text(node, source, "trait");
            let name = match (implemented_trait, implemented_type) {
                (Some(trait_name), Some(type_name)) => format!("impl {trait_name} for {type_name}"),
                (None, Some(type_name)) => format!("impl {type_name}"),
                _ => "impl".to_string(),
            };
            Some((SymbolKind::Impl, name))
        }
        _ => None,
    }
}

pub(crate) fn parse_impl_signature(symbol_name: &str) -> Option<(Option<&str>, &str)> {
    let body = symbol_name.strip_prefix("impl ")?;
    if let Some((implemented_trait, implementing_type)) = body.split_once(" for ") {
        let implemented_trait = implemented_trait.trim();
        let implementing_type = implementing_type.trim();
        if implemented_trait.is_empty() || implementing_type.is_empty() {
            return None;
        }
        return Some((Some(implemented_trait), implementing_type));
    }
    let implementing_type = body.trim();
    if implementing_type.is_empty() {
        return None;
    }
    Some((None, implementing_type))
}

pub(crate) fn source_suffix_looks_like_call(mut suffix: &str) -> bool {
    suffix = suffix.trim_start_matches([' ', '\t']);
    suffix = suffix.trim_start_matches(|ch: char| ch.is_ascii_alphanumeric() || ch == '_');
    suffix = suffix.trim_start_matches([' ', '\t']);
    if suffix.starts_with('(') {
        return true;
    }
    if !suffix.starts_with("::") {
        return false;
    }

    suffix = suffix[2..].trim_start_matches([' ', '\t']);
    if !suffix.starts_with('<') {
        return false;
    }

    let mut depth = 0usize;
    let mut end_index = None;
    for (index, ch) in suffix.char_indices() {
        match ch {
            '<' => depth = depth.saturating_add(1),
            '>' => {
                depth = depth.saturating_sub(1);
                if depth == 0 {
                    end_index = Some(index + ch.len_utf8());
                    break;
                }
            }
            _ => {}
        }
    }
    let Some(end_index) = end_index else {
        return false;
    };
    suffix[end_index..]
        .trim_start_matches([' ', '\t'])
        .starts_with('(')
}

pub(crate) fn heuristic_implementation_candidates<'a>(
    target_symbol: &'a SymbolDefinition,
    symbols: &'a [SymbolDefinition],
) -> Vec<RustImplementationMatchCandidate<'a>> {
    let target_name = target_symbol.name.trim();
    if target_name.is_empty() {
        return Vec::new();
    }

    let mut matches = Vec::new();
    for symbol in symbols {
        if symbol.kind != SymbolKind::Impl {
            continue;
        }

        let impl_symbol_name = symbol.name.trim();
        if impl_symbol_name.is_empty() {
            continue;
        }

        let mut relation = "implementation";
        let matched_display_name = if let Some((implemented_trait, implementing_type)) =
            parse_impl_signature(impl_symbol_name)
        {
            if let Some(implemented_trait) = implemented_trait {
                if implemented_trait.eq_ignore_ascii_case(target_name) {
                    relation = "implements";
                    implementing_type.to_owned()
                } else if implementing_type.eq_ignore_ascii_case(target_name) {
                    impl_symbol_name.to_owned()
                } else {
                    continue;
                }
            } else if implementing_type.eq_ignore_ascii_case(target_name) {
                impl_symbol_name.to_owned()
            } else {
                continue;
            }
        } else {
            continue;
        };

        matches.push(RustImplementationMatchCandidate {
            source_symbol: symbol,
            symbol: matched_display_name,
            relation,
            fallback_reason: "precise_absent",
        });
    }

    matches
}

#[cfg(test)]
pub(crate) fn analyze_source(source: &str, symbols: &[SymbolDefinition]) -> RustSourceAnalysis {
    let symbol_indices = (0..symbols.len()).collect::<Vec<_>>();
    let indexed = analyze_indexed_source(source, symbols, &symbol_indices);
    let symbol_contexts_by_stable_id = indexed
        .symbol_contexts_by_index
        .into_iter()
        .filter_map(|(index, context)| {
            symbols
                .get(index)
                .map(|symbol| (symbol.stable_id.clone(), context))
        })
        .collect();

    RustSourceAnalysis {
        symbol_contexts_by_stable_id,
        implementation_facts: indexed.implementation_facts,
    }
}

pub(crate) fn analyze_indexed_source(
    source: &str,
    symbols: &[SymbolDefinition],
    symbol_indices: &[usize],
) -> RustIndexedSourceAnalysis {
    #[derive(Clone, Copy)]
    struct IndexedSymbolEntry {
        index: usize,
        start: usize,
        end: usize,
        self_context: RustSymbolContext,
    }

    #[derive(Clone, Copy)]
    struct ContextStackEntry {
        start: usize,
        end: usize,
        context: RustSymbolContext,
    }

    let mut entries = Vec::with_capacity(symbol_indices.len());
    let mut implementation_facts = Vec::new();
    for &symbol_index in symbol_indices {
        let Some(symbol) = symbols.get(symbol_index) else {
            continue;
        };
        entries.push(IndexedSymbolEntry {
            index: symbol_index,
            start: symbol.span.start_byte,
            end: symbol.span.end_byte,
            self_context: rust_symbol_self_context(source, symbol),
        });
        if symbol.kind == SymbolKind::Impl
            && let Some(fact) = rust_implementation_fact_for_symbol(source, symbol, symbol_index)
        {
            implementation_facts.push(fact);
        }
    }
    entries.sort_by(|left, right| {
        left.start
            .cmp(&right.start)
            .then(right.end.cmp(&left.end))
            .then(left.index.cmp(&right.index))
    });

    let mut stack = Vec::<ContextStackEntry>::new();
    let mut symbol_contexts_by_index = Vec::new();
    for entry in entries {
        while let Some(ancestor) = stack.last() {
            if ancestor.start <= entry.start && ancestor.end >= entry.end {
                break;
            }
            stack.pop();
        }

        let mut context = stack.last().map(|entry| entry.context).unwrap_or_default();
        context.inside_test_module |= entry.self_context.inside_test_module;
        context.inside_cfg_test |= entry.self_context.inside_cfg_test;
        context.inside_test_fn |= entry.self_context.inside_test_fn;
        if context.is_test_context() {
            symbol_contexts_by_index.push((entry.index, context));
        }
        stack.push(ContextStackEntry {
            start: entry.start,
            end: entry.end,
            context,
        });
    }

    RustIndexedSourceAnalysis {
        symbol_contexts_by_index,
        implementation_facts,
    }
}

pub(crate) fn implementation_candidates_from_facts<'a>(
    target_symbol: &'a SymbolDefinition,
    symbols: &'a [SymbolDefinition],
    facts: &'a [RustImplementationFact],
) -> Vec<RustImplementationMatchCandidate<'a>> {
    let target_name = target_symbol.name.trim();
    if target_name.is_empty() {
        return Vec::new();
    }

    let mut matches = Vec::new();
    let target_is_trait = target_symbol.kind == SymbolKind::Trait;

    for fact in facts {
        let Some(source_symbol) = symbols.get(fact.source_symbol_index) else {
            continue;
        };

        if target_is_trait {
            let Some(implemented_trait) = fact.trait_name.as_deref() else {
                continue;
            };
            if !implemented_trait.eq_ignore_ascii_case(target_name) {
                continue;
            }

            matches.push(RustImplementationMatchCandidate {
                source_symbol,
                symbol: fact.self_type.clone(),
                relation: match fact.kind {
                    RustImplementationKind::ConcreteTrait => "implements",
                    RustImplementationKind::BlanketTrait => "implements_blanket",
                    RustImplementationKind::Inherent => "implements",
                },
                fallback_reason: "precise_absent_rust_impl_index",
            });
            continue;
        }

        if !rust_type_matches_target(&fact.self_type, target_name) {
            continue;
        }

        matches.push(RustImplementationMatchCandidate {
            source_symbol,
            symbol: source_symbol.name.clone(),
            relation: match fact.kind {
                RustImplementationKind::ConcreteTrait => "implementation",
                RustImplementationKind::BlanketTrait => "implementation_blanket",
                RustImplementationKind::Inherent => "inherent_impl",
            },
            fallback_reason: "precise_absent_rust_impl_index",
        });
    }

    matches
}

pub(crate) fn navigation_query_hint_from_source(
    path: &Path,
    source: &str,
    line: usize,
    column: usize,
) -> Option<RustNavigationQueryHint> {
    let mut parser = parser_for_path(SymbolLanguage::Rust, path).ok()?;
    let tree = parser.parse(source, None)?;
    let offset = byte_offset_for_line_column(source, line, column)?;
    let focus = rust_focus_node_for_offset(tree.root_node(), offset);

    let mut ancestors = Vec::new();
    let mut cursor = Some(focus);
    while let Some(node) = cursor {
        ancestors.push(node);
        cursor = node.parent();
    }

    let mut hint = rust_use_query_hint_from_focus(focus, source);
    if hint.is_none() {
        for node in &ancestors {
            if let Some(candidate) = rust_location_query_hint_for_node(*node, focus, source) {
                hint = Some(candidate);
                break;
            }
        }
    }

    let mut hint = hint?;
    for node in &ancestors {
        match node.kind() {
            "trait_item" if hint.enclosing_trait.is_none() => {
                hint.enclosing_trait = node_name_text(*node, source);
            }
            "impl_item" if hint.enclosing_impl_type.is_none() => {
                hint.enclosing_impl_type = node_field_text(*node, source, "type");
                if hint.enclosing_trait.is_none() {
                    hint.enclosing_trait = node_field_text(*node, source, "trait");
                }
            }
            _ => {}
        }
    }

    Some(hint)
}

pub(crate) fn relative_path_module_segments(relative_path: &str) -> Vec<String> {
    let path = Path::new(relative_path);
    let mut segments = path
        .iter()
        .map(|segment| segment.to_string_lossy().into_owned())
        .collect::<Vec<_>>();
    if segments.is_empty() {
        return segments;
    }

    if matches!(
        segments.first().map(String::as_str),
        Some("src" | "tests" | "examples" | "benches")
    ) {
        segments.remove(0);
    }

    let Some(file_name) = segments.pop() else {
        return segments;
    };
    let stem = Path::new(&file_name)
        .file_stem()
        .and_then(|value| value.to_str())
        .unwrap_or_default();
    if !stem.is_empty() && !matches!(stem, "lib" | "main" | "mod") {
        segments.push(stem.to_owned());
    }
    segments
}

pub(crate) fn enclosing_symbol_context(
    symbol: &SymbolDefinition,
    symbols: &[SymbolDefinition],
) -> RustEnclosingSymbolContext {
    let mut trait_name = None;
    let mut impl_type = None;
    let mut impl_trait = None;
    let mut best_trait_span = usize::MAX;
    let mut best_impl_span = usize::MAX;

    for candidate in symbols {
        if candidate.path != symbol.path {
            continue;
        }
        if candidate.span.start_byte > symbol.span.start_byte
            || candidate.span.end_byte < symbol.span.end_byte
        {
            continue;
        }
        let span_len = candidate
            .span
            .end_byte
            .saturating_sub(candidate.span.start_byte);
        match candidate.kind {
            SymbolKind::Trait if span_len < best_trait_span => {
                best_trait_span = span_len;
                trait_name = Some(candidate.name.clone());
            }
            SymbolKind::Impl if span_len < best_impl_span => {
                best_impl_span = span_len;
                if let Some((implemented_trait, implementing_type)) =
                    parse_impl_signature(candidate.name.as_str())
                {
                    impl_type = Some(implementing_type.to_owned());
                    impl_trait = implemented_trait.map(str::to_owned);
                }
            }
            _ => {}
        }
    }

    RustEnclosingSymbolContext {
        trait_name,
        impl_type,
        impl_trait,
    }
}

fn rust_location_query_hint_for_node(
    node: Node<'_>,
    focus: Node<'_>,
    source: &str,
) -> Option<RustNavigationQueryHint> {
    match node.kind() {
        "field_expression" if focus.kind() == "field_identifier" => {
            let field = node.child_by_field_name("field")?;
            let symbol_query = rust_node_text(field, source)?;
            Some(RustNavigationQueryHint {
                symbol_query,
                prefer_same_file: true,
                prefer_method: true,
                module_path_segments: Vec::new(),
                enclosing_trait: None,
                enclosing_impl_type: None,
            })
        }
        "scoped_identifier" => {
            let segments = rust_path_segments(node, source)?;
            let symbol_query = segments.last()?.clone();
            let first = segments.first().map(String::as_str);
            let mut hint = RustNavigationQueryHint {
                symbol_query,
                prefer_same_file: matches!(first, Some("self" | "super" | "Self")),
                prefer_method: false,
                module_path_segments: Vec::new(),
                enclosing_trait: None,
                enclosing_impl_type: None,
            };
            if segments.len() > 1 {
                let prefix = &segments[..segments.len() - 1];
                if matches!(first, Some("crate" | "self" | "super")) {
                    hint.module_path_segments = prefix
                        .iter()
                        .filter(|segment| !matches!(segment.as_str(), "crate" | "self" | "super"))
                        .cloned()
                        .collect();
                } else if prefix
                    .first()
                    .and_then(|segment| segment.chars().next())
                    .is_some_and(|ch| ch.is_ascii_uppercase())
                {
                    hint.prefer_method = true;
                    hint.enclosing_impl_type = prefix.first().cloned();
                } else {
                    hint.module_path_segments = prefix.to_vec();
                }
            }
            Some(hint)
        }
        "mod_item" => node_name_text(node, source).map(|symbol_query| RustNavigationQueryHint {
            module_path_segments: vec![symbol_query.clone()],
            symbol_query,
            prefer_same_file: false,
            prefer_method: false,
            enclosing_trait: None,
            enclosing_impl_type: None,
        }),
        "identifier" | "type_identifier" | "field_identifier" => {
            rust_node_text(node, source).map(|symbol_query| RustNavigationQueryHint {
                symbol_query,
                prefer_same_file: true,
                prefer_method: node.kind() == "field_identifier",
                module_path_segments: Vec::new(),
                enclosing_trait: None,
                enclosing_impl_type: None,
            })
        }
        _ => None,
    }
}

fn rust_use_query_hint_from_focus(
    focus: Node<'_>,
    source: &str,
) -> Option<RustNavigationQueryHint> {
    let mut current = Some(focus);
    let mut segments: Option<Vec<String>> = None;
    let mut saw_use_declaration = false;

    while let Some(node) = current {
        match node.kind() {
            "use_declaration" => {
                saw_use_declaration = true;
                break;
            }
            "use_as_clause" => {
                segments = node
                    .child_by_field_name("path")
                    .and_then(|path| rust_path_segments(path, source));
            }
            "scoped_identifier" if segments.as_ref().is_none_or(|existing| existing.len() <= 1) => {
                segments = rust_path_segments(node, source);
            }
            "identifier" | "type_identifier" | "crate" | "self" | "super" if segments.is_none() => {
                segments = rust_node_text(node, source).map(|value| vec![value]);
            }
            "scoped_use_list" => {
                if let Some(prefix) = node
                    .child_by_field_name("path")
                    .and_then(|path| rust_path_segments(path, source))
                    && let Some(existing) = segments.as_mut()
                    && !existing.starts_with(prefix.as_slice())
                {
                    let mut merged = prefix;
                    merged.extend(existing.iter().cloned());
                    *existing = merged;
                }
            }
            _ => {}
        }
        current = node.parent();
    }

    if !saw_use_declaration {
        return None;
    }
    let segments = segments?;
    let symbol_query = segments
        .last()
        .filter(|segment| !matches!(segment.as_str(), "crate" | "self" | "super"))?
        .clone();
    let module_path_segments = segments[..segments.len().saturating_sub(1)]
        .iter()
        .filter(|segment| !matches!(segment.as_str(), "crate" | "self" | "super"))
        .cloned()
        .collect::<Vec<_>>();
    Some(RustNavigationQueryHint {
        symbol_query,
        prefer_same_file: false,
        prefer_method: false,
        module_path_segments,
        enclosing_trait: None,
        enclosing_impl_type: None,
    })
}

fn rust_path_segments(node: Node<'_>, source: &str) -> Option<Vec<String>> {
    match node.kind() {
        "scoped_identifier" => {
            let mut segments = node
                .child_by_field_name("path")
                .and_then(|path| rust_path_segments(path, source))
                .unwrap_or_default();
            let name = node.child_by_field_name("name")?;
            segments.push(rust_node_text(name, source)?);
            Some(segments)
        }
        "use_as_clause" => node
            .child_by_field_name("path")
            .and_then(|path| rust_path_segments(path, source)),
        "identifier" | "type_identifier" | "field_identifier" | "crate" | "self" | "super" => {
            rust_node_text(node, source).map(|value| vec![value])
        }
        _ => None,
    }
}

fn rust_node_text(node: Node<'_>, source: &str) -> Option<String> {
    node.utf8_text(source.as_bytes())
        .ok()
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(str::to_owned)
}

fn rust_focus_node_for_offset(root: Node<'_>, offset: usize) -> Node<'_> {
    let mut current = root;
    loop {
        let mut next_named = None;
        let mut next_any = None;
        let mut cursor = current.walk();
        for child in current.children(&mut cursor) {
            if child.start_byte() > offset || child.end_byte() < offset {
                continue;
            }
            if next_any.is_none() {
                next_any = Some(child);
            }
            if child.is_named() {
                next_named = Some(child);
                break;
            }
        }
        let Some(next) = next_named.or(next_any) else {
            break;
        };
        if next == current {
            break;
        }
        current = next;
    }
    current
}

fn rust_symbol_self_context(source: &str, symbol: &SymbolDefinition) -> RustSymbolContext {
    let snippet = source_for_symbol_span(source, symbol);
    let leading = source_before_symbol(source, symbol);
    RustSymbolContext {
        inside_test_module: symbol.kind == SymbolKind::Module
            && rust_module_looks_like_test(symbol.name.as_str(), snippet, leading),
        inside_cfg_test: snippet.contains("#[cfg(test") || leading.contains("#[cfg(test"),
        inside_test_fn: matches!(symbol.kind, SymbolKind::Function | SymbolKind::Method)
            && rust_function_looks_like_test(symbol.name.as_str(), snippet, leading),
    }
}

fn rust_implementation_fact_for_symbol(
    source: &str,
    symbol: &SymbolDefinition,
    source_symbol_index: usize,
) -> Option<RustImplementationFact> {
    let (trait_name, self_type) = parse_impl_signature(symbol.name.as_str())?;
    let header = rust_impl_header(source_for_symbol_span(source, symbol));
    let kind = if trait_name.is_none() {
        RustImplementationKind::Inherent
    } else {
        let generic_params = rust_impl_generic_parameters(header);
        if self_type_mentions_generic_params(self_type, &generic_params) {
            RustImplementationKind::BlanketTrait
        } else {
            RustImplementationKind::ConcreteTrait
        }
    };

    Some(RustImplementationFact {
        source_symbol_index,
        trait_name: trait_name.map(str::to_owned),
        self_type: self_type.to_owned(),
        kind,
    })
}

fn source_for_symbol_span<'a>(source: &'a str, symbol: &SymbolDefinition) -> &'a str {
    let start = symbol.span.start_byte.min(source.len());
    let end = symbol.span.end_byte.min(source.len());
    if start >= end {
        return "";
    }
    source.get(start..end).unwrap_or("")
}

fn source_before_symbol<'a>(source: &'a str, symbol: &SymbolDefinition) -> &'a str {
    let start = symbol.span.start_byte.min(source.len());
    let leading_start = start.saturating_sub(128);
    source.get(leading_start..start).unwrap_or("")
}

fn rust_module_looks_like_test(name: &str, snippet: &str, leading: &str) -> bool {
    name == "tests"
        || name.ends_with("_tests")
        || snippet.contains("#[cfg(test")
        || leading.contains("#[cfg(test")
}

fn rust_function_looks_like_test(name: &str, snippet: &str, leading: &str) -> bool {
    name.starts_with("test_")
        || name.ends_with("_test")
        || snippet.contains("#[test")
        || snippet.contains("::test]")
        || leading.contains("#[test")
        || leading.contains("::test]")
}

fn rust_impl_header(snippet: &str) -> &str {
    snippet.split('{').next().unwrap_or(snippet).trim()
}

fn rust_impl_generic_parameters(header: &str) -> BTreeSet<String> {
    let mut generics = BTreeSet::new();
    let Some(after_impl) = header.strip_prefix("impl") else {
        return generics;
    };
    let after_impl = after_impl.trim_start();
    if !after_impl.starts_with('<') {
        return generics;
    }

    let mut depth = 0usize;
    let mut body = String::new();
    for ch in after_impl.chars() {
        match ch {
            '<' => {
                depth = depth.saturating_add(1);
                if depth > 1 {
                    body.push(ch);
                }
            }
            '>' => {
                depth = depth.saturating_sub(1);
                if depth == 0 {
                    break;
                }
                body.push(ch);
            }
            _ if depth > 0 => body.push(ch),
            _ => break,
        }
    }

    let mut token = String::new();
    for ch in body.chars() {
        if ch.is_ascii_alphanumeric() || ch == '_' {
            token.push(ch);
            continue;
        }
        rust_push_generic_param_token(&mut generics, &mut token);
    }
    rust_push_generic_param_token(&mut generics, &mut token);
    generics
}

fn rust_push_generic_param_token(generics: &mut BTreeSet<String>, token: &mut String) {
    if token
        .chars()
        .next()
        .is_some_and(|ch| ch.is_ascii_uppercase())
    {
        generics.insert(token.clone());
    }
    token.clear();
}

fn self_type_mentions_generic_params(self_type: &str, generic_params: &BTreeSet<String>) -> bool {
    if generic_params.is_empty() {
        return false;
    }
    let mut token = String::new();
    for ch in self_type.chars() {
        if ch.is_ascii_alphanumeric() || ch == '_' {
            token.push(ch);
            continue;
        }
        if generic_params.contains(&token) {
            return true;
        }
        token.clear();
    }
    generic_params.contains(&token)
}

fn rust_type_matches_target(self_type: &str, target_name: &str) -> bool {
    let normalized = self_type
        .split("::")
        .last()
        .unwrap_or(self_type)
        .split('<')
        .next()
        .unwrap_or(self_type)
        .trim();
    normalized.eq_ignore_ascii_case(target_name)
}

fn rust_has_ancestor_kind(node: Node<'_>, expected_kinds: &[&str]) -> bool {
    let mut cursor = node.parent();
    while let Some(parent) = cursor {
        if expected_kinds.iter().any(|kind| *kind == parent.kind()) {
            return true;
        }
        cursor = parent.parent();
    }
    false
}

#[cfg(test)]
mod tests {
    use std::path::{Path, PathBuf};

    use crate::languages::SymbolLanguage;

    use super::{
        RustEnclosingSymbolContext, RustImplementationFact, RustImplementationKind,
        RustImplementationMatchCandidate, analyze_source, enclosing_symbol_context,
        heuristic_implementation_candidates, implementation_candidates_from_facts,
        navigation_query_hint_from_source, parse_impl_signature, relative_path_module_segments,
        source_suffix_looks_like_call,
    };
    use crate::indexer::{SourceSpan, SymbolDefinition, SymbolKind};

    #[test]
    fn parse_impl_signature_handles_trait_and_inherent_impls() {
        assert_eq!(
            parse_impl_signature("impl Display for App"),
            Some((Some("Display"), "App"))
        );
        assert_eq!(parse_impl_signature("impl App"), Some((None, "App")));
        assert_eq!(parse_impl_signature("impl "), None);
    }

    #[test]
    fn rust_call_suffix_detection_accepts_direct_and_turbofish_calls() {
        assert!(source_suffix_looks_like_call("(arg)"));
        assert!(source_suffix_looks_like_call("::<T>(arg)"));
        assert!(source_suffix_looks_like_call("::<Vec<String>>(arg)"));
        assert!(!source_suffix_looks_like_call("::<Vec<String>"));
        assert!(!source_suffix_looks_like_call("::VALUE"));
    }

    #[test]
    fn heuristic_implementation_candidates_match_trait_and_inherent_impls() {
        let target_trait = symbol("Display", SymbolKind::Trait, 10);
        let target_type = symbol("App", SymbolKind::Struct, 11);
        let trait_impl = symbol("impl Display for App", SymbolKind::Impl, 20);
        let inherent_impl = symbol("impl App", SymbolKind::Impl, 21);
        let unrelated_impl = symbol("impl Debug for Other", SymbolKind::Impl, 22);

        let trait_symbols = [
            trait_impl.clone(),
            inherent_impl.clone(),
            unrelated_impl.clone(),
        ];
        let trait_matches = heuristic_implementation_candidates(&target_trait, &trait_symbols);
        assert_eq!(trait_matches.len(), 1);
        assert_match(
            &trait_matches[0],
            &trait_impl,
            "App",
            "implements",
            "precise_absent",
        );

        let type_symbols = [trait_impl.clone(), inherent_impl.clone(), unrelated_impl];
        let type_matches = heuristic_implementation_candidates(&target_type, &type_symbols);
        assert_eq!(type_matches.len(), 2);
        assert_match(
            &type_matches[0],
            &trait_impl,
            "impl Display for App",
            "implementation",
            "precise_absent",
        );
        assert_match(
            &type_matches[1],
            &inherent_impl,
            "impl App",
            "implementation",
            "precise_absent",
        );
    }

    #[test]
    fn rust_source_analysis_marks_test_context_and_blanket_impls() {
        let source = "pub trait Service {}\n\
                      pub struct App;\n\
                      impl<T> Service for Wrapper<T> {}\n\
                      #[cfg(test)] mod tests {\n\
                          fn helper() {}\n\
                      }\n";
        let symbols = crate::indexer::extract_symbols_from_source(
            SymbolLanguage::Rust,
            Path::new("src/lib.rs"),
            source,
        )
        .expect("rust source should extract symbols");
        let analysis = analyze_source(source, &symbols);
        let tests_module = symbols
            .iter()
            .find(|symbol| symbol.kind == SymbolKind::Module && symbol.name == "tests")
            .expect("tests module symbol");
        let helper = symbols
            .iter()
            .find(|symbol| symbol.kind == SymbolKind::Function && symbol.name == "helper")
            .expect("helper symbol");
        let impl_fact = analysis
            .implementation_facts
            .iter()
            .find(|fact| fact.trait_name.as_deref() == Some("Service"))
            .expect("trait impl fact");

        assert!(
            analysis
                .symbol_contexts_by_stable_id
                .get(helper.stable_id.as_str())
                .expect("helper context")
                .is_test_context()
        );
        assert!(
            analysis
                .symbol_contexts_by_stable_id
                .get(tests_module.stable_id.as_str())
                .expect("tests module context")
                .inside_cfg_test
        );
        assert_eq!(impl_fact.self_type, "Wrapper<T>");
        assert_eq!(impl_fact.kind, RustImplementationKind::BlanketTrait);
    }

    #[test]
    fn rust_impl_index_candidates_distinguish_blanket_and_inherent_impls() {
        let target_trait = symbol("Service", SymbolKind::Trait, 1);
        let target_type = symbol("Wrapper", SymbolKind::Struct, 2);
        let blanket_impl = symbol("impl Service for Wrapper<T>", SymbolKind::Impl, 8);
        let inherent_impl = symbol("impl Wrapper<T>", SymbolKind::Impl, 12);
        let facts = vec![
            RustImplementationFact {
                source_symbol_index: 0,
                trait_name: Some("Service".to_owned()),
                self_type: "Wrapper<T>".to_owned(),
                kind: RustImplementationKind::BlanketTrait,
            },
            RustImplementationFact {
                source_symbol_index: 1,
                trait_name: None,
                self_type: "Wrapper<T>".to_owned(),
                kind: RustImplementationKind::Inherent,
            },
        ];
        let symbols = vec![blanket_impl.clone(), inherent_impl.clone()];

        let trait_matches = implementation_candidates_from_facts(&target_trait, &symbols, &facts);
        assert_eq!(trait_matches.len(), 1);
        assert_match(
            &trait_matches[0],
            &blanket_impl,
            "Wrapper<T>",
            "implements_blanket",
            "precise_absent_rust_impl_index",
        );

        let type_matches = implementation_candidates_from_facts(&target_type, &symbols, &facts);
        assert_eq!(type_matches.len(), 2);
        assert_match(
            &type_matches[0],
            &blanket_impl,
            "impl Service for Wrapper<T>",
            "implementation_blanket",
            "precise_absent_rust_impl_index",
        );
        assert_match(
            &type_matches[1],
            &inherent_impl,
            "impl Wrapper<T>",
            "inherent_impl",
            "precise_absent_rust_impl_index",
        );
    }

    #[test]
    fn rust_navigation_query_hint_prefers_underlying_import_path_over_alias() {
        let source = "pub use crate::worker::helper as local_helper;\nfn local_helper() {}\n";
        let hint = navigation_query_hint_from_source(Path::new("src/lib.rs"), source, 1, 35)
            .expect("rust import alias should produce a navigation hint");
        assert_eq!(hint.symbol_query, "helper");
        assert_eq!(hint.module_path_segments, vec!["worker"]);
        assert!(!hint.prefer_same_file);
    }

    #[test]
    fn rust_navigation_query_hint_prefers_methods_for_field_calls() {
        let call_line = "    fn call(&self) { self.render(); }\n";
        let source = format!("impl App {{\n    fn render(&self) {{}}\n{call_line}}}\n");
        let hint = navigation_query_hint_from_source(
            Path::new("src/lib.rs"),
            &source,
            3,
            call_line.rfind("render").expect("method token present") + 1,
        )
        .expect("field method call should produce a navigation hint");
        assert_eq!(hint.symbol_query, "render");
        assert!(hint.prefer_method);
        assert!(hint.prefer_same_file);
        assert_eq!(hint.enclosing_impl_type.as_deref(), Some("App"));
    }

    #[test]
    fn rust_relative_path_module_segments_ignore_runtime_roots_and_mod_files() {
        assert_eq!(
            relative_path_module_segments("src/worker/mod.rs"),
            vec!["worker"]
        );
        assert_eq!(
            relative_path_module_segments("src/worker/helpers.rs"),
            vec!["worker", "helpers"]
        );
        assert_eq!(
            relative_path_module_segments("src/lib.rs"),
            Vec::<String>::new()
        );
    }

    #[test]
    fn rust_enclosing_symbol_context_finds_trait_and_impl_containers() {
        let method = SymbolDefinition {
            stable_id: "method".to_owned(),
            language: SymbolLanguage::Rust,
            kind: SymbolKind::Method,
            name: "render".to_owned(),
            path: PathBuf::from("src/lib.rs"),
            line: 12,
            span: SourceSpan {
                start_byte: 120,
                end_byte: 160,
                start_line: 12,
                start_column: 1,
                end_line: 14,
                end_column: 1,
            },
        };
        let symbols = vec![
            SymbolDefinition {
                stable_id: "trait".to_owned(),
                language: SymbolLanguage::Rust,
                kind: SymbolKind::Trait,
                name: "Renderer".to_owned(),
                path: PathBuf::from("src/lib.rs"),
                line: 1,
                span: SourceSpan {
                    start_byte: 0,
                    end_byte: 240,
                    start_line: 1,
                    start_column: 1,
                    end_line: 20,
                    end_column: 1,
                },
            },
            SymbolDefinition {
                stable_id: "impl".to_owned(),
                language: SymbolLanguage::Rust,
                kind: SymbolKind::Impl,
                name: "impl Renderer for App".to_owned(),
                path: PathBuf::from("src/lib.rs"),
                line: 10,
                span: SourceSpan {
                    start_byte: 100,
                    end_byte: 220,
                    start_line: 10,
                    start_column: 1,
                    end_line: 18,
                    end_column: 1,
                },
            },
            method.clone(),
        ];

        let context = enclosing_symbol_context(&method, &symbols);
        assert_eq!(
            context,
            RustEnclosingSymbolContext {
                trait_name: Some("Renderer".to_owned()),
                impl_type: Some("App".to_owned()),
                impl_trait: Some("Renderer".to_owned()),
            }
        );
    }

    fn assert_match(
        candidate: &RustImplementationMatchCandidate<'_>,
        source_symbol: &SymbolDefinition,
        symbol: &str,
        relation: &str,
        fallback_reason: &str,
    ) {
        assert_eq!(candidate.source_symbol, source_symbol);
        assert_eq!(candidate.symbol, symbol);
        assert_eq!(candidate.relation, relation);
        assert_eq!(candidate.fallback_reason, fallback_reason);
    }

    fn symbol(name: &str, kind: SymbolKind, line: usize) -> SymbolDefinition {
        SymbolDefinition {
            stable_id: format!("{name}:{line}"),
            language: SymbolLanguage::Rust,
            kind,
            name: name.to_owned(),
            path: PathBuf::from("src/lib.rs"),
            line,
            span: SourceSpan {
                start_byte: 0,
                end_byte: 0,
                start_line: line,
                start_column: 1,
                end_line: line,
                end_column: 1,
            },
        }
    }
}
