use crate::models::{CallIntent, EntityKind, ParsedEntity, ReferenceIntent};
use crate::pipeline::parser::utils::{
    extract_identifiers_from_decorator, extract_new_expression_name, is_capitalized, node_text,
};
use tree_sitter::Node;
use uuid::Uuid;

/// Recursively extract all call intents from JavaScript, returning (intent, byte_pos) pairs.
pub(crate) fn collect_all_reference_intents_javascript(
    node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<(ReferenceIntent, usize)>,
) {
    let byte_pos = node.start_byte();
    let line = node.start_position().row + 1;

    match node.kind() {
        "call_expression" | "new_expression" => {
            // Use non-recursive extraction to avoid double-processing children
            let call_intents = extract_single_call_intent_javascript(node, source);
            for call in call_intents {
                intents.push((
                    ReferenceIntent::Call {
                        method: call.method,
                        receiver: call.receiver,
                        line,
                        arg_count: call.arg_count,
                    },
                    byte_pos,
                ));
            }
        }
        "jsx_self_closing_element" | "jsx_opening_element" => {
            let mut call_intents = Vec::new();
            extract_jsx_component_invocation(node, source, &mut call_intents);
            for call in call_intents {
                intents.push((
                    ReferenceIntent::Call {
                        method: call.method,
                        receiver: call.receiver,
                        line,
                        arg_count: call.arg_count,
                    },
                    byte_pos,
                ));
            }
        }
        "decorator" => {
            // Extract decorator references (e.g., @Component({ declarations: [AppComponent] }))
            let mut decorator_refs = Vec::new();
            extract_identifiers_from_decorator(node, source, &mut decorator_refs, line);
            for ref_intent in decorator_refs {
                intents.push((ref_intent, byte_pos));
            }
        }
        "import_statement" => {
            collect_import_intents_javascript(node, source, intents, byte_pos, line, false);
        }
        "lexical_declaration" | "variable_declaration" => {
            collect_require_destructure_intents(node, source, intents, byte_pos, line);
        }
        _ => {}
    }

    // Recursively process children
    let mut child = node.child(0);
    while let Some(c) = child {
        collect_all_reference_intents_javascript(c, source, intents);
        child = c.next_sibling();
    }
}

pub(crate) fn collect_import_intents_javascript(
    node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<(ReferenceIntent, usize)>,
    byte_pos: usize,
    line: usize,
    is_type_import: bool,
) {
    let mut cursor = node.walk();
    let import_clause = node
        .children(&mut cursor)
        .find(|c| c.kind() == "import_clause");
    let Some(clause) = import_clause else {
        return;
    };

    let mut clause_child = clause.child(0);
    while let Some(c) = clause_child {
        match c.kind() {
            "named_imports" => {
                let mut spec_child = c.child(0);
                while let Some(spec) = spec_child {
                    if spec.kind() == "import_specifier" {
                        let name_node = spec.child_by_field_name("name");
                        if let Some(nn) = name_node {
                            let name = node_text(nn, source);
                            push_import_ref_if_capitalized(
                                name,
                                is_type_import,
                                intents,
                                byte_pos,
                                line,
                            );
                        }
                    }
                    spec_child = spec.next_sibling();
                }
            }
            "identifier" => {
                let name = node_text(c, source);
                push_import_ref_if_capitalized(name, is_type_import, intents, byte_pos, line);
            }
            _ => {}
        }
        clause_child = c.next_sibling();
    }
}

fn push_import_ref_if_capitalized(
    name: String,
    is_type_import: bool,
    intents: &mut Vec<(ReferenceIntent, usize)>,
    byte_pos: usize,
    line: usize,
) {
    if is_capitalized(&name) {
        if is_type_import {
            intents.push((
                ReferenceIntent::TypeReference {
                    type_name: name,
                    line,
                },
                byte_pos,
            ));
        } else {
            intents.push((
                ReferenceIntent::ValueReference {
                    value_name: name,
                    line,
                },
                byte_pos,
            ));
        }
    }
}

fn collect_require_destructure_intents(
    node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<(ReferenceIntent, usize)>,
    byte_pos: usize,
    line: usize,
) {
    let mut declarator = None;
    let mut child = node.child(0);
    while let Some(c) = child {
        if c.kind() == "variable_declarator" {
            declarator = Some(c);
            break;
        }
        child = c.next_sibling();
    }
    let Some(decl) = declarator else {
        return;
    };

    let Some(value) = decl.child_by_field_name("value") else {
        return;
    };
    if value.kind() != "call_expression" {
        return;
    }
    let Some(func) = value.child_by_field_name("function") else {
        return;
    };
    if func.kind() != "identifier" || node_text(func, source) != "require" {
        return;
    }

    let Some(name_node) = decl.child_by_field_name("name") else {
        return;
    };
    if name_node.kind() != "object_pattern" {
        return;
    }

    let mut pattern_child = name_node.child(0);
    while let Some(pc) = pattern_child {
        match pc.kind() {
            "shorthand_property_identifier_pattern" => {
                let name = node_text(pc, source);
                if is_capitalized(&name) {
                    intents.push((
                        ReferenceIntent::ValueReference {
                            value_name: name,
                            line,
                        },
                        byte_pos,
                    ));
                }
            }
            "pair_pattern" => {
                if let Some(key) = pc.child_by_field_name("key") {
                    let name = node_text(key, source);
                    if is_capitalized(&name) {
                        intents.push((
                            ReferenceIntent::ValueReference {
                                value_name: name,
                                line,
                            },
                            byte_pos,
                        ));
                    }
                }
            }
            _ => {}
        }
        pattern_child = pc.next_sibling();
    }
}

/// Extract class inheritance (extends clause) from JavaScript class AST.
/// JavaScript doesn't have implements, so we only handle extends.
/// Navigates through the `class_heritage` wrapper to find the parent class expression.
pub(crate) fn extract_class_inheritance_js(
    class_node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<ReferenceIntent>,
) {
    let line = class_node.start_position().row + 1;

    let mut child = class_node.child(0);
    while let Some(c) = child {
        if c.kind() == "class_heritage" {
            let parent_name = extract_js_heritage_name(c, source);
            if let Some(name) = parent_name {
                intents.push(ReferenceIntent::Extends { parent: name, line });
            }
        }
        child = c.next_sibling();
    }
}

fn extract_js_heritage_name(node: Node<'_>, source: &[u8]) -> Option<String> {
    let mut child = node.child(0);
    while let Some(c) = child {
        match c.kind() {
            "identifier" => return Some(node_text(c, source)),
            "member_expression" => {
                return node_text(c, source)
                    .split('.')
                    .next_back()
                    .map(|s| s.to_string());
            }
            _ => {
                if let Some(name) = extract_js_heritage_name(c, source) {
                    return Some(name);
                }
            }
        }
        child = c.next_sibling();
    }
    None
}

/// Extract reference intents from a JavaScript function/method body (wrapper for backward compatibility).
pub(crate) fn extract_reference_intents_javascript(
    node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<ReferenceIntent>,
) {
    let mut call_intents = Vec::new();
    extract_call_intents_javascript(node, source, &mut call_intents);
    for call in call_intents {
        intents.push(ReferenceIntent::Call {
            method: call.method,
            receiver: call.receiver,
            line: call.line,
            arg_count: call.arg_count,
        });
    }

    // Also extract enum/static member usages (e.g., ClassName.STATIC, Constants.VALUE)
    extract_enum_usages_javascript(node, source, intents);
}

/// Extract call expression call intents from a JavaScript function/method body.
///
/// Handles:
/// - Direct calls: `method()`, `this.method()`
/// - Member calls: `obj.method()`, `this.service.method()`
/// - New expressions: `new MyClass()`
/// - JSX components: `<ChartToolbar />`, `<Sheet.Content />`
/// - Callbacks passed as arguments: `app.use(this.handler)` -> records call to handler
/// - Bind calls: `this.method.bind(this)` -> records call to method
/// - Property/getter access: `this.client`, `this.field` -> records access to property/getter
pub(crate) fn extract_call_intents_javascript(
    node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<CallIntent>,
) {
    intents.extend(extract_single_call_intent_javascript(node, source));

    let mut child = node.child(0);
    while let Some(c) = child {
        extract_call_intents_javascript(c, source, intents);
        child = c.next_sibling();
    }
}

/// Extract call intents from a SINGLE node without recursive descent.
///
/// This is the non-recursive version of `extract_call_intents_javascript`,
/// designed to be used in contexts where the caller already handles tree traversal.
pub(crate) fn extract_single_call_intent_javascript(
    node: Node<'_>,
    source: &[u8],
) -> Vec<CallIntent> {
    let mut intents = Vec::new();

    if node.kind() == "call_expression" {
        let line = node.start_position().row + 1;

        let mut method_name: Option<String> = None;
        let mut receiver: Option<String> = None;

        let mut child = node.child(0);
        let mut is_bind_call = false;

        while let Some(c) = child {
            if c.kind() == "member_expression" {
                if let Some(property_node) = c.child_by_field_name("property") {
                    let prop_text = node_text(property_node, source);
                    if prop_text == "bind" {
                        is_bind_call = true;
                    }
                    method_name = Some(prop_text);
                }

                if let Some(object_node) = c.child_by_field_name("object") {
                    receiver = Some(node_text(object_node, source));
                }
            } else if c.kind() == "identifier" {
                method_name = Some(node_text(c, source));
            }
            child = c.next_sibling();
        }

        if let Some(method) = method_name {
            if is_bind_call {
                if let Some(receiver) = receiver
                    && let Some(last_part) = receiver.split('.').next_back()
                {
                    intents.push(CallIntent {
                        method: last_part.to_string(),
                        receiver: if receiver.contains('.') {
                            receiver.split('.').next().map(|s| s.to_string())
                        } else {
                            Some("this".to_string())
                        },
                        line,
                        arg_count: None,
                    });
                }
            } else {
                intents.push(CallIntent {
                    method,
                    receiver,
                    line,
                    arg_count: None,
                });
            }
        }

        // Also scan arguments for callback references
        extract_callback_arguments(node, source, &mut intents, line);
    } else if node.kind() == "new_expression" {
        let line = node.start_position().row + 1;
        if let Some(name) = extract_new_expression_name(node, source) {
            intents.push(CallIntent {
                method: name,
                receiver: None,
                line,
                arg_count: None,
            });
        }
    } else if node.kind() == "jsx_self_closing_element" || node.kind() == "jsx_opening_element" {
        // JSX component invocation
        extract_jsx_component_invocation(node, source, &mut intents);
    } else if node.kind() == "member_expression" {
        // Detect property/getter access via `this.property`
        if let Some(object_node) = node.child_by_field_name("object")
            && node_text(object_node, source) == "this"
            && let Some(property_node) = node.child_by_field_name("property")
        {
            let prop_text = node_text(property_node, source);
            let line = node.start_position().row + 1;
            intents.push(CallIntent {
                method: prop_text,
                receiver: Some("this".to_string()),
                line,
                arg_count: None,
            });
        }
    }

    // NO recursive child processing - that's the key difference!
    intents
}

/// Extract JSX component invocation as a call intent.
///
/// Handles React components rendered via JSX syntax:
/// - `<ChartToolbar />` → CallIntent { method: "ChartToolbar", receiver: None }
/// - `<Sheet.Content />` → CallIntent { method: "Content", receiver: Some("Sheet") }
/// - `<Icons.Search />` → CallIntent { method: "Search", receiver: Some("Icons") }
///
/// Native HTML tags (lowercase) are ignored:
/// - `<div />` → skipped
/// - `<span />` → skipped
pub(crate) fn extract_jsx_component_invocation(
    node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<CallIntent>,
) {
    let line = node.start_position().row + 1;

    // Get the name node (can be identifier, member_expression, or namespace_name)
    if let Some(name_node) = node.child_by_field_name("name") {
        let comp_name = node_text(name_node, source);

        // React convention: Components start with uppercase, HTML tags are lowercase
        if is_capitalized(&comp_name) {
            // Handle namespaced components (e.g., Sheet.Content, Icons.Search)
            if comp_name.contains('.') {
                let mut parts = comp_name.split('.');
                let receiver = parts.next().map(|s| s.to_string());
                // Collect remaining parts as method name (handles deeply nested components)
                let method = parts.collect::<Vec<_>>().join(".");

                intents.push(CallIntent {
                    method,
                    receiver,
                    line,
                    arg_count: None,
                });
            } else {
                // Simple component name
                intents.push(CallIntent {
                    method: comp_name,
                    receiver: None,
                    line,
                    arg_count: None,
                });
            }
        }
        // HTML tags (lowercase first letter) are intentionally skipped
    }
}

/// Extract callback arguments from a call expression.
///
/// Detects method references passed as arguments, e.g.:
/// - `app.use(this.authHandler)` -> records call to authHandler
/// - `emitter.on('event', this.handler)` -> records call to handler
/// - `addEventListener('click', this.onClick)` -> records call to onClick
pub(crate) fn extract_callback_arguments(
    call_node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<CallIntent>,
    line: usize,
) {
    // Find the arguments node
    if let Some(args_node) = call_node.child_by_field_name("arguments") {
        let mut arg = args_node.child(0);
        while let Some(a) = arg {
            // Look for member_expression arguments (e.g., this.handler, obj.method)
            if a.kind() == "member_expression" {
                if let Some(property_node) = a.child_by_field_name("property") {
                    let method_name = node_text(property_node, source);
                    if let Some(object_node) = a.child_by_field_name("object") {
                        let receiver = node_text(object_node, source);
                        intents.push(CallIntent {
                            method: method_name,
                            receiver: Some(receiver),
                            line,
                            arg_count: None,
                        });
                    }
                }
            } else if a.kind() == "identifier" {
                // Sometimes callbacks are just identifiers: app.use(authHandler)
                let name = node_text(a, source);
                // Only treat as callback if it looks like a method name (not a keyword or literal)
                if !is_reserved_keyword(&name)
                    && name.chars().next().is_some_and(|c| c.is_alphabetic())
                {
                    intents.push(CallIntent {
                        method: name,
                        receiver: None,
                        line,
                        arg_count: None,
                    });
                }
            }
            arg = a.next_sibling();
        }
    }
}

/// Check if a string is a JavaScript reserved keyword.
pub(crate) fn is_reserved_keyword(word: &str) -> bool {
    matches!(
        word,
        "true"
            | "false"
            | "null"
            | "undefined"
            | "this"
            | "super"
            | "import"
            | "export"
            | "from"
            | "as"
            | "async"
            | "await"
            | "yield"
            | "return"
            | "throw"
            | "try"
            | "catch"
            | "finally"
            | "if"
            | "else"
            | "for"
            | "while"
            | "do"
            | "break"
            | "continue"
            | "switch"
            | "case"
            | "default"
            | "const"
            | "let"
            | "var"
            | "class"
            | "function"
            | "new"
            | "delete"
            | "typeof"
            | "instanceof"
            | "in"
            | "of"
            | "static"
            | "interface"
            | "enum"
            | "type"
            | "public"
            | "private"
            | "protected"
            | "readonly"
            | "abstract"
            | "extends"
            | "implements"
            | "declare"
    )
}

/// Extract enum and static member usages from a JavaScript node (e.g., ClassName.STATIC).
///
/// Recursively searches for member_expression nodes where the object is a capitalized identifier,
/// which typically represents class static member access patterns.
pub(crate) fn extract_enum_usages_javascript(
    node: Node<'_>,
    source: &[u8],
    intents: &mut Vec<ReferenceIntent>,
) {
    if node.kind() == "member_expression" {
        // member_expression has: object . property
        // We only want to capture if object is a capitalized identifier (class name)
        if let Some(object_node) = node.child_by_field_name("object")
            && object_node.kind() == "identifier"
        {
            let obj_text = node_text(object_node, source);
            // Check if it starts with capital letter (typical of classes)
            if is_capitalized(&obj_text) {
                let line = object_node.start_position().row + 1;
                intents.push(ReferenceIntent::TypeReference {
                    type_name: obj_text,
                    line,
                });
            }
        }
    }

    // Recursively process children
    let mut child = node.child(0);
    while let Some(c) = child {
        extract_enum_usages_javascript(c, source, intents);
        child = c.next_sibling();
    }
}

/// Extract HTML attributes (id, className) from JSX elements.
///
/// Used to index React components' HTML attributes for cross-language search
/// (e.g., finding which components use a specific CSS class).
///
/// Extracts:
/// - `id="my-id"` → HtmlId entity with name "my-id"
/// - `className="btn primary"` → HtmlClass entities for "btn" and "primary"
///
/// Returns a vector of tuples (attribute_name, attribute_value, line).
pub(crate) fn extract_jsx_attributes(
    node: Node<'_>,
    source: &[u8],
) -> Vec<(String, String, usize)> {
    use crate::pipeline::parser::utils::node_text;

    let mut attributes = Vec::new();

    // JSX attributes are structured as:
    // jsx_attribute
    //   property_identifier (e.g., "id", "className")
    //   jsx_expression | string (the value)
    let mut child = node.child(0);
    while let Some(c) = child {
        if c.kind() == "jsx_attribute" {
            let line = c.start_position().row + 1;
            let mut attr_name = String::new();
            let mut attr_value = String::new();

            // Navigate children to extract property_identifier and value
            let mut attr_child = c.child(0);
            while let Some(ac) = attr_child {
                if ac.kind() == "property_identifier" {
                    attr_name = node_text(ac, source);
                } else if ac.kind() == "string" {
                    // String literal (e.g., "my-id")
                    let raw = node_text(ac, source);
                    // Remove quotes
                    attr_value = raw.trim_matches(|c| c == '"' || c == '\'').to_string();
                } else if ac.kind() == "jsx_expression" {
                    // Expression (e.g., {myVar}) - we skip these for now
                    // Only capture static string values
                    attr_child = ac.next_sibling();
                    continue;
                }
                attr_child = ac.next_sibling();
            }

            // Only capture id and className attributes with non-empty values
            if (attr_name == "id" || attr_name == "className") && !attr_value.is_empty() {
                attributes.push((attr_name, attr_value, line));
            }
        }
        child = c.next_sibling();
    }

    attributes
}

/// Handle DOM and CSS reference captures in JavaScript
/// (dom.element_id, css.class_name, etc.)
pub(crate) fn handle_dom_css_capture(
    cap_name: &str,
    text: &str,
    line: usize,
) -> Option<ReferenceIntent> {
    match cap_name {
        "dom.element_id" => {
            let clean_id = text
                .trim_start_matches('"')
                .trim_start_matches('\'')
                .trim_end_matches('"')
                .trim_end_matches('\'')
                .to_string();

            Some(ReferenceIntent::DomElementReference {
                element_id: clean_id,
                line,
            })
        }
        "css.class_name" | "css.class_assignment" => {
            let clean_class = text
                .trim_start_matches('"')
                .trim_start_matches('\'')
                .trim_end_matches('"')
                .trim_end_matches('\'')
                .to_string();

            Some(ReferenceIntent::CssClassUsage {
                class_name: clean_class,
                line,
            })
        }
        _ => None,
    }
}

/// Extract JSX HTML attributes (id, className) for cross-language search.
///
/// Recursively traverses the AST looking for JSX elements and creates
/// HtmlId and HtmlClass entities from their attributes.
pub(crate) fn extract_jsx_html_attributes(
    node: Node<'_>,
    source: &[u8],
    entities: &mut Vec<ParsedEntity>,
    file_path: &str,
    repo_name: &str,
) {
    // Check if this is a JSX element
    if matches!(
        node.kind(),
        "jsx_self_closing_element" | "jsx_opening_element"
    ) {
        // Extract attributes
        let attrs = extract_jsx_attributes(node, source);

        // Create entities for each extracted attribute
        let line = node.start_position().row + 1;
        for (attr_name, attr_value, _) in attrs {
            if attr_name == "id" {
                // Create HtmlId entity
                entities.push(ParsedEntity {
                    uuid: Uuid::new_v4(),
                    name: attr_value.clone(),
                    kind: EntityKind::HtmlId,
                    fqn: format!("#{}", attr_value),
                    signature: None,
                    docstring: None,
                    inline_comments: Vec::new(),
                    decorators: Vec::new(),
                    language: "javascript".to_string(),
                    file_path: file_path.to_string(),
                    start_line: line,
                    end_line: line,
                    enclosing_class: None,
                    repo_name: repo_name.to_string(),
                    reference_intents: Vec::new(),
                    calls: Vec::new(),
                    relationships: Vec::new(),
                    embed_text: String::new(),
                    rust_attributes: None,
                    impl_trait: None,
                    impl_target: None,
                    generics: None,
                    lifetimes: None,
                    alias_module_path: None,
                    original_export_name: None,
                    enclosing_class_fqn: None,
                    default_export: None,
                    is_test_context: false,
                });
            } else if attr_name == "className" {
                // Split by whitespace and create HtmlClass entity for each class
                for class_name in attr_value.split_whitespace() {
                    if !class_name.is_empty() {
                        entities.push(ParsedEntity {
                            uuid: Uuid::new_v4(),
                            name: class_name.to_string(),
                            kind: EntityKind::HtmlClass,
                            fqn: format!(".{}", class_name),
                            signature: None,
                            docstring: None,
                            inline_comments: Vec::new(),
                            decorators: Vec::new(),
                            language: "javascript".to_string(),
                            file_path: file_path.to_string(),
                            start_line: line,
                            end_line: line,
                            enclosing_class: None,
                            repo_name: repo_name.to_string(),
                            reference_intents: Vec::new(),
                            calls: Vec::new(),
                            relationships: Vec::new(),
                            embed_text: String::new(),
                            rust_attributes: None,
                            impl_trait: None,
                            impl_target: None,
                            generics: None,
                            lifetimes: None,
                            alias_module_path: None,
                            original_export_name: None,
                            enclosing_class_fqn: None,
                            default_export: None,
                            is_test_context: false,
                        });
                    }
                }
            }
        }
    }

    // Recursively process all children
    let mut child = node.child(0);
    while let Some(c) = child {
        extract_jsx_html_attributes(c, source, entities, file_path, repo_name);
        child = c.next_sibling();
    }
}

// ── Alias extraction (require / module.exports) ──────────────────

pub(crate) fn extract_require_module_path(node: Node<'_>, source: &[u8]) -> Option<String> {
    let mut declarator = node;
    if node.kind() == "lexical_declaration" || node.kind() == "variable_declaration" {
        let mut child = node.child(0);
        while let Some(c) = child {
            if c.kind() == "variable_declarator" {
                declarator = c;
                break;
            }
            child = c.next_sibling();
        }
    }

    let value = declarator.child_by_field_name("value")?;
    if value.kind() == "new_expression" {
        if let Some(constructor) = value.child_by_field_name("constructor") {
            extract_require_string(constructor, source)
        } else {
            None
        }
    } else if value.kind() == "call_expression" {
        extract_require_string(value, source)
    } else {
        None
    }
}

fn extract_require_string(call_node: Node<'_>, source: &[u8]) -> Option<String> {
    let func = call_node.child_by_field_name("function")?;
    if func.kind() != "identifier" || node_text(func, source) != "require" {
        return None;
    }
    let args = call_node.child_by_field_name("arguments")?;
    let first_arg = args.child(1)?;
    if first_arg.kind() != "string" {
        return None;
    }
    let raw = node_text(first_arg, source);
    Some(
        raw.trim_matches(|c| c == '\'' || c == '"' || c == '`')
            .to_string(),
    )
}

pub(crate) fn scan_module_exports_target(root: Node<'_>, source: &[u8]) -> Option<String> {
    fn walk(node: Node<'_>, source: &[u8]) -> Option<String> {
        if node.kind() == "assignment_expression" {
            let left = node.child_by_field_name("left")?;
            if left.kind() == "member_expression"
                && let Some(obj) = left.child_by_field_name("object")
                && node_text(obj, source) == "module"
                && let Some(prop) = left.child_by_field_name("property")
                && node_text(prop, source) == "exports"
            {
                let right = node.child_by_field_name("right")?;
                if right.kind() == "identifier" {
                    return Some(node_text(right, source));
                }
            }
        }
        let mut child = node.child(0);
        while let Some(c) = child {
            if let Some(result) = walk(c, source) {
                return Some(result);
            }
            child = c.next_sibling();
        }
        None
    }
    walk(root, source)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pipeline::parser::test_utils::{
        find_call_expression, find_new_expression, find_var_decl,
    };

    #[test]
    fn test_is_reserved_keyword_true() {
        assert!(is_reserved_keyword("true"));
        assert!(is_reserved_keyword("false"));
        assert!(is_reserved_keyword("class"));
        assert!(is_reserved_keyword("function"));
        assert!(is_reserved_keyword("async"));
        assert!(is_reserved_keyword("await"));
    }

    #[test]
    fn test_is_reserved_keyword_false() {
        assert!(!is_reserved_keyword("myVar"));
        assert!(!is_reserved_keyword("handler"));
        assert!(!is_reserved_keyword("MyClass"));
        assert!(!is_reserved_keyword("someFunction"));
    }

    #[test]
    fn test_extract_jsx_component_invocation_simple() {
        crate::pipeline::parser::test_utils::assert_jsx_component_invocation(
            "function render() { return <ChartToolbar />; }",
            crate::pipeline::parser::test_utils::parse_javascript_snippet,
            "ChartToolbar",
            None,
        );
    }

    #[test]
    fn test_extract_jsx_component_invocation_namespaced() {
        crate::pipeline::parser::test_utils::assert_jsx_component_invocation(
            "function render() { return <Sheet.Content />; }",
            crate::pipeline::parser::test_utils::parse_javascript_snippet,
            "Content",
            Some("Sheet"),
        );
    }

    #[test]
    fn test_extract_single_call_intent_javascript_simple() {
        let code = "function test() { method(); }";
        let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
            .expect("Failed to parse JavaScript code");

        if let Some(call) = find_call_expression(tree.root_node()) {
            let code_bytes = code.as_bytes();
            let intents = extract_single_call_intent_javascript(call, code_bytes);
            assert!(!intents.is_empty());
            assert_eq!(intents[0].method, "method");
            assert!(intents[0].receiver.is_none());
        }
    }

    #[test]
    fn test_extract_single_call_intent_javascript_member() {
        let code = "function test() { obj.method(); }";
        let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
            .expect("Failed to parse JavaScript code");

        if let Some(call) = find_call_expression(tree.root_node()) {
            let code_bytes = code.as_bytes();
            let intents = extract_single_call_intent_javascript(call, code_bytes);
            assert!(!intents.is_empty());
            assert_eq!(intents[0].method, "method");
            assert_eq!(intents[0].receiver, Some("obj".to_string()));
        }
    }

    #[test]
    fn test_extract_single_call_intent_javascript_new() {
        let code = "function test() { new MyClass(); }";
        let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
            .expect("Failed to parse JavaScript code");

        if let Some(new_expr) = find_new_expression(tree.root_node()) {
            let code_bytes = code.as_bytes();
            let intents = extract_single_call_intent_javascript(new_expr, code_bytes);
            assert!(!intents.is_empty());
            assert_eq!(intents[0].method, "MyClass");
            assert!(intents[0].receiver.is_none());
        }
    }

    #[test]
    fn test_extract_class_inheritance_js() {
        crate::pipeline::parser::test_utils::assert_js_class_inheritance(
            "class Child extends Parent { }",
            crate::pipeline::parser::test_utils::parse_javascript_snippet,
            "Parent",
        );
    }

    #[test]
    fn test_extract_class_inheritance_js_qualified() {
        crate::pipeline::parser::test_utils::assert_js_class_inheritance(
            "class Child extends NS.Parent { }",
            crate::pipeline::parser::test_utils::parse_javascript_snippet,
            "Parent",
        );
    }

    #[test]
    fn test_extract_jsx_attributes_multiple() {
        // Uses parse_tsx_snippet in typescript.rs (same function, TSX superset of JSX)
        crate::pipeline::parser::test_utils::assert_jsx_attributes_multi(
            r#"function Form() { return <input id="email-input" className="form-control" />; }"#,
            crate::pipeline::parser::test_utils::parse_javascript_snippet,
            2,
            &[("id", "email-input"), ("className", "form-control")],
        );
    }

    #[test]
    fn test_extract_jsx_attributes_classname() {
        crate::pipeline::parser::test_utils::assert_jsx_attribute(
            r#"function Button() { return <button className="btn primary">Click</button>; }"#,
            crate::pipeline::parser::test_utils::parse_javascript_snippet,
            "className",
            "btn primary",
        );
    }

    #[test]
    fn test_extract_require_module_path() {
        let code = "var MyJsAlias = require('./alias_target_js');";
        let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
            .expect("Failed to parse JavaScript code");

        let var_node = find_var_decl(tree.root_node()).unwrap();
        let path = extract_require_module_path(var_node, code.as_bytes());
        assert_eq!(path.as_deref(), Some("./alias_target_js"));
    }

    #[test]
    fn test_js_import_named_emits_ref() {
        let code = "import { Foo } from './types';";
        let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
            .expect("Failed to parse JavaScript code");
        let mut intents = Vec::new();
        collect_all_reference_intents_javascript(tree.root_node(), code.as_bytes(), &mut intents);
        let has_foo = intents.iter().any(|(i, _)| match i {
            ReferenceIntent::ValueReference { value_name, .. } => value_name == "Foo",
            _ => false,
        });
        assert!(
            has_foo,
            "Should emit ValueReference for Foo from named import, got: {:?}",
            intents
        );
    }

    #[test]
    fn test_js_import_aliased_uses_original() {
        let code = "import { Foo as Bar } from './types';";
        let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
            .expect("Failed to parse JavaScript code");
        let mut intents = Vec::new();
        collect_all_reference_intents_javascript(tree.root_node(), code.as_bytes(), &mut intents);
        let has_foo = intents.iter().any(|(i, _)| match i {
            ReferenceIntent::ValueReference { value_name, .. } => value_name == "Foo",
            _ => false,
        });
        let has_bar = intents.iter().any(|(i, _)| match i {
            ReferenceIntent::ValueReference { value_name, .. } => value_name == "Bar",
            _ => false,
        });
        assert!(
            has_foo,
            "Should emit for Foo (original), got: {:?}",
            intents
        );
        assert!(
            !has_bar,
            "Should NOT emit for Bar (alias), got: {:?}",
            intents
        );
    }

    #[test]
    fn test_js_require_destructure_emits_refs() {
        let code = "const { Foo, helper } = require('./m');";
        let tree = crate::pipeline::parser::test_utils::parse_javascript_snippet(code)
            .expect("Failed to parse JavaScript code");
        let mut intents = Vec::new();
        collect_all_reference_intents_javascript(tree.root_node(), code.as_bytes(), &mut intents);
        let has_foo = intents.iter().any(|(i, _)| match i {
            ReferenceIntent::ValueReference { value_name, .. } => value_name == "Foo",
            _ => false,
        });
        assert!(
            has_foo,
            "Should emit ValueReference for Foo from require destructure, got: {:?}",
            intents
        );
    }
}
