"""Tests for dead code detection in TypeScript."""

from __future__ import annotations

from pathlib import Path

from vibelexity.parsers.typescript import parse
from vibelexity.dead_code.parsers.typescript import TypeScriptDeadCodeParser
from vibelexity.dead_code.core import build_cross_reference_graph


def test_unused_named_imports():
    code = """
import { foo, bar } from './module';

function test() {
    foo();
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    imports = parser.extract_imports(root, code)

    import_names = [imp.name for imp in imports]
    assert "foo" in import_names
    assert "bar" in import_names


def test_unused_namespace_imports():
    code = """
import * as utils from './utils';
import { unused } from './other';

function test() {
    utils.foo();
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    imports = parser.extract_imports(root, code)

    import_names = [imp.name for imp in imports]
    assert "utils" in import_names
    assert "unused" in import_names


def test_unused_default_imports():
    code = """
import Foo from './foo';
import Bar from './bar';

function test() {
    new Foo();
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    imports = parser.extract_imports(root, code)

    import_names = [imp.name for imp in imports]
    assert "Foo" in import_names
    assert "Bar" in import_names


def test_type_only_imports_skipped():
    code = """
import type { SomeType } from './types';

function test() {
    // Type-only imports shouldn't be tracked
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    imports = parser.extract_imports(root, code)

    # Type-only imports should be skipped
    import_names = [imp.name for imp in imports]
    assert len(import_names) == 0


def test_dead_function_declarations():
    code = """
function used() {}

function unused() {}

used();
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    defs = parser.extract_definitions(root, code)

    assert len(defs) == 2
    def_names = [d.name for d in defs]
    assert "used" in def_names
    assert "unused" in def_names


def test_dead_arrow_functions():
    code = """
const used = () => {};
const unused = () => {}

used();
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    defs = parser.extract_definitions(root, code)

    assert len(defs) == 2
    def_names = [d.name for d in defs]
    assert "used" in def_names
    assert "unused" in def_names


def test_dead_classes():
    code = """
class Used {}
class Unused {}

new Used();
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    defs = parser.extract_definitions(root, code)

    assert len(defs) == 2
    def_names = [d.name for d in defs]
    assert "Used" in def_names
    assert "Unused" in def_names


def test_exported_functions_not_flagged():
    code = """
    export function publicFunc() {}
    
    export class PublicClass {}
    
    // These shouldn't be flagged as unused
    """
    parser = TypeScriptDeadCodeParser()
    root = parse(code)

    for child in root.children:
        if child.type == "export_statement":
            for grandchild in child.children:
                if grandchild.type in ("function_declaration", "class_declaration"):
                    assert parser.is_exported(grandchild)


def test_extract_method_definitions():
    code = """
class MyClass {
    public method1() {}
    private method2() {}
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    defs = parser.extract_definitions(root, code)

    def_names = [d.name for d in defs]
    method_names = [d.name for d in defs if d.definition_type == "function"]
    # Methods should be tracked by property_identifier
    # The query handles method_definition nodes
    assert len(defs) >= 1


def test_unused_local_variables():
    code = """
    function test() {
        const used = 1;
        const unused = 2;
        return used;
    }
    """
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    refs = parser.extract_references(root, code)

    ref_names = [r.name for r in refs]
    assert "used" in ref_names
    assert "unused" in ref_names


def test_variables_used_in_nested_blocks():
    code = """
function test(x: number) {
    if (x > 0) {
        const y = x + 1;
        return y;
    }
    return 0;
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    refs = parser.extract_references(root, code)

    ref_names = [r.name for r in refs]
    assert "x" in ref_names
    assert "y" in ref_names  # y is used in the if block


def test_shadowing_separate_tracking():
    code = """
function test() {
    let x = 1;
    if (true) {
        let x = 2;
        return x;
    }
    return x;
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    refs = parser.extract_references(root, code)

    # Both x declarations should be used
    ref_names = [r.name for r in refs]
    assert ref_names.count("x") >= 2


def test_class_implementing_interface():
    code = """
interface MyInterface {
    method(x: number): void;
}

class MyClass implements MyInterface {
    method(x: number) {
        return;
    }
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    defs = parser.extract_definitions(root, code)

    # MyClass should be extracted as a class definition
    class_names = [d.name for d in defs if d.definition_type == "class"]
    assert "MyClass" in class_names


def test_react_component_pattern():
    code = """
    import React from 'react';

    export function Component({ name }: { name: string }) {
        return <div>{name}</div>;
    }
    """
    parser = TypeScriptDeadCodeParser()
    root = parse(code)

    for child in root.children:
        if child.type == "export_statement":
            for grandchild in child.children:
                if grandchild.type == "function_declaration":
                    assert parser.is_exported(grandchild)
                    return
    assert False, "Exported function not found"


def test_extract_references():
    code = """
function foo(x: number, y: number): number {
    const result = x + y;
    return result;
}

const test = foo(1, 2);
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    refs = parser.extract_references(root, code)

    ref_names = [r.name for r in refs]
    assert "x" in ref_names
    assert "y" in ref_names
    assert "foo" in ref_names
    assert "test" in ref_names


def test_abstract_class_detection():
    code = """
abstract class AbstractClass {
    abstract method(): void;
}

class ConcreteClass extends AbstractClass {
    method(): void {}
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    abstract_classes, abstract_methods = parser.extract_abstract_info(root, code)

    assert "AbstractClass" in abstract_classes
    assert "method" in abstract_methods


def test_dead_function_with_export():
    code = """
export const used = () => {};
const unused = () => {};

used();
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    defs = parser.extract_definitions(root, code)

    def_names = [d.name for d in defs]
    assert "used" in def_names
    assert "unused" in def_names


def test_default_parameters():
    code = """
function greet(name: string = "World"): string {
    return `Hello, ${name}`;
}

console.log(greet());
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    refs = parser.extract_references(root, code)

    ref_names = [r.name for r in refs]
    assert "name" in ref_names
    assert "greet" in ref_names
    assert "console" in ref_names


def test_for_loop_variables():
    code = """
function test() {
    for (let i = 0; i < 10; i++) {
        console.log(i);
    }
}
"""
    parser = TypeScriptDeadCodeParser()
    root = parse(code)
    refs = parser.extract_references(root, code)

    ref_names = [r.name for r in refs]
    assert "i" in ref_names  # used in loop condition and body


def test_cross_file_dead_code_detection():
    code1 = """
    export { foo, bar } from './module';

    function internalFunc() {}
    export { internalFunc as exportedFunc };
    """

    parser = TypeScriptDeadCodeParser()
    root = parse(code1)
    imports = parser.extract_imports(root, code1)
    defs = parser.extract_definitions(root, code1)

    # local function definition should be extracted
    def_names = [d.name for d in defs]
    assert "internalFunc" in def_names
