use tree_sitter::Node as TsNode;

use crate::glyph::graph::{ImportMap, ImportTarget, RefName};

pub(crate) fn import_ref(name: &str, imports: &ImportMap) -> RefName {
    imports.get(name).map_or_else(
        || RefName::lexical(name.to_string()),
        |target| RefName {
            name: target.name.clone(),
            imported_mod: Some(target.module.clone()),
            receiver_type: None,
        },
    )
}

pub(crate) fn collect_python_imports(root: TsNode, src: &str) -> ImportMap {
    let mut imports = ImportMap::new();
    collect_python_imports_inner(root, src, &mut imports);
    imports
}

fn collect_python_imports_inner(node: TsNode, src: &str, imports: &mut ImportMap) {
    if node.kind() == "import_from_statement"
        && let Some(module) = node.child_by_field_name("module_name")
    {
        let module_path = src[module.byte_range()].replace('.', "::");
        let mut cursor = node.walk();
        for name in node.children_by_field_name("name", &mut cursor) {
            match name.kind() {
                "aliased_import" => {
                    if let Some(raw_name) = name.child_by_field_name("name") {
                        let target_name = dotted_last(raw_name, src);
                        let local = name.child_by_field_name("alias").map_or_else(
                            || target_name.clone(),
                            |alias| src[alias.byte_range()].to_string(),
                        );
                        imports.insert(
                            local,
                            ImportTarget {
                                name: target_name,
                                module: module_path.clone(),
                            },
                        );
                    }
                }
                "dotted_name" => {
                    let target_name = dotted_last(name, src);
                    imports.insert(
                        target_name.clone(),
                        ImportTarget {
                            name: target_name,
                            module: module_path.clone(),
                        },
                    );
                }
                _ => {}
            }
        }
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        collect_python_imports_inner(child, src, imports);
    }
}

fn dotted_last(node: TsNode, src: &str) -> String {
    src[node.byte_range()]
        .rsplit('.')
        .next()
        .unwrap_or_default()
        .to_string()
}

/// Collect the `use` declarations of a Rust file into an import map (local name → target
/// symbol + module path). This is what lets a cross-module `references` edge band ImportAware
/// (earned Resolved) instead of GlobalUnique (a coincidental-name guess). Handles the common
/// forms: `use a::b::C;`, `use a::b::C as D;`, `use a::{C, D as E};`, nested `use a::{b::C, d};`.
/// Glob (`use a::*;`) is intentionally skipped — it binds no specific name, so it cannot
/// attribute a reference to a target.
pub(crate) fn collect_rust_imports(root: TsNode, src: &str) -> ImportMap {
    let mut imports = ImportMap::new();
    collect_rust_imports_inner(root, src, &mut imports);
    imports
}

fn collect_rust_imports_inner(node: TsNode, src: &str, imports: &mut ImportMap) {
    if node.kind() == "use_declaration"
        && let Some(arg) = node.child_by_field_name("argument")
    {
        collect_rust_use_tree(arg, src, &[], imports);
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        collect_rust_imports_inner(child, src, imports);
    }
}

/// Walk a Rust use-tree, accumulating the module path prefix. `prefix` holds the `::`-separated
/// module segments seen so far (e.g. `["crate", "sched"]` inside `use crate::sched::{...}`).
fn collect_rust_use_tree(node: TsNode, src: &str, prefix: &[String], imports: &mut ImportMap) {
    match node.kind() {
        // `a::b::C` or `a::b::{...}` — split leading path segments from the final name/list.
        "scoped_identifier" => {
            if let Some((mods, last)) = split_scoped(node, src) {
                let mut full = prefix.to_vec();
                full.extend(mods);
                insert_rust_import(&full, &last, &last, imports);
            }
        }
        "scoped_use_list" => {
            let mut full = prefix.to_vec();
            if let Some(path) = node.child_by_field_name("path") {
                full.extend(path_segments(path, src));
            }
            if let Some(list) = node.child_by_field_name("list") {
                let mut cursor = list.walk();
                for child in list.children(&mut cursor) {
                    if child.is_named() {
                        collect_rust_use_tree(child, src, &full, imports);
                    }
                }
            }
        }
        "use_list" => {
            let mut cursor = node.walk();
            for child in node.children(&mut cursor) {
                if child.is_named() {
                    collect_rust_use_tree(child, src, prefix, imports);
                }
            }
        }
        // `C as D` — bind local `D` to target `C`.
        "use_as_clause" => {
            let target = node
                .child_by_field_name("path")
                .map(|p| final_segment(p, src));
            let alias = node
                .child_by_field_name("alias")
                .map(|a| src[a.byte_range()].to_string());
            if let (Some(target), Some(alias)) = (target, alias) {
                let (mods, name) =
                    last_path_and_mods(node.child_by_field_name("path"), src, &target);
                let mut full = prefix.to_vec();
                full.extend(mods);
                insert_rust_import(&full, &name, &alias, imports);
            }
        }
        // A bare final `identifier` in a list — `use a::{B, C}` → the `C`.
        "identifier" => {
            let name = src[node.byte_range()].to_string();
            insert_rust_import(prefix, &name, &name, imports);
        }
        _ => {}
    }
}

fn insert_rust_import(mods: &[String], target: &str, local: &str, imports: &mut ImportMap) {
    if target.is_empty() {
        return;
    }
    imports.insert(
        local.to_string(),
        ImportTarget {
            name: target.to_string(),
            module: mods.join("::"),
        },
    );
}

/// Split a `scoped_identifier` into (leading module segments, final name).
fn split_scoped(node: TsNode, src: &str) -> Option<(Vec<String>, String)> {
    let name = node.child_by_field_name("name")?;
    let last = src[name.byte_range()].to_string();
    let mut mods = Vec::new();
    if let Some(path) = node.child_by_field_name("path") {
        mods = path_segments(path, src);
    }
    Some((mods, last))
}

/// The `::`-separated segments of a path node (`a::b::c` → ["a","b","c"]).
fn path_segments(node: TsNode, src: &str) -> Vec<String> {
    src[node.byte_range()]
        .split("::")
        .map(|s| s.trim().to_string())
        .filter(|s| !s.is_empty())
        .collect()
}

fn final_segment(node: TsNode, src: &str) -> String {
    src[node.byte_range()]
        .rsplit("::")
        .next()
        .unwrap_or_default()
        .trim()
        .to_string()
}

fn last_path_and_mods(path: Option<TsNode>, src: &str, fallback: &str) -> (Vec<String>, String) {
    let Some(path) = path else {
        return (Vec::new(), fallback.to_string());
    };
    let mut segs = path_segments(path, src);
    let name = segs.pop().unwrap_or_else(|| fallback.to_string());
    (segs, name)
}

pub(crate) fn collect_ts_imports(root: TsNode, src: &str, current_modpath: &str) -> ImportMap {
    let mut imports = ImportMap::new();
    collect_ts_imports_inner(root, src, current_modpath, &mut imports);
    imports
}

fn collect_ts_imports_inner(
    node: TsNode,
    src: &str,
    current_modpath: &str,
    imports: &mut ImportMap,
) {
    if node.kind() == "import_statement"
        && let Some(source) = node.child_by_field_name("source")
    {
        let source_text = strip_js_string(&src[source.byte_range()]);
        if let Some(module) = resolve_relative_ts_module(current_modpath, &source_text) {
            collect_ts_named_imports(node, src, &module, imports);
        }
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        collect_ts_imports_inner(child, src, current_modpath, imports);
    }
}

fn collect_ts_named_imports(node: TsNode, src: &str, module: &str, imports: &mut ImportMap) {
    if node.kind() == "import_specifier"
        && let Some(name_node) = node.child_by_field_name("name")
    {
        let target_name = strip_js_string(&src[name_node.byte_range()]);
        let local = node.child_by_field_name("alias").map_or_else(
            || target_name.clone(),
            |alias| src[alias.byte_range()].to_string(),
        );
        imports.insert(
            local,
            ImportTarget {
                name: target_name,
                module: module.to_string(),
            },
        );
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        collect_ts_named_imports(child, src, module, imports);
    }
}

fn resolve_relative_ts_module(current_modpath: &str, source: &str) -> Option<String> {
    if !source.starts_with('.') {
        return None;
    }
    let mut parts: Vec<&str> = current_modpath.split("::").collect();
    parts.pop();
    for part in source.split('/') {
        match part {
            "." | "" => {}
            ".." => {
                parts.pop()?;
            }
            other => parts.push(other),
        }
    }
    Some(parts.join("::"))
}

fn strip_js_string(raw: &str) -> String {
    raw.trim().trim_matches('"').trim_matches('\'').to_string()
}
