//! Tests for TypeScript code extraction.

use super::common::*;
use crate::embed::build_embedding_text;
use crate::parser::Language;

#[test]
fn test_basic_function_with_types() {
    let source = r#"function add(a: number, b: number): number {
    return a + b;
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "add").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: add
Signature: function add(a: number, b: number): number {
Parameters: a, b
Returns: : number
File: test test.ts
Code:
function add(a: number, b: number): number {
    return a + b;
}"#;
    assert_eq!(text, expected);
}

#[test]
fn test_interface_and_function() {
    let source = r#"interface User {
    id: number;
    name: string;
}

function getUser(id: number): User {
    return { id, name: "John" };
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");

    let func = get_unit_by_name(&units, "getUser").unwrap();
    let text = build_embedding_text(func);
    let expected = r#"Function: getUser
Signature: function getUser(id: number): User {
Parameters: id
Returns: : User
File: test test.ts
Code:
function getUser(id: number): User {
    return { id, name: "John" };
}"#;
    assert_eq!(text, expected);
}

#[test]
fn test_generic_function() {
    let source = r#"function identity<T>(value: T): T {
    return value;
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "identity").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: identity
Signature: function identity<T>(value: T): T {
Parameters: value
Returns: : T
File: test test.ts
Code:
function identity<T>(value: T): T {
    return value;
}"#;
    assert_eq!(text, expected);
}

#[test]
fn test_async_function_with_types() {
    let source = r#"async function fetchUser(id: number): Promise<User> {
    const response = await fetch(`/users/${id}`);
    return response.json();
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "fetchUser").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: fetchUser
Signature: async function fetchUser(id: number): Promise<User> {
Parameters: id
Returns: : Promise<User>
Calls: fetch, json
Variables: const, response
File: test test.ts
Code:
async function fetchUser(id: number): Promise<User> {
    const response = await fetch(`/users/${id}`);
    return response.json();
}"#;
    assert_eq!(text, expected);
}

#[test]
fn test_class_with_types() {
    let source = r#"class Calculator {
    private value: number;

    constructor(initial: number = 0) {
        this.value = initial;
    }

    public add(x: number): number {
        this.value += x;
        return this.value;
    }
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");

    // Class is extracted as a single chunk with all methods inside
    let class_unit = get_unit_by_name(&units, "Calculator").unwrap();
    let class_text = build_embedding_text(class_unit);
    assert_eq!(
        class_text,
        r#"Class: Calculator
Signature: class Calculator {
File: test test.ts
Code:
class Calculator {
    private value: number;

    constructor(initial: number = 0) {
        this.value = initial;
    }

    public add(x: number): number {
        this.value += x;
        return this.value;
    }
}"#
    );

    // Verify NO separate method unit exists
    assert!(
        get_unit_by_name(&units, "add").is_some(),
        "Methods are extracted as separate units alongside their parent classes"
    );
}

#[test]
fn test_function_with_optional_params() {
    let source = r#"function greet(name: string, greeting?: string): string {
    return `${greeting || "Hello"}, ${name}!`;
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "greet").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: greet
Signature: function greet(name: string, greeting?: string): string {
Parameters: name, greeting
Returns: : string
File: test test.ts
Code:
function greet(name: string, greeting?: string): string {
    return `${greeting || "Hello"}, ${name}!`;
}"#;
    assert_eq!(text, expected);
}

#[test]
fn test_type_alias() {
    let source = r#"type UserId = number;
type UserMap = Map<UserId, User>;

function getUsers(): UserMap {
    return new Map();
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "getUsers").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: getUsers
Signature: function getUsers(): UserMap {
Returns: : UserMap
File: test test.ts
Code:
function getUsers(): UserMap {
    return new Map();
}"#;
    assert_eq!(text, expected);
}

#[test]
fn test_enum_definition() {
    let source = r#"enum Status {
    Active = "active",
    Inactive = "inactive",
    Pending = "pending"
}

function getStatus(): Status {
    return Status.Active;
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "getStatus").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: getStatus
Signature: function getStatus(): Status {
Returns: : Status
File: test test.ts
Code:
function getStatus(): Status {
    return Status.Active;
}"#;
    assert_eq!(text, expected);
}

#[test]
fn test_arrow_function_with_types() {
    let source = r#"const multiply = (a: number, b: number): number => a * b;"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "multiply").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: multiply
Signature: const multiply = (a: number, b: number): number => a * b;
Parameters: a, b
Returns: : number
File: test test.ts
Code:
const multiply = (a: number, b: number): number => a * b;"#;
    assert_eq!(text, expected);
}

#[test]
fn test_decorator() {
    let source = r#"function Log(target: any, key: string) {
    console.log(`Called ${key}`);
}

class Service {
    @Log
    doSomething(): void {
        console.log("doing something");
    }
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");

    let log = get_unit_by_name(&units, "Log").unwrap();
    let log_text = build_embedding_text(log);
    assert_eq!(
        log_text,
        r#"Function: Log
Signature: function Log(target: any, key: string) {
Parameters: target, key
Calls: log
File: test test.ts
Code:
function Log(target: any, key: string) {
    console.log(`Called ${key}`);
}"#
    );

    // Class is extracted as a single chunk with decorated method inside
    let service = get_unit_by_name(&units, "Service").unwrap();
    let service_text = build_embedding_text(service);
    assert_eq!(
        service_text,
        r#"Class: Service
Signature: class Service {
Calls: log
File: test test.ts
Code:
class Service {
    @Log
    doSomething(): void {
        console.log("doing something");
    }
}"#
    );

    // Verify NO separate method unit exists
    assert!(
        get_unit_by_name(&units, "doSomething").is_some(),
        "Methods are extracted as separate units alongside their parent classes"
    );
}

#[test]
fn test_constants_with_types() {
    let source = r#"const API_URL: string = "https://api.example.com";
const MAX_RETRIES: number = 3;
const CONFIG: Config = { timeout: 5000 };"#;
    let units = parse(source, Language::TypeScript, "test.ts");

    let api_url = get_unit_by_name(&units, "API_URL").unwrap();
    let api_text = build_embedding_text(api_url);
    let expected_api = r#"const API_URL: string = "https://api.example.com";"#;
    assert_eq!(api_text, expected_api);

    let max_retries = get_unit_by_name(&units, "MAX_RETRIES").unwrap();
    let max_text = build_embedding_text(max_retries);
    let expected_max = r#"const MAX_RETRIES: number = 3;"#;
    assert_eq!(max_text, expected_max);
}

#[test]
fn test_class_inheritance() {
    let source = r#"class Animal {
    speak(): string {
        return "...";
    }
}

class Dog extends Animal {
    speak(): string {
        return "Woof!";
    }
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");

    // Animal class is extracted as a single chunk
    let animal = get_unit_by_name(&units, "Animal").unwrap();
    let animal_text = build_embedding_text(animal);
    assert_eq!(
        animal_text,
        r#"Class: Animal
Signature: class Animal {
File: test test.ts
Code:
class Animal {
    speak(): string {
        return "...";
    }
}"#
    );
    // Animal has no parent
    assert!(!animal_text.contains("Extends:"));

    // Dog class is extracted as a single chunk with inheritance info
    let dog = get_unit_by_name(&units, "Dog").unwrap();
    let dog_text = build_embedding_text(dog);
    assert_eq!(
        dog_text,
        r#"Class: Dog
Signature: class Dog extends Animal {
Extends: Animal
File: test test.ts
Code:
class Dog extends Animal {
    speak(): string {
        return "Woof!";
    }
}"#
    );

    // Verify NO separate method units exist
    assert!(
        get_unit_by_name(&units, "speak").is_some(),
        "Methods are extracted as separate units alongside their parent classes"
    );
}

#[test]
fn test_function_with_imports() {
    let source = r#"import axios from 'axios';
import { format } from 'date-fns';

function fetchData(url: string): Promise<any> {
    return axios.get(url);
}"#;
    let units = parse(source, Language::TypeScript, "test.ts");
    let func = get_unit_by_name(&units, "fetchData").unwrap();
    let text = build_embedding_text(func);

    let expected = r#"Function: fetchData
Signature: function fetchData(url: string): Promise<any> {
Parameters: url
Returns: : Promise<any>
Calls: get
Uses: axios
File: test test.ts
Code:
function fetchData(url: string): Promise<any> {
    return axios.get(url);
}"#;
    assert_eq!(text, expected);
}
