"""Tests for TypeScript cognitive complexity."""

from vibelexity.analyzers.typescript import analyze_source


def _score(code: str, tsx: bool = False) -> int:
    """Helper: return total complexity of the first function in code."""
    result = analyze_source(code, tsx=tsx)
    assert result.functions, f"No functions found in:\n{code}"
    return result.functions[0].complexity


def _scores(code: str, tsx: bool = False) -> dict[str, int]:
    """Helper: return {name: complexity} for all functions in code."""
    result = analyze_source(code, tsx=tsx)
    return {f.name: f.complexity for f in result.functions}


# --- Basic control flow ---


def test_empty_function():
    assert _score("function f() {}") == 0


def test_single_if():
    code = """
function f(x) {
    if (x) {       // +1
    }
}
"""
    assert _score(code) == 1


def test_if_else():
    code = """
function f(x) {
    if (x) {       // +1
    } else {       // +1
    }
}
"""
    assert _score(code) == 2


def test_if_else_if_else():
    code = """
function f(x) {
    if (x > 0) {       // +1
    } else if (x < 0) { // +1
    } else {            // +1
    }
}
"""
    assert _score(code) == 3


# --- Loops ---


def test_for_loop():
    code = """
function f(items) {
    for (let i = 0; i < items.length; i++) {   // +1
    }
}
"""
    assert _score(code) == 1


def test_for_in_loop():
    code = """
function f(obj) {
    for (const key in obj) {   // +1
    }
}
"""
    assert _score(code) == 1


def test_while_loop():
    code = """
function f(x) {
    while (x > 0) {   // +1
        x--;
    }
}
"""
    assert _score(code) == 1


def test_do_while_loop():
    code = """
function f(x) {
    do {               // +1
        x--;
    } while (x > 0);
}
"""
    assert _score(code) == 1


# --- Nesting ---


def test_nested_if():
    code = """
function f(a, b) {
    if (a) {           // +1 (nesting 0)
        if (b) {       // +1 + 1 (nesting 1)
        }
    }
}
"""
    assert _score(code) == 3


def test_deeply_nested():
    code = """
function f(a, b, c) {
    if (a) {               // +1 (nesting 0)
        for (let x of b) { // +1 + 1 (nesting 1)
            if (c) {        // +1 + 2 (nesting 2)
            }
        }
    }
}
"""
    assert _score(code) == 6


def test_if_else_nesting():
    code = """
function f(a, b) {
    if (a) {           // +1 (nesting 0)
    } else {           // +1 (no nesting bonus)
        if (b) {       // +1 + 1 (nesting 1, inside else body)
        }
    }
}
"""
    assert _score(code) == 4


# --- Try/Catch ---


def test_try_catch():
    code = """
function f() {
    try {
    } catch (e) {   // +1
    }
}
"""
    assert _score(code) == 1


def test_try_catch_nested():
    code = """
function f(x) {
    if (x) {                   // +1 (nesting 0)
        try {
        } catch (e) {          // +1 + 1 (nesting 1)
        }
    }
}
"""
    assert _score(code) == 3


# --- Boolean operators ---


def test_single_and():
    code = """
function f(a, b) {
    if (a && b) {   // +1 (if) + 1 (boolean)
    }
}
"""
    assert _score(code) == 2


def test_chained_same_op():
    code = """
function f(a, b, c) {
    if (a && b && c) {   // +1 (if) + 1 (boolean chain, same op)
    }
}
"""
    assert _score(code) == 2


def test_mixed_boolean():
    code = """
function f(a, b, c) {
    if (a && b || c) {   // +1 (if) + 2 (&&->|| switch)
    }
}
"""
    assert _score(code) == 3


def test_complex_boolean():
    code = """
function f(a, b, c, d) {
    if (a || b || (c && d)) {   // +1 (if) + 2 (|| sequence, then && subtree)
    }
}
"""
    assert _score(code) == 3


# --- Ternary ---


def test_ternary():
    code = """
function f(x) {
    return x > 0 ? x : -x;   // +1 (ternary, nesting 0)
}
"""
    assert _score(code) == 1


def test_ternary_nested_in_if():
    code = """
function f(x) {
    if (x) {                       // +1 (nesting 0)
        return x > 0 ? x : -x;   // +1 + 1 (ternary, nesting 1)
    }
}
"""
    assert _score(code) == 3


# --- Recursion ---


def test_recursion():
    code = """
function factorial(n) {
    if (n <= 1) {                  // +1
        return 1;
    }
    return n * factorial(n - 1);   // +1 (recursion)
}
"""
    assert _score(code) == 2


def test_no_false_recursion():
    code = """
function f(x) {
    return g(x);
}
"""
    assert _score(code) == 0


# --- Nested functions ---


def test_nested_function_separate_scoring():
    code = """
function outer(x) {
    if (x) {}           // +1 for outer
    function inner(y) {
        if (y) {}       // +1 for inner
    }
}
"""
    scores = _scores(code)
    assert scores["outer"] == 1
    assert scores["inner"] == 1


def test_nested_function_nesting_reset():
    code = """
function outer(x) {
    if (x) {                   // +1 for outer
        function inner(y) {
            if (y) {}           // +1 for inner (nesting 0, not 1)
        }
    }
}
"""
    scores = _scores(code)
    assert scores["outer"] == 1
    assert scores["inner"] == 1


# --- Arrow functions ---


def test_inline_arrow_increases_nesting():
    code = """
function f(items) {
    return items.map(x => x > 0 ? x : 0);
}
"""
    assert _score(code) == 2


def test_named_arrow_scored_separately():
    code = """
const f = (x) => {
    if (x) {}   // +1
};
const g = (a, b) => {
    if (a) {           // +1
        if (b) {}      // +1 + 1
    }
};
"""
    scores = _scores(code)
    assert scores["f"] == 1
    assert scores["g"] == 3


# --- Switch ---


def test_switch_statement():
    code = """
function f(x) {
    switch (x) {       // +1 (nesting 0)
        case 1:
            break;
        case 2:
            break;
        default:
            break;
    }
}
"""
    assert _score(code) == 1


# --- Method definition ---


def test_method_definition():
    code = """
class Foo {
    bar(x) {
        if (x) {}   // +1
    }
}
"""
    scores = _scores(code)
    assert scores["bar"] == 1


# --- Comprehensive example ---


def test_comprehensive():
    code = """
function process(items, flag) {
    if (flag) {                        // +1 (nesting 0)
        for (let i = 0; i < items.length; i++) {  // +1 +1 (nesting 1)
            if (items[i] > 0) {        // +1 +2 (nesting 2)
            } else if (items[i] < 0) { // +1 (no nesting bonus)
            } else {                   // +1 (no nesting bonus)
                try {
                } catch (e) {          // +1 +3 (nesting 3)
                }
            }
        }
    } else {                           // +1 (no nesting bonus)
    }
}
"""
    assert _score(code) == 13


# --- Edge cases ---


def test_not_operator_no_increment():
    code = """
function f(x) {
    if (!x) {   // +1 (if only)
    }
}
"""
    assert _score(code) == 1


def test_async_function():
    code = """
async function f(x) {
    if (x) {}   // +1
}
"""
    assert _score(code) == 1


def test_generator_function():
    code = """
function* gen(items) {
    for (const item of items) {   // +1
        if (item) {               // +1 + 1
            yield item;
        }
    }
}
"""
    assert _score(code) == 3
