//! Parser tests

use cqs::parser::{ChunkType, Language, Parser};

fn fixtures_path() -> std::path::PathBuf {
    std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}

#[test]
fn test_rust_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.rs");
    let chunks = parser.parse_file(&path).unwrap();

    // Should find add, subtract, new, add, get functions
    assert!(
        chunks.len() >= 5,
        "Expected at least 5 chunks, got {}",
        chunks.len()
    );

    // Check for specific function
    let add_fn = chunks
        .iter()
        .find(|c| c.name == "add" && c.chunk_type == ChunkType::Function);
    assert!(add_fn.is_some(), "Should find 'add' function");

    let add_fn = add_fn.unwrap();
    assert_eq!(add_fn.language, Language::Rust);
    assert!(add_fn.content.contains("a + b"));
}

#[test]
fn test_rust_method_detection() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.rs");
    let chunks = parser.parse_file(&path).unwrap();

    // Methods inside impl block
    let methods: Vec<_> = chunks
        .iter()
        .filter(|c| c.chunk_type == ChunkType::Method)
        .collect();
    assert!(!methods.is_empty(), "Should find methods in impl block");

    // Check Calculator::new is a constructor
    let new_ctor = chunks
        .iter()
        .find(|c| c.name == "new" && c.chunk_type == ChunkType::Constructor);
    assert!(
        new_ctor.is_some(),
        "Calculator::new should be a constructor"
    );
}

#[test]
fn test_python_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.py");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(!chunks.is_empty(), "Should find chunks in Python file");

    let greet_fn = chunks.iter().find(|c| c.name == "greet");
    assert!(greet_fn.is_some(), "Should find 'greet' function");

    let greet_fn = greet_fn.unwrap();
    assert_eq!(greet_fn.language, Language::Python);
}

#[test]
fn test_python_method_detection() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.py");
    let chunks = parser.parse_file(&path).unwrap();

    // Methods inside class
    let methods: Vec<_> = chunks
        .iter()
        .filter(|c| c.chunk_type == ChunkType::Method)
        .collect();
    assert!(!methods.is_empty(), "Should find methods in Python class");

    // Check increment is a method
    let increment = chunks.iter().find(|c| c.name == "increment");
    assert!(increment.is_some(), "Should find 'increment' method");
    assert_eq!(increment.unwrap().chunk_type, ChunkType::Method);
}

#[test]
fn test_typescript_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.ts");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(!chunks.is_empty(), "Should find chunks in TypeScript file");

    let format_fn = chunks.iter().find(|c| c.name == "formatName");
    assert!(format_fn.is_some(), "Should find 'formatName' function");

    let format_fn = format_fn.unwrap();
    assert_eq!(format_fn.language, Language::TypeScript);
}

#[test]
fn test_typescript_arrow_function() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.ts");
    let chunks = parser.parse_file(&path).unwrap();

    let double_fn = chunks.iter().find(|c| c.name == "double");
    assert!(double_fn.is_some(), "Should find 'double' arrow function");
}

#[test]
fn test_javascript_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.js");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(!chunks.is_empty(), "Should find chunks in JavaScript file");

    let validate_fn = chunks.iter().find(|c| c.name == "validateEmail");
    assert!(
        validate_fn.is_some(),
        "Should find 'validateEmail' function"
    );

    let validate_fn = validate_fn.unwrap();
    assert_eq!(validate_fn.language, Language::JavaScript);
}

#[test]
fn test_go_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.go");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(!chunks.is_empty(), "Should find chunks in Go file");

    let greet_fn = chunks.iter().find(|c| c.name == "Greet");
    assert!(greet_fn.is_some(), "Should find 'Greet' function");

    let greet_fn = greet_fn.unwrap();
    assert_eq!(greet_fn.language, Language::Go);
    assert_eq!(greet_fn.chunk_type, ChunkType::Function);
}

#[test]
fn test_go_method_detection() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.go");
    let chunks = parser.parse_file(&path).unwrap();

    // Methods on Stack
    let push = chunks.iter().find(|c| c.name == "Push");
    assert!(push.is_some(), "Should find 'Push' method");
    assert_eq!(push.unwrap().chunk_type, ChunkType::Method);
}

#[test]
fn test_signature_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.rs");
    let chunks = parser.parse_file(&path).unwrap();

    let add_fn = chunks
        .iter()
        .find(|c| c.name == "add" && c.chunk_type == ChunkType::Function)
        .unwrap();

    // Signature should be normalized (single space)
    assert!(
        add_fn.signature.contains("pub fn add"),
        "Signature should contain function declaration"
    );
    assert!(
        !add_fn.signature.contains('{'),
        "Signature should not contain body"
    );
}

#[test]
fn test_doc_comment_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.rs");
    let chunks = parser.parse_file(&path).unwrap();

    let add_fn = chunks
        .iter()
        .find(|c| c.name == "add" && c.chunk_type == ChunkType::Function)
        .unwrap();

    assert!(add_fn.doc.is_some(), "Should extract doc comment");
    let doc = add_fn.doc.as_ref().unwrap();
    assert!(
        doc.contains("Adds two numbers"),
        "Doc should contain description"
    );
}

#[test]
fn test_language_from_extension() {
    assert_eq!(Language::from_extension("rs"), Some(Language::Rust));
    assert_eq!(Language::from_extension("py"), Some(Language::Python));
    assert_eq!(Language::from_extension("pyi"), Some(Language::Python));
    assert_eq!(Language::from_extension("ts"), Some(Language::TypeScript));
    assert_eq!(Language::from_extension("tsx"), Some(Language::TypeScript));
    assert_eq!(Language::from_extension("js"), Some(Language::JavaScript));
    assert_eq!(Language::from_extension("jsx"), Some(Language::JavaScript));
    assert_eq!(Language::from_extension("mjs"), Some(Language::JavaScript));
    assert_eq!(Language::from_extension("go"), Some(Language::Go));
    assert_eq!(Language::from_extension("txt"), None);
}

#[test]
fn test_supported_extensions() {
    let parser = Parser::new().unwrap();
    let exts = parser.supported_extensions();

    assert!(exts.contains(&"rs"));
    assert!(exts.contains(&"py"));
    assert!(exts.contains(&"ts"));
    assert!(exts.contains(&"js"));
    assert!(exts.contains(&"go"));
}

// ===== C and Java Parser Fixture Tests (#239) =====

#[test]
fn test_parse_c_fixture() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.c");
    let chunks = parser.parse_file(&path).unwrap();

    // C parser may not extract everything - check what we actually got
    assert!(
        !chunks.is_empty(),
        "Should find at least some chunks in C file"
    );

    // Verify language is correct
    for chunk in &chunks {
        assert_eq!(
            chunk.language,
            Language::C,
            "All chunks should be Language::C"
        );
    }

    // Note: C parser capabilities depend on tree-sitter-c
    // Just verify we can parse the file and extract at least some functions
    let function_count = chunks
        .iter()
        .filter(|c| c.chunk_type == ChunkType::Function)
        .count();
    assert!(
        function_count > 0,
        "Should find at least one function in C file"
    );
}

#[test]
fn test_parse_java_fixture() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("Sample.java");
    let chunks = parser.parse_file(&path).unwrap();

    let names: Vec<&str> = chunks.iter().map(|c| c.name.as_str()).collect();
    assert!(
        names.contains(&"TaskManager"),
        "Should find TaskManager class"
    );
    assert!(names.contains(&"addTask"), "Should find addTask method");
    assert!(
        names.contains(&"findByName"),
        "Should find findByName method"
    );
    assert!(
        names.contains(&"getHighPriority"),
        "Should find getHighPriority method"
    );
    assert!(names.contains(&"size"), "Should find size method");
    assert!(names.contains(&"Task"), "Should find Task class");

    // Verify language
    for chunk in &chunks {
        assert_eq!(
            chunk.language,
            Language::Java,
            "All chunks should be Language::Java"
        );
    }

    // Verify methods are detected correctly
    let add_task = chunks.iter().find(|c| c.name == "addTask");
    assert!(add_task.is_some(), "Should find addTask chunk");
    let add_task = add_task.unwrap();
    assert_eq!(
        add_task.chunk_type,
        ChunkType::Method,
        "addTask should be a method"
    );
    assert!(
        add_task.content.contains("tasks.add"),
        "addTask should contain tasks.add call"
    );

    // Verify doc comments are extracted
    let task_manager = chunks.iter().find(|c| c.name == "TaskManager");
    assert!(task_manager.is_some(), "Should find TaskManager chunk");
    let task_manager = task_manager.unwrap();
    if let Some(doc) = &task_manager.doc {
        assert!(doc.contains("task manager"), "Should extract doc comment");
    }
}

// ===== SQL tests =====

#[test]
fn test_parse_sql_fixture() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sql");
    let chunks = parser.parse_file(&path).unwrap();

    // 2 procs + 1 function + 1 view = 4 chunks
    // (T-SQL trigger syntax not supported by grammar — PostgreSQL triggers only)
    assert!(
        chunks.len() >= 4,
        "Expected at least 4 chunks, got {}",
        chunks.len()
    );

    // Stored procedure
    let proc = chunks.iter().find(|c| c.name.contains("usp_GetOrders"));
    assert!(proc.is_some(), "Should find usp_GetOrders procedure");
    let proc = proc.unwrap();
    assert_eq!(proc.chunk_type, ChunkType::Function);
    assert_eq!(proc.language, Language::Sql);

    // Function
    let func = chunks.iter().find(|c| c.name.contains("fn_CalcTotal"));
    assert!(func.is_some(), "Should find fn_CalcTotal function");
    assert_eq!(func.unwrap().chunk_type, ChunkType::Function);

    // View
    let view = chunks
        .iter()
        .find(|c| c.name.contains("vw_ActiveCustomers"));
    assert!(view.is_some(), "Should find vw_ActiveCustomers view");
    assert_eq!(view.unwrap().chunk_type, ChunkType::Function);
}

#[test]
fn test_sql_signature_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sql");
    let chunks = parser.parse_file(&path).unwrap();

    let func = chunks
        .iter()
        .find(|c| c.name.contains("fn_CalcTotal"))
        .expect("Should find fn_CalcTotal");
    // Signature should stop at AS, not include the body
    assert!(
        func.signature.contains("fn_CalcTotal"),
        "Signature should contain function name: {}",
        func.signature
    );
    assert!(
        !func.signature.contains("BEGIN"),
        "Signature should stop before BEGIN: {}",
        func.signature
    );
}

#[test]
fn test_sql_schema_qualified_names() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sql");
    let chunks = parser.parse_file(&path).unwrap();

    let proc = chunks
        .iter()
        .find(|c| c.name.contains("usp_GetOrders"))
        .expect("Should find usp_GetOrders");
    // Schema prefix should be preserved in the name
    assert!(
        proc.name.contains("dbo"),
        "Should preserve schema prefix: {}",
        proc.name
    );
}

#[test]
fn test_sql_go_separator() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sql");
    let chunks = parser.parse_file(&path).unwrap();

    // GO separators should not prevent multi-batch parsing
    // 4 chunks span multiple GO-separated batches
    assert!(
        chunks.len() >= 4,
        "GO separators should not break parsing, got {} chunks",
        chunks.len()
    );
}

#[test]
fn test_sql_call_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sql");
    let chunks = parser.parse_file(&path).unwrap();

    // usp_ProcessOrder should have EXEC call in its body
    let process = chunks
        .iter()
        .find(|c| c.name.contains("usp_ProcessOrder"))
        .expect("Should find usp_ProcessOrder");
    assert!(
        process.content.contains("EXEC"),
        "usp_ProcessOrder body should contain EXEC call"
    );

    // Extract actual calls from the procedure body
    let calls = parser.extract_calls(&process.content, Language::Sql, 0, process.content.len(), 0);
    assert!(
        !calls.is_empty(),
        "Should extract calls from usp_ProcessOrder"
    );
}

// ===== Markdown tests =====

#[test]
fn test_parse_markdown_fixture() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.md");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(!chunks.is_empty(), "Should find chunks in markdown file");

    // Markdown section chunks
    let md_chunks: Vec<_> = chunks
        .iter()
        .filter(|c| c.language == Language::Markdown)
        .collect();
    for chunk in &md_chunks {
        assert_eq!(chunk.chunk_type, ChunkType::Section);
    }

    // Should have sections from the fixture
    let names: Vec<&str> = md_chunks.iter().map(|c| c.name.as_str()).collect();
    assert!(
        names.contains(&"Getting Started"),
        "Should find 'Getting Started' section, got: {:?}",
        names
    );
    assert!(
        names.contains(&"Advanced Topics"),
        "Should find 'Advanced Topics' section, got: {:?}",
        names
    );

    // Fenced code blocks should extract Python function from ```python block
    let py_chunks: Vec<_> = chunks
        .iter()
        .filter(|c| c.language == Language::Python)
        .collect();
    assert!(
        py_chunks.iter().any(|c| c.name == "example"),
        "Should extract Python function 'example' from fenced block, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.language))
            .collect::<Vec<_>>()
    );
}

#[test]
fn test_markdown_breadcrumb_signatures() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.md");
    let chunks = parser.parse_file(&path).unwrap();

    // H2 sections should have title in breadcrumb
    let getting_started = chunks.iter().find(|c| c.name == "Getting Started");
    assert!(getting_started.is_some(), "Should find 'Getting Started'");
    let gs = getting_started.unwrap();
    assert!(
        gs.signature.contains("Sample Documentation"),
        "Breadcrumb should contain title, got: {}",
        gs.signature
    );
}

#[test]
fn test_markdown_code_block_headings_ignored() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.md");
    let chunks = parser.parse_file(&path).unwrap();

    let names: Vec<&str> = chunks.iter().map(|c| c.name.as_str()).collect();

    // Headings inside code blocks should NOT appear as chunk names
    assert!(
        !names.contains(&"This heading inside a code block should NOT be parsed"),
        "Should not parse heading inside code block, got: {:?}",
        names
    );
    assert!(
        !names.contains(&"Also not a heading"),
        "Should not parse heading inside code block, got: {:?}",
        names
    );
}

#[test]
fn test_markdown_cross_references() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.md");
    let refs = parser.parse_file_calls(&path).unwrap();

    let all_callees: Vec<&str> = refs
        .iter()
        .flat_map(|fc| fc.calls.iter().map(|c| c.callee_name.as_str()))
        .collect();

    // Should find markdown links
    assert!(
        all_callees.contains(&"Configuration Guide"),
        "Should extract link text, got: {:?}",
        all_callees
    );
    assert!(
        all_callees.contains(&"API Reference"),
        "Should extract link text, got: {:?}",
        all_callees
    );

    // Should find backtick function references
    assert!(
        all_callees.contains(&"TagRead"),
        "Should extract backtick function ref, got: {:?}",
        all_callees
    );
    assert!(
        all_callees.contains(&"Module.func"),
        "Should extract backtick module.func ref, got: {:?}",
        all_callees
    );
    assert!(
        all_callees.contains(&"Class::method"),
        "Should extract backtick Class::method ref, got: {:?}",
        all_callees
    );
}

#[test]
fn test_markdown_image_links_skipped() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.md");
    let refs = parser.parse_file_calls(&path).unwrap();

    let all_callees: Vec<&str> = refs
        .iter()
        .flat_map(|fc| fc.calls.iter().map(|c| c.callee_name.as_str()))
        .collect();

    // Image links should NOT be extracted as references
    assert!(
        !all_callees.contains(&"architecture diagram"),
        "Should NOT extract image link text, got: {:?}",
        all_callees
    );
}

#[test]
fn test_markdown_code_blocks_in_content() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.md");
    let chunks = parser.parse_file(&path).unwrap();

    // Code blocks should stay in their parent chunk's content
    let has_code_block = chunks.iter().any(|c| c.content.contains("def example():"));
    assert!(
        has_code_block,
        "Code block content should be preserved in parent chunk"
    );
}

#[test]
fn test_markdown_supported_extensions() {
    let parser = Parser::new().unwrap();
    let exts = parser.supported_extensions();
    assert!(exts.contains(&"md"), "Should support .md extension");
    assert!(exts.contains(&"mdx"), "Should support .mdx extension");
}

#[test]
fn test_markdown_language_from_extension() {
    assert_eq!(Language::from_extension("md"), Some(Language::Markdown));
    assert_eq!(Language::from_extension("mdx"), Some(Language::Markdown));
}

// ===== Bash tests =====

#[test]
fn test_bash_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sh");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 4,
        "Expected at least 4 Bash functions, got {}",
        chunks.len()
    );

    let deploy = chunks.iter().find(|c| c.name == "deploy");
    assert!(deploy.is_some(), "Should find 'deploy' function");
    let deploy = deploy.unwrap();
    assert_eq!(deploy.language, Language::Bash);
    assert_eq!(deploy.chunk_type, ChunkType::Function);
    assert!(deploy.content.contains("build_artifacts"));
}

#[test]
fn test_bash_call_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sh");
    let refs = parser.parse_file_calls(&path).unwrap();

    let all_callees: Vec<&str> = refs
        .iter()
        .flat_map(|fc| fc.calls.iter().map(|c| c.callee_name.as_str()))
        .collect();

    assert!(
        all_callees.contains(&"build_artifacts"),
        "deploy should call build_artifacts, got: {:?}",
        all_callees
    );
}

// ===== HCL/Terraform tests =====

#[test]
fn test_hcl_block_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.tf");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 4,
        "Expected at least 4 HCL blocks, got {}",
        chunks.len()
    );

    // Variables
    let region = chunks.iter().find(|c| c.name == "region");
    assert!(region.is_some(), "Should find 'region' variable");
    let region = region.unwrap();
    assert_eq!(region.language, Language::Hcl);

    // Resources
    let web_instance = chunks
        .iter()
        .find(|c| c.name.contains("aws_instance") && c.name.contains("web"));
    assert!(
        web_instance.is_some(),
        "Should find aws_instance.web resource"
    );
}

// ===== Kotlin tests =====

#[test]
fn test_kotlin_class_and_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.kt");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 4,
        "Expected at least 4 Kotlin chunks, got {}",
        chunks.len()
    );

    // Class
    let stack = chunks
        .iter()
        .find(|c| c.name == "Stack" && c.chunk_type == ChunkType::Class);
    assert!(stack.is_some(), "Should find 'Stack' class");
    assert_eq!(stack.unwrap().language, Language::Kotlin);

    // Interface
    let config = chunks
        .iter()
        .find(|c| c.name == "Config" && c.chunk_type == ChunkType::Interface);
    assert!(config.is_some(), "Should find 'Config' interface");

    // Enum
    let log_level = chunks
        .iter()
        .find(|c| c.name == "LogLevel" && c.chunk_type == ChunkType::Enum);
    assert!(log_level.is_some(), "Should find 'LogLevel' enum");

    // Top-level function
    let format_fn = chunks.iter().find(|c| c.name == "formatDuration");
    assert!(format_fn.is_some(), "Should find 'formatDuration' function");
}

// ===== Swift tests =====

#[test]
fn test_swift_struct_class_protocol_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.swift");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 4,
        "Expected at least 4 Swift chunks, got {}",
        chunks.len()
    );

    // Struct
    let point = chunks
        .iter()
        .find(|c| c.name == "Point" && c.chunk_type == ChunkType::Struct);
    assert!(point.is_some(), "Should find 'Point' struct");
    assert_eq!(point.unwrap().language, Language::Swift);

    // Protocol (captured as Trait by tree-sitter query)
    let shape = chunks
        .iter()
        .find(|c| c.name == "Shape" && c.chunk_type == ChunkType::Trait);
    assert!(shape.is_some(), "Should find 'Shape' protocol (as Trait)");

    // Class
    let circle = chunks
        .iter()
        .find(|c| c.name == "Circle" && c.chunk_type == ChunkType::Class);
    assert!(circle.is_some(), "Should find 'Circle' class");

    // Enum
    let direction = chunks
        .iter()
        .find(|c| c.name == "Direction" && c.chunk_type == ChunkType::Enum);
    assert!(direction.is_some(), "Should find 'Direction' enum");

    // Top-level function
    let greet = chunks.iter().find(|c| c.name == "greet");
    assert!(greet.is_some(), "Should find 'greet' function");
}

// ===== Objective-C tests =====

#[test]
fn test_objc_class_and_protocol_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.m");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 3,
        "Expected at least 3 Objective-C chunks, got {}",
        chunks.len()
    );

    // Protocol
    let drawable = chunks
        .iter()
        .find(|c| c.name == "Drawable" && c.chunk_type == ChunkType::Interface);
    assert!(drawable.is_some(), "Should find 'Drawable' protocol");
    assert_eq!(drawable.unwrap().language, Language::ObjC);

    // Class
    let rect = chunks
        .iter()
        .find(|c| c.name == "Rectangle" && c.chunk_type == ChunkType::Class);
    assert!(rect.is_some(), "Should find 'Rectangle' class");

    // Free function
    let calc = chunks.iter().find(|c| c.name == "calculateDistance");
    assert!(calc.is_some(), "Should find 'calculateDistance' function");
}

// ===== Protobuf tests =====

#[test]
fn test_protobuf_message_and_service_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.proto");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 4,
        "Expected at least 4 Protobuf chunks, got {}",
        chunks.len()
    );

    // Message → Struct
    let user = chunks
        .iter()
        .find(|c| c.name == "User" && c.chunk_type == ChunkType::Struct);
    assert!(user.is_some(), "Should find 'User' message (as Struct)");
    assert_eq!(user.unwrap().language, Language::Protobuf);

    // Service → Interface
    let svc = chunks
        .iter()
        .find(|c| c.name == "UserService" && c.chunk_type == ChunkType::Interface);
    assert!(
        svc.is_some(),
        "Should find 'UserService' service (as Interface)"
    );

    // Enum
    let status = chunks
        .iter()
        .find(|c| c.name == "Status" && c.chunk_type == ChunkType::Enum);
    assert!(status.is_some(), "Should find 'Status' enum");

    // RPC → Method (inside service)
    let rpc = chunks
        .iter()
        .find(|c| c.name == "GetUser" && c.chunk_type == ChunkType::Method);
    assert!(
        rpc.is_some(),
        "Should find 'GetUser' RPC (as Method). Types: {:?}",
        chunks
            .iter()
            .map(|c| format!("{}:{}", c.name, c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== GraphQL tests =====

#[test]
fn test_graphql_type_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.graphql");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 6,
        "Expected at least 6 GraphQL chunks, got {}",
        chunks.len()
    );

    // Object type → Struct
    let user = chunks
        .iter()
        .find(|c| c.name == "User" && c.chunk_type == ChunkType::Struct);
    assert!(user.is_some(), "Should find 'User' type (as Struct)");
    assert_eq!(user.unwrap().language, Language::GraphQL);

    // Interface
    let node = chunks
        .iter()
        .find(|c| c.name == "Node" && c.chunk_type == ChunkType::Interface);
    assert!(node.is_some(), "Should find 'Node' interface");

    // Enum
    let status = chunks
        .iter()
        .find(|c| c.name == "Status" && c.chunk_type == ChunkType::Enum);
    assert!(status.is_some(), "Should find 'Status' enum");

    // Union → TypeAlias
    let search = chunks
        .iter()
        .find(|c| c.name == "SearchResult" && c.chunk_type == ChunkType::TypeAlias);
    assert!(
        search.is_some(),
        "Should find 'SearchResult' union (as TypeAlias)"
    );

    // Operation → Function
    let query = chunks
        .iter()
        .find(|c| c.name == "GetUser" && c.chunk_type == ChunkType::Function);
    assert!(
        query.is_some(),
        "Should find 'GetUser' operation (as Function)"
    );

    // Fragment → Function
    let frag = chunks
        .iter()
        .find(|c| c.name == "UserFields" && c.chunk_type == ChunkType::Function);
    assert!(
        frag.is_some(),
        "Should find 'UserFields' fragment (as Function)"
    );
}

// ===== PHP tests =====

#[test]
fn test_php_class_and_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.php");
    let chunks = parser.parse_file(&path).unwrap();

    assert!(
        chunks.len() >= 5,
        "Expected at least 5 PHP chunks, got {}",
        chunks.len()
    );

    // Class
    let user = chunks
        .iter()
        .find(|c| c.name == "User" && c.chunk_type == ChunkType::Class);
    assert!(user.is_some(), "Should find 'User' class");
    assert_eq!(user.unwrap().language, Language::Php);

    // Interface
    let printable = chunks
        .iter()
        .find(|c| c.name == "Printable" && c.chunk_type == ChunkType::Interface);
    assert!(printable.is_some(), "Should find 'Printable' interface");

    // Trait
    let ts = chunks
        .iter()
        .find(|c| c.name == "Timestampable" && c.chunk_type == ChunkType::Trait);
    assert!(ts.is_some(), "Should find 'Timestampable' trait");

    // Enum
    let status = chunks
        .iter()
        .find(|c| c.name == "Status" && c.chunk_type == ChunkType::Enum);
    assert!(status.is_some(), "Should find 'Status' enum");

    // Free function
    let fmt = chunks.iter().find(|c| c.name == "formatDuration");
    assert!(fmt.is_some(), "Should find 'formatDuration' function");
}

#[test]
#[cfg(feature = "lang-lua")]
fn test_lua_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.lua");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Lua chunks from sample.lua"
    );
    let greet = chunks.iter().find(|c| c.name == "greet");
    assert!(greet.is_some(), "Should find 'greet' function");
    assert_eq!(greet.unwrap().chunk_type, ChunkType::Function);

    let fibonacci = chunks.iter().find(|c| c.name == "fibonacci");
    assert!(fibonacci.is_some(), "Should find 'fibonacci' function");
}

#[test]
#[cfg(feature = "lang-zig")]
fn test_zig_function_and_struct_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.zig");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Zig chunks from sample.zig"
    );

    // Function
    let add = chunks.iter().find(|c| c.name == "add");
    assert!(add.is_some(), "Should find 'add' function");
    assert_eq!(add.unwrap().chunk_type, ChunkType::Function);

    // Struct
    let point = chunks
        .iter()
        .find(|c| c.name == "Point" && c.chunk_type == ChunkType::Struct);
    assert!(point.is_some(), "Should find 'Point' struct");

    // Enum
    let color = chunks
        .iter()
        .find(|c| c.name == "Color" && c.chunk_type == ChunkType::Enum);
    assert!(color.is_some(), "Should find 'Color' enum");
}

#[test]
#[cfg(feature = "lang-r")]
fn test_r_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.r");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(!chunks.is_empty(), "Should extract R chunks from sample.r");
    let calc = chunks.iter().find(|c| c.name == "calculate_mean");
    assert!(calc.is_some(), "Should find 'calculate_mean' function");
    assert_eq!(calc.unwrap().chunk_type, ChunkType::Function);

    let filter = chunks.iter().find(|c| c.name == "filter_above");
    assert!(filter.is_some(), "Should find 'filter_above' function");
}

#[test]
#[cfg(feature = "lang-yaml")]
fn test_yaml_key_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.yaml");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract YAML chunks from sample.yaml"
    );
    let names: Vec<_> = chunks.iter().map(|c| c.name.as_str()).collect();
    assert!(
        names.contains(&"name"),
        "Should find 'name' key, got: {:?}",
        names
    );
}

#[test]
#[cfg(feature = "lang-toml")]
fn test_toml_table_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.toml");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract TOML chunks from sample.toml"
    );
    let names: Vec<_> = chunks.iter().map(|c| c.name.as_str()).collect();
    assert!(
        names.contains(&"package"),
        "Should find 'package' table, got: {:?}",
        names
    );
}

#[test]
#[cfg(feature = "lang-elixir")]
fn test_elixir_function_and_module_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.ex");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Elixir chunks from sample.ex"
    );

    // Function
    let add = chunks.iter().find(|c| c.name == "add");
    assert!(add.is_some(), "Should find 'add' function");
    assert_eq!(add.unwrap().chunk_type, ChunkType::Function);

    // Module
    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Module);
    assert!(calc.is_some(), "Should find 'Calculator' module");
}

#[test]
#[cfg(feature = "lang-erlang")]
fn test_erlang_function_and_module_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.erl");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Erlang chunks from sample.erl"
    );

    // Function
    let add = chunks.iter().find(|c| c.name == "add");
    assert!(add.is_some(), "Should find 'add' function");
    assert_eq!(add.unwrap().chunk_type, ChunkType::Function);

    // Module
    let module = chunks
        .iter()
        .find(|c| c.name == "calculator" && c.chunk_type == ChunkType::Module);
    assert!(module.is_some(), "Should find 'calculator' module");
}

// ===== OCaml tests =====

#[test]
#[cfg(feature = "lang-ocaml")]
fn test_ocaml_function_and_type_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.ml");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract OCaml chunks from sample.ml"
    );

    // Function
    let add = chunks.iter().find(|c| c.name == "add");
    assert!(add.is_some(), "Should find 'add' function");
    assert_eq!(add.unwrap().chunk_type, ChunkType::Function);

    // Variant type (Enum)
    let color = chunks
        .iter()
        .find(|c| c.name == "color" && c.chunk_type == ChunkType::Enum);
    assert!(color.is_some(), "Should find 'color' variant type as Enum");

    // Module
    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Module);
    assert!(calc.is_some(), "Should find 'Calculator' module");
}

// ===== Julia tests =====

#[test]
#[cfg(feature = "lang-julia")]
fn test_julia_function_and_struct_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.jl");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Julia chunks from sample.jl"
    );

    // Function (greet is top-level)
    let greet = chunks.iter().find(|c| c.name == "greet");
    assert!(greet.is_some(), "Should find 'greet' function");
    assert_eq!(greet.unwrap().chunk_type, ChunkType::Function);

    // Struct
    let point = chunks
        .iter()
        .find(|c| c.name == "Point" && c.chunk_type == ChunkType::Struct);
    assert!(point.is_some(), "Should find 'Point' struct");

    // Module
    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Module);
    assert!(calc.is_some(), "Should find 'Calculator' module");
}

// ===== Gleam tests =====

#[test]
#[cfg(feature = "lang-gleam")]
fn test_gleam_function_and_type_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.gleam");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Gleam chunks from sample.gleam"
    );

    // Function
    let add = chunks.iter().find(|c| c.name == "add");
    assert!(add.is_some(), "Should find 'add' function");
    assert_eq!(add.unwrap().chunk_type, ChunkType::Function);

    // Custom type (Enum)
    let color = chunks
        .iter()
        .find(|c| c.name == "Color" && c.chunk_type == ChunkType::Enum);
    assert!(color.is_some(), "Should find 'Color' type as Enum");

    // Type alias
    let user_id = chunks
        .iter()
        .find(|c| c.name == "UserId" && c.chunk_type == ChunkType::TypeAlias);
    assert!(user_id.is_some(), "Should find 'UserId' type alias");

    // Constant
    let max = chunks
        .iter()
        .find(|c| c.name == "max_retries" && c.chunk_type == ChunkType::Constant);
    assert!(max.is_some(), "Should find 'max_retries' constant");
}

// ===== CSS tests =====

#[test]
#[cfg(feature = "lang-css")]
fn test_css_rule_set_and_keyframes_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.css");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract CSS chunks from sample.css"
    );

    // Rule set (selector as Property)
    let container = chunks
        .iter()
        .find(|c| c.name.contains("container") && c.chunk_type == ChunkType::Property);
    assert!(
        container.is_some(),
        "Should find '.container' rule set as Property"
    );

    // Keyframes (Section)
    let fade = chunks
        .iter()
        .find(|c| c.name == "fadeIn" && c.chunk_type == ChunkType::Section);
    assert!(fade.is_some(), "Should find 'fadeIn' keyframes as Section");
}

// ===== Perl tests =====

#[test]
#[cfg(feature = "lang-perl")]
fn test_perl_subroutine_and_package_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.pl");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Perl chunks from sample.pl"
    );

    // Subroutine
    let greet = chunks.iter().find(|c| c.name == "greet");
    assert!(greet.is_some(), "Should find 'greet' subroutine");
    assert_eq!(greet.unwrap().chunk_type, ChunkType::Function);

    // Package
    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Module);
    assert!(calc.is_some(), "Should find 'Calculator' package as Module");
}

// ===== Haskell tests =====

#[test]
#[cfg(feature = "lang-haskell")]
fn test_haskell_function_and_data_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.hs");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Haskell chunks from sample.hs"
    );

    // Function
    let add = chunks.iter().find(|c| c.name == "add");
    assert!(add.is_some(), "Should find 'add' function");
    assert_eq!(add.unwrap().chunk_type, ChunkType::Function);

    // Data type (Enum)
    let color = chunks
        .iter()
        .find(|c| c.name == "Color" && c.chunk_type == ChunkType::Enum);
    assert!(color.is_some(), "Should find 'Color' data type");

    // Typeclass (Trait)
    let printable = chunks
        .iter()
        .find(|c| c.name == "Printable" && c.chunk_type == ChunkType::Trait);
    assert!(printable.is_some(), "Should find 'Printable' typeclass");
}

// ===== HTML tests =====

#[test]
#[cfg(feature = "lang-html")]
fn test_html_heading_and_landmark_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.html");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract HTML chunks from sample.html"
    );

    // Heading
    let h1 = chunks
        .iter()
        .find(|c| c.name == "Welcome to the Sample" && c.chunk_type == ChunkType::Section);
    assert!(h1.is_some(), "Should find h1 heading as Section");

    // Script module (external src — no raw_text, kept as Module)
    let script = chunks
        .iter()
        .find(|c| c.name.contains("main.js") && c.chunk_type == ChunkType::Module);
    assert!(
        script.is_some(),
        "Should find script as Module, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    // Injected JS functions from inline <script>
    let js_funcs: Vec<_> = chunks
        .iter()
        .filter(|c| c.language == Language::JavaScript)
        .collect();
    assert!(
        js_funcs.iter().any(|c| c.name == "handleClick"),
        "Should find injected JS function 'handleClick', got: {:?}",
        js_funcs.iter().map(|c| &c.name).collect::<Vec<_>>()
    );
    assert!(
        js_funcs.iter().any(|c| c.name == "setupListeners"),
        "Should find injected JS function 'setupListeners', got: {:?}",
        js_funcs.iter().map(|c| &c.name).collect::<Vec<_>>()
    );
}

// ===== JSON tests =====

#[test]
#[cfg(feature = "lang-json")]
fn test_json_top_level_key_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.json");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract JSON chunks from sample.json"
    );

    let names: Vec<_> = chunks.iter().map(|c| c.name.as_str()).collect();
    assert!(
        names.contains(&"name"),
        "Should find 'name' key, got: {:?}",
        names
    );
    assert!(
        names.contains(&"version"),
        "Should find 'version' key, got: {:?}",
        names
    );
}

// ===== XML tests =====

#[test]
#[cfg(feature = "lang-xml")]
fn test_xml_element_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.xml");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract XML chunks from sample.xml"
    );

    let catalog = chunks
        .iter()
        .find(|c| c.name == "catalog" && c.chunk_type == ChunkType::Struct);
    assert!(
        catalog.is_some(),
        "Should find 'catalog' element as Struct, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== INI tests =====

#[test]
#[cfg(feature = "lang-ini")]
fn test_ini_section_and_setting_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.ini");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract INI chunks from sample.ini"
    );

    let database = chunks
        .iter()
        .find(|c| c.name == "database" && c.chunk_type == ChunkType::Module);
    assert!(
        database.is_some(),
        "Should find 'database' section as Module, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let host = chunks
        .iter()
        .find(|c| c.name == "host" && c.chunk_type == ChunkType::Property);
    assert!(host.is_some(), "Should find 'host' setting as Property");
}

// ===== Nix tests =====

#[test]
#[cfg(feature = "lang-nix")]
fn test_nix_function_and_attrset_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.nix");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Nix chunks from sample.nix"
    );

    let func = chunks
        .iter()
        .find(|c| c.name == "greet" && c.chunk_type == ChunkType::Function);
    assert!(
        func.is_some(),
        "Should find 'greet' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let attrset = chunks
        .iter()
        .find(|c| c.name == "config" && c.chunk_type == ChunkType::Struct);
    assert!(
        attrset.is_some(),
        "Should find 'config' attrset as Struct, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== Make tests =====

#[test]
#[cfg(feature = "lang-make")]
fn test_make_rule_and_variable_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.mk");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Make chunks from sample.mk"
    );

    let rule = chunks
        .iter()
        .find(|c| c.name == "clean" && c.chunk_type == ChunkType::Function);
    assert!(
        rule.is_some(),
        "Should find 'clean' rule as Function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let var = chunks
        .iter()
        .find(|c| c.name == "CC" && c.chunk_type == ChunkType::Property);
    assert!(
        var.is_some(),
        "Should find 'CC' variable as Property, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== LaTeX tests =====

#[test]
#[cfg(feature = "lang-latex")]
fn test_latex_section_and_command_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.tex");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract LaTeX chunks from sample.tex"
    );

    let section = chunks
        .iter()
        .find(|c| c.name == "Introduction" && c.chunk_type == ChunkType::Section);
    assert!(
        section.is_some(),
        "Should find 'Introduction' section, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let cmd = chunks
        .iter()
        .find(|c| c.name == "highlight" && c.chunk_type == ChunkType::Function);
    assert!(
        cmd.is_some(),
        "Should find 'highlight' command as Function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

#[test]
#[cfg(feature = "lang-solidity")]
fn test_solidity_contract_and_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.sol");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Solidity chunks from sample.sol"
    );

    let contract = chunks
        .iter()
        .find(|c| c.name == "Token" && c.chunk_type == ChunkType::Class);
    assert!(
        contract.is_some(),
        "Should find 'Token' contract as Class, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let iface = chunks
        .iter()
        .find(|c| c.name == "IERC20" && c.chunk_type == ChunkType::Interface);
    assert!(
        iface.is_some(),
        "Should find 'IERC20' interface, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let lib = chunks
        .iter()
        .find(|c| c.name == "SafeMath" && c.chunk_type == ChunkType::Module);
    assert!(
        lib.is_some(),
        "Should find 'SafeMath' library as Module, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

#[test]
#[cfg(feature = "lang-cuda")]
fn test_cuda_kernel_and_struct_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.cu");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract CUDA chunks from sample.cu"
    );

    let kernel = chunks
        .iter()
        .find(|c| c.name == "vectorAdd" && c.chunk_type == ChunkType::Function);
    assert!(
        kernel.is_some(),
        "Should find 'vectorAdd' kernel as Function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let s = chunks
        .iter()
        .find(|c| c.name == "DeviceConfig" && c.chunk_type == ChunkType::Struct);
    assert!(
        s.is_some(),
        "Should find 'DeviceConfig' struct, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

#[test]
#[cfg(feature = "lang-glsl")]
fn test_glsl_function_and_struct_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.vert");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract GLSL chunks from sample.vert"
    );

    let func = chunks
        .iter()
        .find(|c| c.name == "transformNormal" && c.chunk_type == ChunkType::Function);
    assert!(
        func.is_some(),
        "Should find 'transformNormal' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let s = chunks
        .iter()
        .find(|c| c.name == "Material" && c.chunk_type == ChunkType::Struct);
    assert!(
        s.is_some(),
        "Should find 'Material' struct, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== End-to-end injection tests =====

/// Full pipeline test: parse HTML with injection → verify chunks + relationships
#[test]
fn test_html_injection_end_to_end() {
    use std::io::Write;

    let content = r#"<html>
<head>
<style>
.container { display: flex; gap: 1rem; }
@media (max-width: 768px) { .container { flex-direction: column; } }
</style>
</head>
<body>
<h1>Dashboard</h1>
<script>
function loadData(url) {
    return fetch(url).then(r => r.json());
}

function renderChart(data) {
    loadData('/api/stats');
    console.log(data);
}
</script>
<script src="vendor.js"></script>
</body>
</html>
"#;

    let mut file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
    file.write_all(content.as_bytes()).unwrap();
    file.flush().unwrap();

    let parser = Parser::new().unwrap();

    // 1. Parse chunks
    let chunks = parser.parse_file(file.path()).unwrap();

    // HTML heading
    assert!(
        chunks
            .iter()
            .any(|c| c.name == "Dashboard" && c.chunk_type == ChunkType::Section),
        "HTML heading should be extracted"
    );

    // JS functions from inline script
    let js_chunks: Vec<_> = chunks
        .iter()
        .filter(|c| c.language == Language::JavaScript)
        .collect();
    assert!(
        js_chunks.iter().any(|c| c.name == "loadData"),
        "JS function 'loadData' should be extracted via injection"
    );
    assert!(
        js_chunks.iter().any(|c| c.name == "renderChart"),
        "JS function 'renderChart' should be extracted via injection"
    );

    // CSS chunks from style
    let css_chunks: Vec<_> = chunks
        .iter()
        .filter(|c| c.language == Language::Css)
        .collect();
    assert!(
        !css_chunks.is_empty(),
        "CSS chunks should be extracted from <style>"
    );

    // External script module preserved
    assert!(
        chunks
            .iter()
            .any(|c| c.chunk_type == ChunkType::Module && c.name.contains("vendor.js")),
        "External script Module should be preserved"
    );

    // 2. Parse relationships (calls + types)
    let (calls, _types) = parser.parse_file_relationships(file.path()).unwrap();

    // JS call graph: renderChart → loadData
    let render_calls = calls.iter().find(|c| c.name == "renderChart");
    assert!(
        render_calls.is_some(),
        "Call graph should include 'renderChart', got: {:?}",
        calls.iter().map(|c| &c.name).collect::<Vec<_>>()
    );
    let callees: Vec<_> = render_calls
        .unwrap()
        .calls
        .iter()
        .map(|c| c.callee_name.as_str())
        .collect();
    assert!(
        callees.contains(&"loadData"),
        "renderChart should call loadData, got: {:?}",
        callees
    );

    // 3. Per-chunk call extraction also works
    let render_chunk = chunks.iter().find(|c| c.name == "renderChart").unwrap();
    let chunk_calls = parser.extract_calls_from_chunk(render_chunk);
    let chunk_callees: Vec<_> = chunk_calls.iter().map(|c| c.callee_name.as_str()).collect();
    assert!(
        chunk_callees.contains(&"loadData"),
        "extract_calls_from_chunk should find loadData, got: {:?}",
        chunk_callees
    );
}

#[test]
fn test_fenced_blocks_call_extraction() {
    // TC-6: Verify whether parse_file_all extracts function calls from fenced code blocks
    let md_content = r#"# Example

```rust
fn caller() {
    helper();
    another_func();
}

fn helper() -> i32 {
    42
}

fn another_func() {}
```
"#;
    let tmp = std::env::temp_dir().join("test_fenced_calls.md");
    std::fs::write(&tmp, md_content).unwrap();
    let parser = Parser::new().unwrap();
    let (chunks, calls, _type_refs) = parser.parse_file_all(&tmp).unwrap();
    std::fs::remove_file(&tmp).ok();

    // Should extract chunks from fenced blocks
    assert!(
        !chunks.is_empty(),
        "should extract chunks from fenced Rust block"
    );
    let names: Vec<&str> = chunks.iter().map(|c| c.name.as_str()).collect();
    assert!(names.contains(&"caller"), "missing 'caller' function");
    assert!(names.contains(&"helper"), "missing 'helper' function");

    // TC-6 finding: parse_file_all extracts chunks from fenced code blocks (functions
    // are correctly parsed) but does NOT extract call graph edges from within those blocks.
    // The only FunctionCalls entry is the file-level markdown section linkage, not
    // intra-function calls like caller() -> helper(). This is a known limitation.
    let caller_calls: Vec<_> = calls.iter().filter(|c| c.name == "caller").collect();
    assert!(
        caller_calls.is_empty(),
        "Known limitation: fenced block call extraction does not produce calls for \
         inner functions. If this starts passing, the limitation is fixed — \
         update this test to assert the calls."
    );
}

// ===== C# tests =====

#[test]
#[cfg(feature = "lang-csharp")]
fn test_csharp_class_and_method_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.cs");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract C# chunks from sample.cs"
    );

    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Class);
    assert!(
        calc.is_some(),
        "Should find 'Calculator' class, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
    assert_eq!(calc.unwrap().language, Language::CSharp);

    let add = chunks.iter().find(|c| {
        c.name == "Add" && matches!(c.chunk_type, ChunkType::Method | ChunkType::Function)
    });
    assert!(
        add.is_some(),
        "Should find 'Add' method, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let op = chunks
        .iter()
        .find(|c| c.name == "Operation" && c.chunk_type == ChunkType::Enum);
    assert!(
        op.is_some(),
        "Should find 'Operation' enum, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== F# tests =====

#[test]
#[cfg(feature = "lang-fsharp")]
fn test_fsharp_module_and_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.fs");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract F# chunks from sample.fs"
    );

    let add = chunks.iter().find(|c| {
        c.name == "add" && matches!(c.chunk_type, ChunkType::Function | ChunkType::Method)
    });
    assert!(
        add.is_some(),
        "Should find 'add' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
    assert_eq!(add.unwrap().language, Language::FSharp);

    let area = chunks.iter().find(|c| {
        c.name == "area" && matches!(c.chunk_type, ChunkType::Function | ChunkType::Method)
    });
    assert!(
        area.is_some(),
        "Should find 'area' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== PowerShell tests =====

#[test]
#[cfg(feature = "lang-powershell")]
fn test_powershell_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.ps1");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract PowerShell chunks from sample.ps1"
    );

    let get_user = chunks
        .iter()
        .find(|c| c.name == "Get-UserInfo" && c.chunk_type == ChunkType::Function);
    assert!(
        get_user.is_some(),
        "Should find 'Get-UserInfo' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
    assert_eq!(get_user.unwrap().language, Language::PowerShell);

    let set_user = chunks
        .iter()
        .find(|c| c.name == "Set-UserStatus" && c.chunk_type == ChunkType::Function);
    assert!(
        set_user.is_some(),
        "Should find 'Set-UserStatus' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== Scala tests =====

#[test]
#[cfg(feature = "lang-scala")]
fn test_scala_class_and_trait_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.scala");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Scala chunks from sample.scala"
    );

    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Class);
    assert!(
        calc.is_some(),
        "Should find 'Calculator' class, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
    assert_eq!(calc.unwrap().language, Language::Scala);

    let printable = chunks.iter().find(|c| {
        c.name == "Printable" && matches!(c.chunk_type, ChunkType::Interface | ChunkType::Trait)
    });
    assert!(
        printable.is_some(),
        "Should find 'Printable' trait, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let math_utils = chunks.iter().find(|c| {
        c.name == "MathUtils"
            && matches!(
                c.chunk_type,
                ChunkType::Object | ChunkType::Module | ChunkType::Class
            )
    });
    assert!(
        math_utils.is_some(),
        "Should find 'MathUtils' object, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== Ruby tests =====

#[test]
#[cfg(feature = "lang-ruby")]
fn test_ruby_class_and_module_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.rb");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Ruby chunks from sample.rb"
    );

    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Class);
    assert!(
        calc.is_some(),
        "Should find 'Calculator' class, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
    assert_eq!(calc.unwrap().language, Language::Ruby);

    let add = chunks.iter().find(|c| {
        c.name == "add" && matches!(c.chunk_type, ChunkType::Method | ChunkType::Function)
    });
    assert!(
        add.is_some(),
        "Should find 'add' method, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let helpers = chunks
        .iter()
        .find(|c| c.name == "MathHelpers" && c.chunk_type == ChunkType::Module);
    assert!(
        helpers.is_some(),
        "Should find 'MathHelpers' module, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== Vue tests =====

#[test]
#[cfg(feature = "lang-vue")]
fn test_vue_script_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.vue");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Vue chunks from sample.vue"
    );

    // Vue should extract JS functions from the <script> block via injection
    let has_js = chunks.iter().any(|c| c.language == Language::JavaScript);
    assert!(
        has_js,
        "Should extract JavaScript chunks from Vue <script>, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type, &c.language))
            .collect::<Vec<_>>()
    );
}

// ===== Svelte tests =====

#[test]
#[cfg(feature = "lang-svelte")]
fn test_svelte_script_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.svelte");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Svelte chunks from sample.svelte"
    );

    // Svelte should extract JS functions from the <script> block via injection
    let has_js = chunks.iter().any(|c| c.language == Language::JavaScript);
    assert!(
        has_js,
        "Should extract JavaScript chunks from Svelte <script>, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type, &c.language))
            .collect::<Vec<_>>()
    );

    let increment = chunks.iter().find(|c| c.name == "increment");
    assert!(
        increment.is_some(),
        "Should find 'increment' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}

// ===== Razor tests =====

#[test]
#[cfg(feature = "lang-razor")]
fn test_razor_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.cshtml");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract Razor chunks from sample.cshtml, got empty"
    );

    // Razor extracts C# from @functions blocks via injection
    let names: Vec<_> = chunks.iter().map(|c| (&c.name, &c.chunk_type)).collect();
    assert!(
        chunks.len() >= 1,
        "Should extract at least 1 chunk from Razor file, got: {:?}",
        names
    );
}

// ===== VB.NET tests =====

#[test]
#[cfg(feature = "lang-vbnet")]
fn test_vbnet_class_and_function_extraction() {
    let parser = Parser::new().unwrap();
    let path = fixtures_path().join("sample.vb");
    let chunks = parser.parse_file(&path).unwrap();
    assert!(
        !chunks.is_empty(),
        "Should extract VB.NET chunks from sample.vb"
    );

    let calc = chunks
        .iter()
        .find(|c| c.name == "Calculator" && c.chunk_type == ChunkType::Class);
    assert!(
        calc.is_some(),
        "Should find 'Calculator' class, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
    assert_eq!(calc.unwrap().language, Language::VbNet);

    let add = chunks.iter().find(|c| {
        c.name == "Add" && matches!(c.chunk_type, ChunkType::Function | ChunkType::Method)
    });
    assert!(
        add.is_some(),
        "Should find 'Add' function, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );

    let op = chunks
        .iter()
        .find(|c| c.name == "Operation" && c.chunk_type == ChunkType::Enum);
    assert!(
        op.is_some(),
        "Should find 'Operation' enum, got: {:?}",
        chunks
            .iter()
            .map(|c| (&c.name, &c.chunk_type))
            .collect::<Vec<_>>()
    );
}
