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: &["comment"],
    doc_prefixes: None,
    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>,
) {
    let kind = match node.kind() {
        "method" => Some(SymbolKind::Method),
        "singleton_method" => Some(SymbolKind::Function),
        "class" => Some(SymbolKind::Class),
        "module" => 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, &["identifier", "constant"])
}

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

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

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

    #[test]
    fn test_ruby_method_definition() {
        let source = "def greet\n  puts 'hello'\nend";
        let symbols = parse_ruby(source);
        let func = symbols.iter().find(|s| s.kind == SymbolKind::Method);
        assert!(func.is_some(), "should extract method, got: {:?}", symbols);
        assert_eq!(func.unwrap().name, "greet");
    }

    #[test]
    fn test_ruby_class_definition() {
        let source = "class Animal\n  def speak\n  end\nend";
        let symbols = parse_ruby(source);
        let cls = symbols.iter().find(|s| s.kind == SymbolKind::Class);
        assert!(cls.is_some(), "should extract class, got: {:?}", symbols);
        assert_eq!(cls.unwrap().name, "Animal");
    }

    #[test]
    fn test_ruby_module_definition() {
        let source = "module Helpers\n  def help\n  end\nend";
        let symbols = parse_ruby(source);
        let m = symbols.iter().find(|s| s.kind == SymbolKind::Module);
        assert!(m.is_some(), "should extract module, got: {:?}", symbols);
        assert_eq!(m.unwrap().name, "Helpers");
    }

    #[test]
    fn test_ruby_process_file_returns_processed() {
        let source = b"class Foo\n  def bar\n  end\nend";
        let result = process_file("test.rb", source, LanguageId::Ruby);
        assert_eq!(
            result.outcome,
            FileOutcome::Processed,
            "outcome: {:?}",
            result.outcome
        );
        assert!(!result.symbols.is_empty(), "should have symbols");
    }
}
