use super::{extract_doc_comment, field_text, 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();

    extract_children(&root, source, &mut symbols, &mut imports, None);

    (symbols, imports)
}

fn extract_children(
    node: &tree_sitter::Node,
    source: &[u8],
    symbols: &mut Vec<Symbol>,
    imports: &mut Vec<Import>,
    parent_name: Option<&str>,
) {
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        match child.kind() {
            "using_directive" => {
                let text = node_text(&child, source).trim().to_string();
                imports.push(Import {
                    path: text,
                    alias: None,
                    span: Span::from_node(&child),
                });
            }
            "namespace_declaration" | "file_scoped_namespace_declaration" => {
                let name = field_text(&child, "name", source)
                    .unwrap_or("(anonymous)")
                    .to_string();
                let mut ns_sym = Symbol {
                    name: name.clone(),
                    kind: SymbolKind::Module,
                    span: Span::from_node(&child),
                    signature: format!("namespace {}", name),
                    doc_comment: extract_doc_comment(&child, source),
                    parent: parent_name.map(|s| s.to_string()),
                    children: Vec::new(),
                };
                // Recurse into namespace body
                if let Some(body) = child.child_by_field_name("body") {
                    let mut ns_children = Vec::new();
                    extract_children(&body, source, &mut ns_children, imports, Some(&name));
                    ns_sym.children = ns_children;
                } else {
                    // File-scoped namespace — children are siblings
                    let mut ns_children = Vec::new();
                    let mut inner = child.walk();
                    for ns_child in child.children(&mut inner) {
                        extract_children(&ns_child, source, &mut ns_children, imports, Some(&name));
                    }
                    ns_sym.children = ns_children;
                }
                symbols.push(ns_sym);
            }
            "class_declaration" => {
                if let Some(sym) = extract_type_decl(&child, source, SymbolKind::Class, parent_name)
                {
                    symbols.push(sym);
                }
            }
            "struct_declaration" => {
                if let Some(sym) =
                    extract_type_decl(&child, source, SymbolKind::Struct, parent_name)
                {
                    symbols.push(sym);
                }
            }
            "interface_declaration" => {
                if let Some(sym) =
                    extract_type_decl(&child, source, SymbolKind::Interface, parent_name)
                {
                    symbols.push(sym);
                }
            }
            "enum_declaration" => {
                if let Some(sym) = extract_enum(&child, source, parent_name) {
                    symbols.push(sym);
                }
            }
            "record_declaration" => {
                if let Some(sym) = extract_type_decl(&child, source, SymbolKind::Class, parent_name)
                {
                    symbols.push(sym);
                }
            }
            "delegate_declaration" => {
                if let Some(name) = field_text(&child, "name", source) {
                    symbols.push(Symbol {
                        name: name.to_string(),
                        kind: SymbolKind::TypeAlias,
                        span: Span::from_node(&child),
                        signature: node_text(&child, source)
                            .trim_end_matches(';')
                            .trim()
                            .to_string(),
                        doc_comment: extract_doc_comment(&child, source),
                        parent: parent_name.map(|s| s.to_string()),
                        children: Vec::new(),
                    });
                }
            }
            "method_declaration" | "constructor_declaration" | "destructor_declaration" => {
                if let Some(sym) = extract_method(&child, source, parent_name) {
                    symbols.push(sym);
                }
            }
            "field_declaration" | "event_field_declaration" => {
                extract_field(&child, source, symbols, parent_name);
            }
            "property_declaration" => {
                if let Some(sym) = extract_property(&child, source, parent_name) {
                    symbols.push(sym);
                }
            }
            // Recurse into declaration_list and other container nodes
            "declaration_list" | "compilation_unit" => {
                extract_children(&child, source, symbols, imports, parent_name);
            }
            _ => {}
        }
    }
}

fn extract_type_decl(
    node: &tree_sitter::Node,
    source: &[u8],
    kind: SymbolKind,
    parent_name: Option<&str>,
) -> Option<Symbol> {
    let name = field_text(node, "name", source)?;

    // Build signature: everything before the body
    let signature = if let Some(body) = node.child_by_field_name("body") {
        let sig = &source[node.start_byte()..body.start_byte()];
        std::str::from_utf8(sig).unwrap_or("").trim().to_string()
    } else {
        node_text(node, source).to_string()
    };

    // Extract members from the declaration body
    let mut children = Vec::new();
    let mut imports = Vec::new();
    if let Some(body) = node.child_by_field_name("body") {
        extract_children(&body, source, &mut children, &mut imports, Some(name));
    }

    Some(Symbol {
        name: name.to_string(),
        kind,
        span: Span::from_node(node),
        signature,
        doc_comment: extract_doc_comment(node, source),
        parent: parent_name.map(|s| s.to_string()),
        children,
    })
}

fn extract_enum(
    node: &tree_sitter::Node,
    source: &[u8],
    parent_name: Option<&str>,
) -> Option<Symbol> {
    let name = field_text(node, "name", source)?;

    let signature = if let Some(body) = node.child_by_field_name("body") {
        let sig = &source[node.start_byte()..body.start_byte()];
        std::str::from_utf8(sig).unwrap_or("").trim().to_string()
    } else {
        node_text(node, source).to_string()
    };

    // Extract enum members
    let mut children = Vec::new();
    if let Some(body) = node.child_by_field_name("body") {
        let mut cursor = body.walk();
        for child in body.children(&mut cursor) {
            if child.kind() == "enum_member_declaration" {
                if let Some(member_name) = field_text(&child, "name", source) {
                    children.push(Symbol {
                        name: member_name.to_string(),
                        kind: SymbolKind::Const,
                        span: Span::from_node(&child),
                        signature: node_text(&child, source).to_string(),
                        doc_comment: extract_doc_comment(&child, source),
                        parent: Some(name.to_string()),
                        children: Vec::new(),
                    });
                }
            }
        }
    }

    Some(Symbol {
        name: name.to_string(),
        kind: SymbolKind::Enum,
        span: Span::from_node(node),
        signature,
        doc_comment: extract_doc_comment(node, source),
        parent: parent_name.map(|s| s.to_string()),
        children,
    })
}

fn extract_method(
    node: &tree_sitter::Node,
    source: &[u8],
    parent_name: Option<&str>,
) -> Option<Symbol> {
    let name = field_text(node, "name", source).or_else(|| {
        // Constructor/destructor: use the type name
        if node.kind() == "constructor_declaration" || node.kind() == "destructor_declaration" {
            // The first identifier child is the name
            let mut cursor = node.walk();
            for child in node.children(&mut cursor) {
                if child.kind() == "identifier" {
                    return Some(node_text(&child, source));
                }
            }
        }
        None
    })?;

    let kind = if parent_name.is_some() {
        SymbolKind::Method
    } else {
        SymbolKind::Function
    };

    // Signature: everything before the body
    let signature = if let Some(body) = node.child_by_field_name("body") {
        let sig = &source[node.start_byte()..body.start_byte()];
        std::str::from_utf8(sig).unwrap_or("").trim().to_string()
    } else {
        // Expression-bodied member or abstract method
        node_text(node, source)
            .trim_end_matches(';')
            .trim()
            .to_string()
    };

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

fn extract_field(
    node: &tree_sitter::Node,
    source: &[u8],
    symbols: &mut Vec<Symbol>,
    parent_name: Option<&str>,
) {
    // field_declaration contains a variable_declaration with variable_declarators
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        if child.kind() == "variable_declaration" {
            let mut inner = child.walk();
            for decl in child.children(&mut inner) {
                if decl.kind() == "variable_declarator" {
                    if let Some(name) = field_text(&decl, "name", source).or_else(|| {
                        // Some versions use identifier directly
                        let mut c = decl.walk();
                        for ch in decl.children(&mut c) {
                            if ch.kind() == "identifier" {
                                return Some(node_text(&ch, source));
                            }
                        }
                        None
                    }) {
                        symbols.push(Symbol {
                            name: name.to_string(),
                            kind: SymbolKind::Const,
                            span: Span::from_node(node),
                            signature: node_text(node, source)
                                .trim_end_matches(';')
                                .trim()
                                .to_string(),
                            doc_comment: extract_doc_comment(node, source),
                            parent: parent_name.map(|s| s.to_string()),
                            children: Vec::new(),
                        });
                    }
                }
            }
        }
    }
}

fn extract_property(
    node: &tree_sitter::Node,
    source: &[u8],
    parent_name: Option<&str>,
) -> Option<Symbol> {
    let name = field_text(node, "name", source)?;

    // Signature: everything before the accessor list
    let signature = node_text(node, source)
        .lines()
        .next()
        .unwrap_or("")
        .trim()
        .to_string();

    Some(Symbol {
        name: name.to_string(),
        kind: SymbolKind::Const, // Properties map to Const in our symbol model
        span: Span::from_node(node),
        signature,
        doc_comment: extract_doc_comment(node, source),
        parent: parent_name.map(|s| s.to_string()),
        children: Vec::new(),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    fn parse_csharp(source: &str) -> (Vec<Symbol>, Vec<Import>) {
        let mut parser = tree_sitter::Parser::new();
        parser
            .set_language(&tree_sitter_c_sharp::LANGUAGE.into())
            .unwrap();
        let tree = parser.parse(source, None).unwrap();
        extract(&tree, source.as_bytes())
    }

    #[test]
    fn test_class_with_methods() {
        let source = r#"
using System;
using System.Collections.Generic;

namespace MyApp
{
    public class Calculator
    {
        private int _value;

        public Calculator(int initial)
        {
            _value = initial;
        }

        public int Add(int x)
        {
            _value += x;
            return _value;
        }

        public string Name { get; set; }
    }
}
"#;
        let (symbols, imports) = parse_csharp(source);

        assert_eq!(imports.len(), 2);
        assert!(imports[0].path.contains("System"));

        // Top-level: namespace
        assert_eq!(symbols.len(), 1);
        assert_eq!(symbols[0].name, "MyApp");
        assert_eq!(symbols[0].kind, SymbolKind::Module);

        // Inside namespace: class
        let ns_children = &symbols[0].children;
        assert_eq!(ns_children.len(), 1);
        assert_eq!(ns_children[0].name, "Calculator");
        assert_eq!(ns_children[0].kind, SymbolKind::Class);

        // Inside class: field, constructor, method, property
        let class_children = &ns_children[0].children;
        let names: Vec<&str> = class_children.iter().map(|s| s.name.as_str()).collect();
        assert!(names.contains(&"_value"));
        assert!(names.contains(&"Calculator"));
        assert!(names.contains(&"Add"));
        assert!(names.contains(&"Name"));
    }

    #[test]
    fn test_interface_and_enum() {
        let source = r#"
public interface IService
{
    void Execute();
    string GetName();
}

public enum Status
{
    Active,
    Inactive,
    Pending
}
"#;
        let (symbols, _imports) = parse_csharp(source);

        let iface = symbols.iter().find(|s| s.name == "IService").unwrap();
        assert_eq!(iface.kind, SymbolKind::Interface);
        assert_eq!(iface.children.len(), 2);

        let enm = symbols.iter().find(|s| s.name == "Status").unwrap();
        assert_eq!(enm.kind, SymbolKind::Enum);
        assert_eq!(enm.children.len(), 3);
        assert_eq!(enm.children[0].name, "Active");
    }

    #[test]
    fn test_struct_and_record() {
        let source = r#"
public struct Point
{
    public int X;
    public int Y;
}

public record Person(string Name, int Age);
"#;
        let (symbols, _imports) = parse_csharp(source);

        let point = symbols.iter().find(|s| s.name == "Point").unwrap();
        assert_eq!(point.kind, SymbolKind::Struct);

        let person = symbols.iter().find(|s| s.name == "Person").unwrap();
        // Records map to Class
        assert_eq!(person.kind, SymbolKind::Class);
    }

    #[test]
    fn test_language_detection() {
        assert_eq!(Language::from_extension("cs"), Some(Language::CSharp));
        assert_eq!(Language::from_extension("csx"), Some(Language::CSharp));
        assert_eq!(
            Language::from_filename("Program.cs"),
            Some(Language::CSharp)
        );
    }
}
