use super::{extract_doc_comment, node_text};
use crate::types::*;

pub fn extract(tree: &tree_sitter::Tree, source: &[u8]) -> (Vec<Symbol>, Vec<Import>) {
    let root = tree.root_node();
    let mut symbols = Vec::new();
    let mut imports = Vec::new();
    let mut cursor = root.walk();

    for child in root.children(&mut cursor) {
        extract_node(&child, source, &mut symbols, &mut imports, None);
    }

    (symbols, imports)
}

fn extract_node(
    node: &tree_sitter::Node,
    source: &[u8],
    symbols: &mut Vec<Symbol>,
    imports: &mut Vec<Import>,
    current_module: Option<&str>,
) {
    match node.kind() {
        "call" => {
            extract_call(node, source, symbols, imports, current_module);
        }
        // Some tree-sitter-elixir versions use specific node types
        "def" | "defp" | "defmacro" | "defmacrop" => {
            if let Some(sym) = extract_def(node, source, current_module) {
                symbols.push(sym);
            }
        }
        "defmodule" => {
            extract_defmodule(node, source, symbols, imports);
        }
        // Walk into blocks and other wrapper nodes
        _ => {
            let mut inner = node.walk();
            for child in node.children(&mut inner) {
                extract_node(&child, source, symbols, imports, current_module);
            }
        }
    }
}

fn extract_call(
    node: &tree_sitter::Node,
    source: &[u8],
    symbols: &mut Vec<Symbol>,
    imports: &mut Vec<Import>,
    current_module: Option<&str>,
) {
    let text = node_text(node, source);
    let call_name = get_call_name(node, source);

    match call_name.as_deref() {
        Some("defmodule") => {
            extract_defmodule(node, source, symbols, imports);
        }
        Some("def") | Some("defp") => {
            if let Some(sym) = extract_def(node, source, current_module) {
                symbols.push(sym);
            }
        }
        Some("defmacro") | Some("defmacrop") => {
            if let Some(sym) = extract_macro_def(node, source, current_module) {
                symbols.push(sym);
            }
        }
        Some("defstruct") => {
            // defstruct inside a module — record as Struct
            if let Some(mod_name) = current_module {
                symbols.push(Symbol {
                    name: mod_name.to_string(),
                    kind: SymbolKind::Struct,
                    span: Span::from_node(node),
                    signature: first_line(text).to_string(),
                    doc_comment: extract_doc_comment(node, source),
                    parent: current_module.map(|s| s.to_string()),
                    children: Vec::new(),
                });
            }
        }
        Some("use") | Some("import") | Some("alias") | Some("require") => {
            let path = extract_module_ref(node, source, text);
            imports.push(Import {
                path: path.unwrap_or_else(|| text.trim().to_string()),
                alias: None,
                span: Span::from_node(node),
            });
        }
        _ => {
            // Walk children to find nested calls
            let mut inner = node.walk();
            for child in node.children(&mut inner) {
                if child.kind() == "call"
                    || child.kind() == "do_block"
                    || child.kind() == "arguments"
                {
                    extract_node(&child, source, symbols, imports, current_module);
                }
            }
        }
    }
}

fn extract_defmodule(
    node: &tree_sitter::Node,
    source: &[u8],
    symbols: &mut Vec<Symbol>,
    imports: &mut Vec<Import>,
) {
    let text = node_text(node, source);
    let module_name = extract_module_name(node, source, text);
    let name = module_name.clone().unwrap_or_else(|| "?".to_string());

    let mut children = Vec::new();
    let mut module_imports = Vec::new();

    // Walk into the do block to find defs, uses, imports
    walk_do_block(
        node,
        source,
        &mut children,
        &mut module_imports,
        Some(&name),
    );

    imports.extend(module_imports);

    symbols.push(Symbol {
        name,
        kind: SymbolKind::Module,
        span: Span::from_node(node),
        signature: first_line(text).to_string(),
        doc_comment: extract_doc_comment(node, source),
        parent: None,
        children,
    });
}

fn walk_do_block(
    node: &tree_sitter::Node,
    source: &[u8],
    symbols: &mut Vec<Symbol>,
    imports: &mut Vec<Import>,
    current_module: Option<&str>,
) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "do_block" | "do" | "body" | "arguments" | "stab_clause" => {
                walk_do_block(&child, source, symbols, imports, current_module);
            }
            "call" => {
                extract_call(&child, source, symbols, imports, current_module);
            }
            "def" | "defp" | "defmacro" | "defmacrop" => {
                if let Some(sym) = extract_def(&child, source, current_module) {
                    symbols.push(sym);
                }
            }
            _ => {
                // Recurse into other container nodes
                let mut inner = child.walk();
                for grandchild in child.children(&mut inner) {
                    if grandchild.kind() == "call"
                        || grandchild.kind() == "do_block"
                        || grandchild.kind() == "body"
                    {
                        walk_do_block(&grandchild, source, symbols, imports, current_module);
                    }
                }
            }
        }
    }
}

fn extract_def(node: &tree_sitter::Node, source: &[u8], parent: Option<&str>) -> Option<Symbol> {
    let text = node_text(node, source);
    let name = extract_def_name(node, source, text)?;

    Some(Symbol {
        name,
        kind: SymbolKind::Function,
        span: Span::from_node(node),
        signature: first_line(text).to_string(),
        doc_comment: extract_doc_comment(node, source),
        parent: parent.map(|s| s.to_string()),
        children: Vec::new(),
    })
}

fn extract_macro_def(
    node: &tree_sitter::Node,
    source: &[u8],
    parent: Option<&str>,
) -> Option<Symbol> {
    let text = node_text(node, source);
    let name = extract_def_name(node, source, text)?;

    Some(Symbol {
        name,
        kind: SymbolKind::Function,
        span: Span::from_node(node),
        signature: first_line(text).to_string(),
        doc_comment: extract_doc_comment(node, source),
        parent: parent.map(|s| s.to_string()),
        children: Vec::new(),
    })
}

fn get_call_name(node: &tree_sitter::Node, source: &[u8]) -> Option<String> {
    // Try "target" field (used by tree-sitter-elixir)
    if let Some(target) = node.child_by_field_name("target") {
        let t = node_text(&target, source).trim().to_string();
        if !t.is_empty() {
            return Some(t);
        }
    }

    // First child is often the call name
    if let Some(first) = node.child(0) {
        let kind = first.kind();
        if kind == "identifier" || kind == "atom" || kind == "special_identifier" {
            let t = node_text(&first, source).trim().to_string();
            if !t.is_empty() {
                return Some(t);
            }
        }
    }

    // Fallback: parse from text
    let text = node_text(node, source).trim().to_string();
    let first_word = text.split_whitespace().next()?;
    Some(first_word.to_string())
}

fn extract_def_name(node: &tree_sitter::Node, source: &[u8], text: &str) -> Option<String> {
    // In tree-sitter-elixir, the function name is in the arguments of the call.
    // `def foo(a, b) do ... end` → call target=def, arguments contain foo(a,b)
    if let Some(args) = node.child_by_field_name("arguments") {
        let mut cursor = args.walk();
        for child in args.children(&mut cursor) {
            match child.kind() {
                "call" => {
                    // `foo(a, b)` — the call target is the function name
                    if let Some(target) = child.child_by_field_name("target") {
                        let t = node_text(&target, source).trim().to_string();
                        if !t.is_empty() {
                            return Some(t);
                        }
                    }
                    // First child of the inner call
                    if let Some(first) = child.child(0) {
                        let t = node_text(&first, source).trim().to_string();
                        if !t.is_empty() && t != "(" {
                            return Some(t);
                        }
                    }
                }
                "identifier" => {
                    let t = node_text(&child, source).trim().to_string();
                    if !t.is_empty() {
                        return Some(t);
                    }
                }
                _ => {}
            }
        }
    }

    // Walk children for patterns like: def identifier(...)
    let mut cursor = node.walk();
    let mut found_keyword = false;
    for child in node.children(&mut cursor) {
        let t = node_text(&child, source).trim().to_string();
        if t == "def" || t == "defp" || t == "defmacro" || t == "defmacrop" {
            found_keyword = true;
            continue;
        }
        if found_keyword {
            if child.kind() == "call" || child.kind() == "identifier" {
                // For a call like `foo(a, b)`, get the function name part
                if let Some(target) = child.child_by_field_name("target") {
                    return Some(node_text(&target, source).trim().to_string());
                }
                let name_text = node_text(&child, source);
                let name = name_text.split('(').next().unwrap_or(name_text).trim();
                if !name.is_empty() {
                    return Some(name.to_string());
                }
            }
        }
    }

    // Fallback: parse from text
    extract_def_name_from_text(text)
}

fn extract_def_name_from_text(text: &str) -> Option<String> {
    let text = text.trim();
    // Skip def/defp/defmacro keyword
    let rest = text
        .strip_prefix("defmacrop ")
        .or_else(|| text.strip_prefix("defmacro "))
        .or_else(|| text.strip_prefix("defp "))
        .or_else(|| text.strip_prefix("def "))?;

    let end = rest.find(|c: char| c == '(' || c == ' ' || c == ',' || c == '\n')?;
    let name = rest[..end].trim();
    if name.is_empty() {
        None
    } else {
        Some(name.to_string())
    }
}

fn extract_module_name(node: &tree_sitter::Node, source: &[u8], text: &str) -> Option<String> {
    // Try arguments field
    if let Some(args) = node.child_by_field_name("arguments") {
        let mut cursor = args.walk();
        for child in args.children(&mut cursor) {
            let kind = child.kind();
            if kind == "alias" || kind == "identifier" || kind == "dot" {
                let t = node_text(&child, source).trim().to_string();
                if !t.is_empty() && t != "do" {
                    return Some(t);
                }
            }
        }
    }

    // Walk children after `defmodule` keyword
    let mut cursor = node.walk();
    let mut found_keyword = false;
    for child in node.children(&mut cursor) {
        let t = node_text(&child, source).trim().to_string();
        if t == "defmodule" {
            found_keyword = true;
            continue;
        }
        if found_keyword && child.kind() != "do_block" && !t.is_empty() && t != "do" {
            // Could be `Foo.Bar` or just `Foo`
            let name = t.split_whitespace().next().unwrap_or(&t);
            return Some(name.to_string());
        }
    }

    // Fallback: parse from text
    let rest = text.strip_prefix("defmodule ")?;
    let end = rest.find(|c: char| c == ' ' || c == '\n')?;
    Some(rest[..end].trim().to_string())
}

fn extract_module_ref(node: &tree_sitter::Node, source: &[u8], text: &str) -> Option<String> {
    // Try arguments field for the module reference
    if let Some(args) = node.child_by_field_name("arguments") {
        let mut cursor = args.walk();
        for child in args.children(&mut cursor) {
            let kind = child.kind();
            if kind == "alias" || kind == "identifier" || kind == "dot" || kind == "atom" {
                let t = node_text(&child, source).trim().to_string();
                if !t.is_empty() {
                    return Some(t);
                }
            }
        }
    }

    // Fallback: parse from text — `use Phoenix.Controller` → `Phoenix.Controller`
    let text = text.trim();
    let rest = text
        .strip_prefix("use ")
        .or_else(|| text.strip_prefix("import "))
        .or_else(|| text.strip_prefix("alias "))
        .or_else(|| text.strip_prefix("require "))?;

    let end = rest
        .find(|c: char| c == ',' || c == '\n')
        .unwrap_or(rest.len());
    Some(rest[..end].trim().to_string())
}

fn first_line(text: &str) -> &str {
    text.lines().next().unwrap_or(text)
}

#[cfg(test)]
mod tests {
    use crate::{parse, Language, SymbolKind};

    #[test]
    fn test_elixir_extract() {
        let source = r#"defmodule MyApp.Calculator do
  use GenServer
  import Enum
  alias MyApp.Helper

  def add(a, b) do
    a + b
  end

  defp validate(x) do
    x > 0
  end

  defmacro debug(expr) do
    quote do
      IO.inspect(unquote(expr))
    end
  end
end

defmodule MyApp.Runner do
  def run do
    :ok
  end
end
"#;
        let parsed = parse(source, Language::Elixir);
        if let Some(parsed) = parsed {
            let top_names: Vec<&str> = parsed.symbols.iter().map(|s| s.name.as_str()).collect();
            let top_kinds: Vec<(&str, SymbolKind)> = parsed
                .symbols
                .iter()
                .map(|s| (s.name.as_str(), s.kind))
                .collect();
            let import_paths: Vec<&str> = parsed.imports.iter().map(|i| i.path.as_str()).collect();

            // Should find modules
            assert!(
                top_kinds
                    .iter()
                    .any(|(n, k)| n.contains("Calculator") && *k == SymbolKind::Module),
                "Should find Calculator module, got: {:?}",
                top_kinds
            );
            assert!(
                top_kinds
                    .iter()
                    .any(|(n, k)| n.contains("Runner") && *k == SymbolKind::Module),
                "Should find Runner module, got: {:?}",
                top_kinds
            );

            // Should find imports (use, import, alias)
            assert!(
                import_paths.iter().any(|p| p.contains("GenServer")),
                "Should find GenServer use, got: {:?}",
                import_paths
            );
            assert!(
                import_paths.iter().any(|p| p.contains("Enum")),
                "Should find Enum import, got: {:?}",
                import_paths
            );

            // Should find functions as children of modules
            let calc = parsed
                .symbols
                .iter()
                .find(|s| s.name.contains("Calculator"));
            if let Some(calc) = calc {
                let child_names: Vec<&str> =
                    calc.children.iter().map(|s| s.name.as_str()).collect();
                let child_kinds: Vec<(&str, SymbolKind)> = calc
                    .children
                    .iter()
                    .map(|s| (s.name.as_str(), s.kind))
                    .collect();

                assert!(
                    child_kinds
                        .iter()
                        .any(|(n, k)| *n == "add" && *k == SymbolKind::Function),
                    "Calculator should have 'add' function, got children: {:?}",
                    child_kinds
                );
                assert!(
                    child_kinds
                        .iter()
                        .any(|(n, k)| *n == "validate" && *k == SymbolKind::Function),
                    "Calculator should have 'validate' function, got children: {:?}",
                    child_kinds
                );
                assert!(
                    child_kinds
                        .iter()
                        .any(|(n, k)| *n == "debug" && *k == SymbolKind::Function),
                    "Calculator should have 'debug' macro, got children: {:?}",
                    child_kinds
                );

                eprintln!("Calculator children: {:?}", child_names);
            }

            eprintln!("Elixir top-level symbols: {:?}", top_names);
            eprintln!("Elixir imports: {:?}", import_paths);
        } else {
            eprintln!("Elixir parser not available or parse failed — skipping assertions");
        }
    }
}
