"""Tests for NPath complexity (TypeScript)."""

from vibelexity.analyzers.typescript import analyze_source


def _npath(code: str) -> int:
    """Return NPath of the first function in code."""
    result = analyze_source(code)
    assert result.functions, f"No functions found in:\n{code}"
    npath_val = result.functions[0].npath
    assert npath_val is not None
    return npath_val


def _npaths(code: str) -> dict[str, int]:
    """Return {name: npath} for all functions in code."""
    result = analyze_source(code)
    return {f.name: f.npath for f in result.functions if f.npath is not None}


# --- Simple ---


def test_simple_function():
    assert _npath("function f() {}") == 1


def test_empty_source():
    result = analyze_source("")
    assert len(result.functions) == 0


# --- If statements ---


def test_if_only():
    code = """
function f(x: boolean) {
    if (x) {
        return 1;
    }
}
"""
    assert _npath(code) == 2


def test_if_else():
    code = """
function f(x: boolean) {
    if (x) {
        return 1;
    } else {
        return 2;
    }
}
"""
    assert _npath(code) == 2


def test_if_elif_else():
    code = """
function f(x: number) {
    if (x > 0) {
        return 1;
    } else if (x < 0) {
        return -1;
    } else {
        return 0;
    }
}
"""
    assert _npath(code) == 3


def test_if_elif_no_else():
    code = """
function f(x: number) {
    if (x > 0) {
        return 1;
    } else if (x < 0) {
        return -1;
    }
}
"""
    assert _npath(code) == 3  # +1 for no terminal else


# --- Bool ops ---


def test_bool_ops_in_if():
    code = """
function f(a: boolean, b: boolean) {
    if (a && b) {
        return 1;
    }
}
"""
    assert _npath(code) == 3  # 1 + 1 + 1 bool op


def test_two_bool_ops():
    code = """
function f(a: boolean, b: boolean, c: boolean) {
    if (a && b || c) {
        return 1;
    }
}
"""
    assert _npath(code) == 4  # 1 + 1 + 2 bool ops


# --- Loops ---


def test_for_loop():
    code = """
function f() {
    for (let i = 0; i < 10; i++) {
        console.log(i);
    }
}
"""
    assert _npath(code) == 2


def test_while_loop():
    code = """
function f(x: boolean) {
    while (x) {
        break;
    }
}
"""
    assert _npath(code) == 2


def test_while_with_bool_cond():
    code = """
function f(a: boolean, b: boolean) {
    while (a && b) {
        break;
    }
}
"""
    assert _npath(code) == 3


def test_do_while():
    code = """
function f(x: boolean) {
    do {
        break;
    } while (x);
}
"""
    assert _npath(code) == 2


def test_do_while_with_bool_cond():
    code = """
function f(a: boolean, b: boolean) {
    do {
        break;
    } while (a || b);
}
"""
    assert _npath(code) == 3


# --- Try/catch ---


def test_try_catch():
    code = """
function f() {
    try {
        foo();
    } catch (e) {
        bar();
    }
}
"""
    assert _npath(code) == 2


# --- Switch ---


def test_switch_three_cases():
    code = """
function f(x: number) {
    switch (x) {
        case 1: break;
        case 2: break;
        case 3: break;
    }
}
"""
    assert _npath(code) == 4  # 1+1+1 + 1 (no default)


def test_switch_with_default():
    code = """
function f(x: number) {
    switch (x) {
        case 1: break;
        case 2: break;
        default: break;
    }
}
"""
    assert _npath(code) == 3  # 1+1+1, has default


# --- Ternary ---


def test_ternary():
    code = """
function f(c: boolean, x: number, y: number) {
    return c ? x : y;
}
"""
    assert _npath(code) == 2


def test_nested_ternary():
    code = """
function f(c1: boolean, c2: boolean) {
    return c1 ? 1 : (c2 ? 2 : 3);
}
"""
    assert _npath(code) == 3


# --- Sequential ifs (multiplicative) ---


def test_sequential_ifs():
    code = """
function f(a: boolean, b: boolean) {
    if (a) { foo(); }
    if (b) { bar(); }
}
"""
    assert _npath(code) == 4


# --- Nested if ---


def test_nested_if():
    code = """
function f(a: boolean, b: boolean) {
    if (a) {
        if (b) {
            foo();
        }
    }
}
"""
    assert _npath(code) == 3


# --- Arrow functions ---


def test_named_arrow():
    code = """
const f = (x: boolean) => {
    if (x) {
        return 1;
    }
    return 0;
};
"""
    assert _npath(code) == 2


def test_nested_named_arrow():
    code = """
function outer(x: boolean) {
    if (x) { foo(); }
    const inner = (y: boolean) => {
        if (y) { bar(); }
    };
}
"""
    npaths = _npaths(code)
    assert npaths["outer"] == 2  # inner arrow counts as 1
    assert npaths["inner"] == 2


# --- Method definition ---


def test_method_definition():
    code = """
class Foo {
    bar(x: boolean) {
        if (x) {
            return 1;
        }
    }
}
"""
    assert _npath(code) == 2


# --- For-in loop ---


def test_for_in_loop():
    code = """
function f(obj: any) {
    for (const key in obj) {
        console.log(key);
    }
}
"""
    assert _npath(code) == 2
