"""NPath complexity for TypeScript."""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from tree_sitter import Node

from vibelexity.common import _count_bool_ops


def compute_npath_ts(func_node: Node) -> int:
    """Compute NPath complexity for a TypeScript function node."""
    for child in func_node.children:
        if child.type == "statement_block":
            return _npath_block_ts(child, func_node)
    if func_node.type == "arrow_function":
        children = [
            c
            for c in func_node.children
            if c.type not in ("=>", "formal_parameters", "identifier")
        ]
        for child in children:
            return _npath_expr_ts(child, func_node)
    return 1


def _npath_block_ts(block: Node, top_func: Node) -> int:
    """Multiply NPath of each child statement in a statement_block."""
    result = 1
    for child in block.children:
        if child.type in ("{", "}"):
            continue
        result *= _npath_stmt_ts(child, top_func)
    return result


def _npath_stmt_ts(node: Node, top_func: Node) -> int:
    """Dispatch a statement node to its NPath handler."""
    # Handle arrow functions specially
    if node.type == "arrow_function" and node is not top_func:
        return (
            compute_npath_ts(node)
            if not (node.parent and node.parent.type == "variable_declarator")
            else 1
        )

    # Function-like declarations return 1
    func_types = {
        "function_declaration",
        "function_expression",
        "method_definition",
        "generator_function_declaration",
        "class_declaration",
    }
    if node.type in func_types and node is not top_func:
        return 1

    # Dispatch handlers
    handlers = {
        "if_statement": _npath_if_ts,
        "for_statement": _npath_loop_ts,
        "for_in_statement": _npath_loop_ts,
        "while_statement": _npath_loop_ts,
        "do_statement": _npath_do_while_ts,
        "try_statement": _npath_try_ts,
        "switch_statement": _npath_switch_ts,
        "ternary_expression": _npath_ternary_ts,
        "statement_block": _npath_block_ts,
    }
    handler = handlers.get(node.type)
    if handler:
        return handler(node, top_func)

    # Expression statements
    expr_stmt_types = {
        "expression_statement",
        "return_statement",
        "variable_declaration",
        "lexical_declaration",
    }
    if node.type in expr_stmt_types:
        return _npath_expr_ts(node, top_func)

    return 1


def _npath_if_condition_ops(node: Node) -> int:
    """Count boolean operators in the if condition (before the body)."""
    ops = 0
    for child in node.children:
        if child.type in ("statement_block", "else_clause"):
            break
        if child.type not in ("if", "(", ")"):
            ops += _count_bool_ops(child)
    return ops


def _npath_if_body(node, top_func) -> int:
    """Compute NPath for the if-body (the first statement_block)."""
    for child in node.children:
        if child.type == "statement_block":
            return _npath_block_ts(child, top_func)
    return 0


def _npath_else_clause(else_node, top_func):
    """Compute NPath for an else clause (may contain else-if or plain block)."""
    for sub in else_node.children:
        if sub.type == "if_statement":
            return _npath_if_ts(sub, top_func)
        if sub.type == "statement_block":
            return _npath_block_ts(sub, top_func)
    return 0


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

    for child in node.children:
        if child.type == "else_clause":
            has_else = True
            total += _npath_else_clause(child, top_func)

    if not has_else:
        total += 1

    return total + _npath_if_condition_ops(node)


def _npath_loop_ts(node: Node, top_func: Node) -> int:
    """NPath for for/while loops."""
    body_npath = 1
    bool_ops = 0

    for child in node.children:
        if child.type == "statement_block":
            body_npath = _npath_block_ts(child, top_func)

    if node.type == "while_statement":
        for child in node.children:
            if child.type == "statement_block":
                break
            if child.type not in ("while", "(", ")"):
                bool_ops += _count_bool_ops(child)

    return body_npath + 1 + bool_ops


def _npath_do_while_ts(node: Node, top_func: Node) -> int:
    """NPath for do-while: NP(body) + 1 + bool_ops(condition)."""
    body_npath = 1
    bool_ops = 0

    for child in node.children:
        if child.type == "statement_block":
            body_npath = _npath_block_ts(child, top_func)

    seen_while = False
    for child in node.children:
        if child.type == "while":
            seen_while = True
            continue
        if seen_while and child.type not in ("(", ")", ";"):
            bool_ops += _count_bool_ops(child)

    return body_npath + 1 + bool_ops


def _npath_try_ts(node: Node, top_func: Node) -> int:
    """NPath for try_statement: sum of try/catch/finally bodies."""
    total = 0
    for child in node.children:
        if child.type == "statement_block":
            total += _npath_block_ts(child, top_func)
        elif child.type == "catch_clause":
            for sub in child.children:
                if sub.type == "statement_block":
                    total += _npath_block_ts(sub, top_func)
        elif child.type == "finally_clause":
            for sub in child.children:
                if sub.type == "statement_block":
                    total += _npath_block_ts(sub, top_func)
    return max(total, 1)


def _npath_switch_ts(node: Node, top_func: Node) -> int:
    """NPath for switch_statement: sum of case bodies (+1 if no default)."""
    total = 0
    has_default = False
    for child in node.children:
        if child.type == "switch_body":
            for case_node in child.children:
                if case_node.type == "switch_case":
                    case_npath = 1
                    for sub in case_node.children:
                        if (
                            sub.type not in ("case", ":", "{", "}")
                            and sub.type != "switch_case"
                        ):
                            sub_np = _npath_stmt_ts(sub, top_func)
                            if sub_np > 1:
                                case_npath *= sub_np
                    total += case_npath
                elif case_node.type == "switch_default":
                    has_default = True
                    case_npath = 1
                    for sub in case_node.children:
                        if sub.type not in ("default", ":", "{", "}"):
                            sub_np = _npath_stmt_ts(sub, top_func)
                            if sub_np > 1:
                                case_npath *= sub_np
                    total += case_npath
    if not has_default:
        total += 1
    return max(total, 1)


def _npath_ternary_ts(node: Node, top_func: Node) -> int:
    """NPath for ternary_expression: NP(true) + NP(false) + bool_ops(cond)."""
    non_punct = [c for c in node.children if c.type not in ("?", ":")]
    if len(non_punct) >= 3:
        condition = non_punct[0]
        true_expr = non_punct[1]
        false_expr = non_punct[2]
        np_true = _npath_expr_ts(true_expr, top_func)
        np_false = _npath_expr_ts(false_expr, top_func)
        bool_ops = _count_bool_ops(condition)
        return np_true + np_false + bool_ops
    return 2


def _npath_expr_ts(node: Node, top_func: Node) -> int:
    """Scan expression for ternaries/inline arrows, multiplying results."""
    if node.type == "ternary_expression":
        return _npath_ternary_ts(node, top_func)
    if node.type == "arrow_function" and node is not top_func:
        if node.parent and node.parent.type == "variable_declarator":
            return 1
        return compute_npath_ts(node)
    if node.type in (
        "function_expression",
        "function_declaration",
        "class_declaration",
        "class_expression",
    ):
        return 1

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