"""NPath complexity for Python."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from tree_sitter import Node


def _count_bool_ops_py(node: Node) -> int:
    """Count boolean_operator nodes (and/or) in an expression subtree."""
    count = 0
    if node.type == "boolean_operator":
        count += 1
    for child in node.children:
        count += _count_bool_ops_py(child)
    return count


def compute_npath_py(func_node: Node) -> int:
    """Compute NPath complexity for a Python function_definition node."""
    for child in func_node.children:
        if child.type == "block":
            return _npath_block_py(child, func_node)
    return 1


def _npath_block_py(block: Node, top_func: Node) -> int:
    """Multiply NPath of each child statement in a block."""
    result = 1
    for child in block.children:
        result *= _npath_stmt_py(child, top_func)
    return result


def _npath_stmt_py(node: Node, top_func: Node) -> int:
    """Dispatch a statement node to its NPath handler."""
    if node.type == "function_definition" and node is not top_func:
        return 1
    if node.type == "class_definition":
        return 1
    if node.type == "decorated_definition":
        return 1

    if node.type == "if_statement":
        return _npath_if_py(node, top_func)
    if node.type in ("for_statement", "while_statement"):
        return _npath_loop_py(node, top_func)
    if node.type == "try_statement":
        return _npath_try_py(node, top_func)
    if node.type == "match_statement":
        return _npath_match_py(node, top_func)
    if node.type == "block":
        return _npath_block_py(node, top_func)
    if node.type == "with_statement":
        for child in node.children:
            if child.type == "block":
                return _npath_block_py(child, top_func)
        return 1
    if node.type in (
        "expression_statement",
        "return_statement",
        "assignment",
        "augmented_assignment",
    ):
        return _npath_expr_py(node, top_func)
    return 1


def _npath_if_py(node: Node, top_func: Node) -> int:
    """NPath for if_statement with elif/else chains."""
    total = 0
    has_else = False

    condition_bool_ops = 0
    for child in node.children:
        if child.type in ("block", "elif_clause", "else_clause"):
            break
        condition_bool_ops += _count_bool_ops_py(child)

    for child in node.children:
        if child.type == "block":
            total += _npath_block_py(child, top_func)
            break

    for child in node.children:
        if child.type == "elif_clause":
            total += _npath_elif_py(child, top_func)
        elif child.type == "else_clause":
            has_else = True
            for sub in child.children:
                if sub.type == "block":
                    total += _npath_block_py(sub, top_func)

    if not has_else:
        total += 1

    total += condition_bool_ops
    return total


def _npath_elif_py(node: Node, top_func: Node) -> int:
    """NPath contribution of a single elif clause."""
    body_npath = 1
    bool_ops = 0
    for child in node.children:
        if child.type == "block":
            body_npath = _npath_block_py(child, top_func)
        elif child.type != "block":
            bool_ops += _count_bool_ops_py(child)
    return body_npath + bool_ops


def _npath_loop_py(node: Node, top_func: Node) -> int:
    """NPath for for_statement / while_statement (with optional else)."""
    body_npath = 1
    else_npath = 0
    bool_ops = 0
    has_else = False

    for child in node.children:
        if child.type == "block" and not has_else:
            body_npath = _npath_block_py(child, top_func)
        elif child.type == "else_clause":
            has_else = True
            for sub in child.children:
                if sub.type == "block":
                    else_npath = _npath_block_py(sub, top_func)

    if node.type == "while_statement":
        for child in node.children:
            if child.type == "block":
                break
            bool_ops += _count_bool_ops_py(child)

    if has_else:
        return body_npath + else_npath + bool_ops
    return body_npath + 1 + bool_ops


def _npath_try_py(node: Node, top_func: Node) -> int:
    """NPath for try_statement: sum of try body + except/finally bodies."""
    total = 0
    for child in node.children:
        if child.type == "block":
            total += _npath_block_py(child, top_func)
        elif child.type == "except_clause":
            for sub in child.children:
                if sub.type == "block":
                    total += _npath_block_py(sub, top_func)
        elif child.type == "finally_clause":
            for sub in child.children:
                if sub.type == "block":
                    total += _npath_block_py(sub, top_func)
    return max(total, 1)


def _npath_match_py(node: Node, top_func: Node) -> int:
    """NPath for match_statement: sum of case bodies (+1 if no wildcard)."""
    total = 0
    has_wildcard = False
    for child in node.children:
        if child.type == "block":
            for case_node in child.children:
                if case_node.type == "case_clause":
                    for sub in case_node.children:
                        if sub.type == "case_pattern":
                            pattern_text = sub.text.decode().strip()
                            if pattern_text == "_":
                                has_wildcard = True
                    for sub in case_node.children:
                        if sub.type == "block":
                            total += _npath_block_py(sub, top_func)
    if not has_wildcard:
        total += 1
    return max(total, 1)


def _npath_ternary_py(node, top_func) -> int:
    """NPath for conditional_expression: NP(true) + NP(false) + bool_ops(cond)."""
    children = [c for c in node.children if c.type not in ("if", "else")]
    if len(children) >= 3:
        true_expr = children[0]
        condition = children[1]
        false_expr = children[2]
        np_true = _npath_expr_py(true_expr, top_func)
        np_false = _npath_expr_py(false_expr, top_func)
        bool_ops = _count_bool_ops_py(condition)
        return np_true + np_false + bool_ops
    return 2


def _npath_expr_py(node: Node, top_func: Node) -> int:
    """Scan an expression for embedded ternaries/lambdas, multiplying results."""
    if node.type == "conditional_expression":
        return _npath_ternary_py(node, top_func)
    if node.type == "lambda":
        for child in node.children:
            if child.type not in ("lambda", "lambda_parameters", ":"):
                return _npath_expr_py(child, top_func)
        return 1
    if node.type == "function_definition":
        return 1
    if node.type == "class_definition":
        return 1

    result = 1
    for child in node.children:
        child_np = _npath_expr_py(child, top_func)
        if child_np > 1:
            result *= child_np
    return result
