use tree_sitter::Node;

use super::{
    DocCommentSpec, SymbolSink, collect_symbols, find_first_named_child, push_named_symbol,
    walk_children,
};

pub(super) const DOC_SPEC: DocCommentSpec = DocCommentSpec {
    comment_node_types: &["line_comment", "multiline_comment"],
    doc_prefixes: Some(&["/**"]),
    custom_doc_check: None,
};
use crate::domain::{SymbolKind, SymbolRecord};

pub fn extract_symbols(node: &Node, source: &str) -> Vec<SymbolRecord> {
    collect_symbols(node, source, walk_node)
}

fn walk_node(
    node: &Node,
    source: &str,
    depth: u32,
    sort_order: &mut u32,
    symbols: &mut Vec<SymbolRecord>,
) {
    // NOTE: tree-sitter-kotlin-sg maps enum class, interface, and class to class_declaration.
    // Inspect child nodes to distinguish enum/interface from plain class.
    let kind = match node.kind() {
        "function_declaration" => Some(SymbolKind::Function),
        "class_declaration" => {
            let mut cursor = node.walk();
            let mut refined = SymbolKind::Class;
            for child in node.children(&mut cursor) {
                match child.utf8_text(source.as_bytes()).unwrap_or("") {
                    "enum" => {
                        refined = SymbolKind::Enum;
                        break;
                    }
                    "interface" => {
                        refined = SymbolKind::Interface;
                        break;
                    }
                    _ => {}
                }
            }
            Some(refined)
        }
        "object_declaration" => Some(SymbolKind::Module),
        _ => None,
    };

    {
        let mut sink = SymbolSink::new(source, sort_order, symbols, &DOC_SPEC);
        push_named_symbol(
            node,
            depth,
            kind,
            |node, source, _| find_name(node, source),
            &mut sink,
        );
    }
    walk_children(node, source, depth, sort_order, symbols, kind, walk_node);
}

fn find_name(node: &Node, source: &str) -> Option<String> {
    find_first_named_child(node, source, &["type_identifier", "simple_identifier"])
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::{FileOutcome, LanguageId, SymbolKind};
    use crate::parsing::process_file;
    use tree_sitter::Parser;

    fn parse_kotlin(source: &str) -> Vec<SymbolRecord> {
        let mut parser = Parser::new();
        let lang: tree_sitter::Language = tree_sitter_kotlin_sg::LANGUAGE.into();
        parser.set_language(&lang).expect("set Kotlin language");
        let tree = parser.parse(source, None).expect("parse Kotlin source");
        extract_symbols(&tree.root_node(), source)
    }

    #[test]
    fn test_kotlin_function_declaration() {
        let source = "fun greet() { println(\"hello\") }";
        let symbols = parse_kotlin(source);
        let func = symbols.iter().find(|s| s.kind == SymbolKind::Function);
        assert!(
            func.is_some(),
            "should extract function, got: {:?}",
            symbols
        );
        assert_eq!(func.unwrap().name, "greet");
    }

    #[test]
    fn test_kotlin_class_declaration() {
        // tree-sitter-kotlin-sg maps both class and interface to class_declaration
        let source = "class Animal { fun speak() { } }";
        let symbols = parse_kotlin(source);
        // Grammar may report has_error for some constructs but still extracts symbols
        let cls = symbols
            .iter()
            .find(|s| s.kind == SymbolKind::Class && s.name == "Animal");
        assert!(
            cls.is_some(),
            "should extract Animal class, got: {:?}",
            symbols
        );
    }

    #[test]
    fn test_kotlin_interface_maps_to_interface() {
        let source = "interface Runnable { fun run() }";
        let symbols = parse_kotlin(source);
        let iface = symbols
            .iter()
            .find(|s| s.kind == SymbolKind::Interface && s.name == "Runnable");
        assert!(
            iface.is_some(),
            "should extract Runnable as Interface, got: {:?}",
            symbols
        );
    }

    #[test]
    fn test_kotlin_enum_class_maps_to_enum() {
        let source = "enum class Color { RED, GREEN, BLUE }";
        let symbols = parse_kotlin(source);
        let enm = symbols
            .iter()
            .find(|s| s.kind == SymbolKind::Enum && s.name == "Color");
        assert!(
            enm.is_some(),
            "should extract Color as Enum, got: {:?}",
            symbols
        );
    }

    #[test]
    fn test_kotlin_process_file_extracts_symbols() {
        // Note: tree-sitter-kotlin-sg may report parse errors on some valid Kotlin
        // but still extracts symbols. Accept both Processed and PartialParse.
        let source = b"class Foo { fun bar() { } }";
        let result = process_file("test.kt", source, LanguageId::Kotlin);
        assert!(
            matches!(
                result.outcome,
                FileOutcome::Processed | FileOutcome::PartialParse { .. }
            ),
            "should be Processed or PartialParse, got: {:?}",
            result.outcome
        );
        assert!(
            !result.symbols.is_empty(),
            "should have symbols, got: {:?}",
            result.symbols
        );
    }
}
