use super::helpers::*;
use super::SymbolExtractor;
use crate::cfg::build_function_cfg;
use crate::complexity::compute_all_metrics;
use crate::types::*;
use tree_sitter::{Node, Tree};

pub struct CSharpExtractor;

impl SymbolExtractor for CSharpExtractor {
    fn extract(&self, tree: &Tree, source: &[u8], file_path: &str) -> FileSymbols {
        let mut symbols = FileSymbols::new(file_path.to_string());
        walk_tree(&tree.root_node(), source, &mut symbols, match_csharp_node);
        walk_ast_nodes_with_config(&tree.root_node(), source, &mut symbols.ast_nodes, &CSHARP_AST_CONFIG);
        walk_tree(&tree.root_node(), source, &mut symbols, match_csharp_type_map);
        symbols
    }
}

const CSHARP_TYPE_KINDS: &[&str] = &[
    "class_declaration", "struct_declaration", "interface_declaration",
    "enum_declaration", "record_declaration",
];

fn find_csharp_parent_type(node: &Node, source: &[u8]) -> Option<String> {
    find_enclosing_type_name(node, CSHARP_TYPE_KINDS, source)
}

fn match_csharp_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth: usize) {
    match node.kind() {
        "class_declaration" => handle_class_decl(node, source, symbols),
        "struct_declaration" => handle_struct_decl(node, source, symbols),
        "record_declaration" => handle_record_decl(node, source, symbols),
        "interface_declaration" => handle_interface_decl(node, source, symbols),
        "enum_declaration" => handle_enum_decl(node, source, symbols),
        "method_declaration" => handle_method_decl(node, source, symbols),
        "constructor_declaration" => handle_constructor_decl(node, source, symbols),
        "property_declaration" => handle_property_decl(node, source, symbols),
        "using_directive" => handle_using_directive(node, source, symbols),
        "invocation_expression" => handle_invocation_expr(node, source, symbols),
        "object_creation_expression" => handle_object_creation(node, source, symbols),
        _ => {}
    }
}

// ── Per-node-kind handlers for walk_node_depth ───────────────────────────────

fn handle_class_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let Some(name_node) = node.child_by_field_name("name") else { return };
    let class_name = node_text(&name_node, source).to_string();
    let children = extract_csharp_class_fields(node, source);
    symbols.definitions.push(Definition {
        name: class_name.clone(),
        kind: "class".to_string(),
        line: start_line(node),
        end_line: Some(end_line(node)),
        decorators: None,
        complexity: None,
        cfg: None,
        children: opt_children(children),
    });
    extract_csharp_base_types(node, &class_name, source, symbols);
}

fn handle_struct_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let Some(name_node) = node.child_by_field_name("name") else { return };
    let name = node_text(&name_node, source).to_string();
    symbols.definitions.push(Definition {
        name: name.clone(),
        kind: "struct".to_string(),
        line: start_line(node),
        end_line: Some(end_line(node)),
        decorators: None,
        complexity: None,
        cfg: None,
        children: None,
    });
    extract_csharp_base_types(node, &name, source, symbols);
}

fn handle_record_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let Some(name_node) = node.child_by_field_name("name") else { return };
    let name = node_text(&name_node, source).to_string();
    symbols.definitions.push(Definition {
        name: name.clone(),
        kind: "record".to_string(),
        line: start_line(node),
        end_line: Some(end_line(node)),
        decorators: None,
        complexity: None,
        cfg: None,
        children: None,
    });
    extract_csharp_base_types(node, &name, source, symbols);
}

fn handle_interface_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let Some(name_node) = node.child_by_field_name("name") else { return };
    let iface_name = node_text(&name_node, source).to_string();
    symbols.definitions.push(Definition {
        name: iface_name.clone(),
        kind: "interface".to_string(),
        line: start_line(node),
        end_line: Some(end_line(node)),
        decorators: None,
        complexity: None,
        cfg: None,
        children: None,
    });
    if let Some(body) = node.child_by_field_name("body") {
        for i in 0..body.child_count() {
            let Some(child) = body.child(i) else { continue };
            if child.kind() != "method_declaration" { continue; }
            if let Some(meth_name) = child.child_by_field_name("name") {
                symbols.definitions.push(Definition {
                    name: format!("{}.{}", iface_name, node_text(&meth_name, source)),
                    kind: "method".to_string(),
                    line: start_line(&child),
                    end_line: Some(end_line(&child)),
                    decorators: None,
                    complexity: compute_all_metrics(&child, source, "csharp"),
                    cfg: build_function_cfg(&child, "csharp", source),
                    children: None,
                });
            }
        }
    }
}

fn handle_enum_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    if let Some(name_node) = node.child_by_field_name("name") {
        let enum_name = node_text(&name_node, source).to_string();
        let children = extract_csharp_enum_members(node, source);
        symbols.definitions.push(Definition {
            name: enum_name,
            kind: "enum".to_string(),
            line: start_line(node),
            end_line: Some(end_line(node)),
            decorators: None,
            complexity: None,
            cfg: None,
            children: opt_children(children),
        });
    }
}

fn handle_method_or_ctor(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let Some(name_node) = node.child_by_field_name("name") else { return };
    let parent_type = find_csharp_parent_type(node, source);
    let name = node_text(&name_node, source);
    let full_name = match &parent_type {
        Some(pt) => format!("{}.{}", pt, name),
        None => name.to_string(),
    };
    let children = extract_csharp_parameters(node, source);
    symbols.definitions.push(Definition {
        name: full_name,
        kind: "method".to_string(),
        line: start_line(node),
        end_line: Some(end_line(node)),
        decorators: None,
        complexity: compute_all_metrics(node, source, "csharp"),
        cfg: build_function_cfg(node, "csharp", source),
        children: opt_children(children),
    });
}

fn handle_method_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    handle_method_or_ctor(node, source, symbols);
}

fn handle_constructor_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    handle_method_or_ctor(node, source, symbols);
}

fn handle_property_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let Some(name_node) = node.child_by_field_name("name") else { return };
    let parent_type = find_csharp_parent_type(node, source);
    let name = node_text(&name_node, source);
    let full_name = match &parent_type {
        Some(pt) => format!("{}.{}", pt, name),
        None => name.to_string(),
    };
    symbols.definitions.push(Definition {
        name: full_name,
        kind: "method".to_string(),
        line: start_line(node),
        end_line: Some(end_line(node)),
        decorators: None,
        complexity: compute_all_metrics(node, source, "csharp"),
        cfg: build_function_cfg(node, "csharp", source),
        children: None,
    });
}

fn handle_using_directive(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let name_node = node
        .child_by_field_name("name")
        .or_else(|| find_child(node, "qualified_name"))
        .or_else(|| find_child(node, "identifier"));
    if let Some(name_node) = name_node {
        let full_path = node_text(&name_node, source).to_string();
        let last_name = full_path.split('.').last().unwrap_or("").to_string();
        let mut imp = Import::new(full_path, vec![last_name], start_line(node));
        imp.csharp_using = Some(true);
        symbols.imports.push(imp);
    }
}

fn handle_invocation_expr(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let fn_node = node.child_by_field_name("function").or_else(|| node.child(0));
    let Some(fn_node) = fn_node else { return };
    match fn_node.kind() {
        "identifier" => {
            symbols.calls.push(Call {
                name: node_text(&fn_node, source).to_string(),
                line: start_line(node),
                dynamic: None,
                receiver: None,
            });
        }
        "member_access_expression" => {
            if let Some(name) = fn_node.child_by_field_name("name") {
                let receiver = named_child_text(&fn_node, "expression", source)
                    .map(|s| s.to_string());
                symbols.calls.push(Call {
                    name: node_text(&name, source).to_string(),
                    line: start_line(node),
                    dynamic: None,
                    receiver,
                });
            }
        }
        "generic_name" | "member_binding_expression" => {
            let name = fn_node.child_by_field_name("name").or_else(|| fn_node.child(0));
            if let Some(name) = name {
                symbols.calls.push(Call {
                    name: node_text(&name, source).to_string(),
                    line: start_line(node),
                    dynamic: None,
                    receiver: None,
                });
            }
        }
        _ => {}
    }
}

fn handle_object_creation(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
    let Some(type_node) = node.child_by_field_name("type") else { return };
    let type_name = if type_node.kind() == "generic_name" {
        type_node.child_by_field_name("name").or_else(|| type_node.child(0))
            .map(|n| node_text(&n, source).to_string())
    } else {
        Some(node_text(&type_node, source).to_string())
    };
    if let Some(name) = type_name {
        symbols.calls.push(Call {
            name,
            line: start_line(node),
            dynamic: None,
            receiver: None,
        });
    }
}

// ── Extended kinds helpers ──────────────────────────────────────────────────

fn extract_csharp_parameters(node: &Node, source: &[u8]) -> Vec<Definition> {
    let mut params = Vec::new();
    let params_node = node.child_by_field_name("parameters")
        .or_else(|| find_child(node, "parameter_list"));
    if let Some(params_node) = params_node {
        for i in 0..params_node.child_count() {
            if let Some(child) = params_node.child(i) {
                if child.kind() == "parameter" {
                    if let Some(name_node) = child.child_by_field_name("name") {
                        params.push(child_def(
                            node_text(&name_node, source).to_string(),
                            "parameter",
                            start_line(&child),
                        ));
                    }
                }
            }
        }
    }
    params
}

fn extract_csharp_class_fields(node: &Node, source: &[u8]) -> Vec<Definition> {
    let mut fields = Vec::new();
    let body = node.child_by_field_name("body")
        .or_else(|| find_child(node, "declaration_list"));
    let Some(body) = body else { return fields };
    for i in 0..body.child_count() {
        let Some(child) = body.child(i) else { continue };
        if child.kind() == "field_declaration" {
            collect_field_declarator_names(&child, source, &mut fields);
        }
    }
    fields
}

fn collect_field_declarator_names(field: &Node, source: &[u8], fields: &mut Vec<Definition>) {
    for j in 0..field.child_count() {
        let Some(decl) = field.child(j) else { continue };
        if decl.kind() != "variable_declaration" { continue; }
        for k in 0..decl.child_count() {
            let Some(declarator) = decl.child(k) else { continue };
            if declarator.kind() != "variable_declarator" { continue; }
            let name_node = declarator.child_by_field_name("name")
                .or_else(|| declarator.child(0));
            let Some(name_node) = name_node else { continue };
            if name_node.kind() == "identifier" {
                fields.push(child_def(
                    node_text(&name_node, source).to_string(),
                    "property",
                    start_line(field),
                ));
            }
        }
    }
}

fn extract_csharp_enum_members(node: &Node, source: &[u8]) -> Vec<Definition> {
    let mut members = Vec::new();
    let body = node.child_by_field_name("body")
        .or_else(|| find_child(node, "enum_member_declaration_list"));
    if let Some(body) = body {
        for i in 0..body.child_count() {
            if let Some(child) = body.child(i) {
                if child.kind() == "enum_member_declaration" {
                    if let Some(name_node) = child.child_by_field_name("name")
                        .or_else(|| child.child(0))
                    {
                        if name_node.kind() == "identifier" {
                            members.push(child_def(
                                node_text(&name_node, source).to_string(),
                                "constant",
                                start_line(&child),
                            ));
                        }
                    }
                }
            }
        }
    }
    members
}

// ── Existing helpers ────────────────────────────────────────────────────────

fn extract_csharp_base_types(
    node: &Node,
    class_name: &str,
    source: &[u8],
    symbols: &mut FileSymbols,
) {
    // tree-sitter-c-sharp exposes base_list as a child node type, not a field,
    // so child_by_field_name("bases") returns None — fall back to find_child.
    let base_list = node
        .child_by_field_name("bases")
        .or_else(|| find_child(node, "base_list"));
    let base_list = match base_list {
        Some(bl) => bl,
        None => return,
    };

    for i in 0..base_list.child_count() {
        if let Some(child) = base_list.child(i) {
            match child.kind() {
                "identifier" | "qualified_name" => {
                    symbols.classes.push(ClassRelation {
                        name: class_name.to_string(),
                        extends: Some(node_text(&child, source).to_string()),
                        implements: None,
                        line: start_line(node),
                    });
                }
                "generic_name" => {
                    let name = child
                        .child_by_field_name("name")
                        .or_else(|| child.child(0));
                    if let Some(name) = name {
                        symbols.classes.push(ClassRelation {
                            name: class_name.to_string(),
                            extends: Some(node_text(&name, source).to_string()),
                            implements: None,
                            line: start_line(node),
                        });
                    }
                }
                _ => {}
            }
        }
    }
}

// ── Type map extraction ─────────────────────────────────────────────────────

fn extract_csharp_type_name<'a>(type_node: &Node<'a>, source: &'a [u8]) -> Option<&'a str> {
    match type_node.kind() {
        "identifier" | "qualified_name" => Some(node_text(type_node, source)),
        "predefined_type" => None, // skip int, string, etc.
        "generic_name" => type_node.child(0).map(|n| node_text(&n, source)),
        "nullable_type" => {
            type_node.child(0).and_then(|inner| extract_csharp_type_name(&inner, source))
        }
        _ => None,
    }
}

fn match_csharp_type_map(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth: usize) {
    match node.kind() {
        "variable_declaration" => {
            let type_node = node.child_by_field_name("type").or_else(|| node.child(0));
            if let Some(type_node) = type_node {
                if type_node.kind() != "var_keyword" && type_node.kind() != "implicit_type" {
                    if let Some(type_name) = extract_csharp_type_name(&type_node, source) {
                        for i in 0..node.child_count() {
                            if let Some(child) = node.child(i) {
                                if child.kind() == "variable_declarator" {
                                    let name_node = child.child_by_field_name("name")
                                        .or_else(|| child.child(0));
                                    if let Some(name_node) = name_node {
                                        if name_node.kind() == "identifier" {
                                            symbols.type_map.push(TypeMapEntry {
                                                name: node_text(&name_node, source).to_string(),
                                                type_name: type_name.to_string(),
                                                confidence: 0.9,
                                            });
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        "parameter" => {
            if let Some(type_node) = node.child_by_field_name("type") {
                if let Some(type_name) = extract_csharp_type_name(&type_node, source) {
                    if let Some(name_node) = node.child_by_field_name("name") {
                        symbols.type_map.push(TypeMapEntry {
                            name: node_text(&name_node, source).to_string(),
                            type_name: type_name.to_string(),
                            confidence: 0.9,
                        });
                    }
                }
            }
        }
        _ => {}
    }
}
