from blends.models import (
    NId,
    SyntaxGraph,
)
from blends.query import (
    adj_ast,
    get_n_arg,
    matching_nodes,
    pred_ast,
    pred_ast_lazy,
)
from blends.symbolic_evaluation.dispatcher import (
    generic,
)
from blends.symbolic_evaluation.models import (
    SymbolicEvalArgs,
    SymbolicEvaluation,
)
from blends.symbolic_evaluation.utils import (
    get_backward_paths,
)


def _get_parent_method_name(graph: SyntaxGraph, n_id: NId) -> str | None:
    for parent_id in pred_ast_lazy(graph, n_id, -1):
        if graph.nodes[parent_id].get("label_type") == "MethodDeclaration":
            return graph.nodes[parent_id].get("name")
    return None


def _get_param_index(graph: SyntaxGraph, n_id: NId) -> int | None:
    param_list_ids = pred_ast(graph, n_id, 1)
    if not param_list_ids:
        return None
    param_ids = adj_ast(graph, param_list_ids[0], label_type="Parameter")
    if n_id not in param_ids:
        return None
    return param_ids.index(n_id)


def _propagate_to_call_sites(args: SymbolicEvalArgs) -> bool:
    method_name = _get_parent_method_name(args.graph, args.n_id)
    if not method_name:
        return False
    param_index = _get_param_index(args.graph, args.n_id)
    if param_index is None:
        return False

    for inv_id in matching_nodes(args.graph, label_type="MethodInvocation", expression=method_name):
        if (arg_id := get_n_arg(args.graph, inv_id, param_index)) and any(
            generic(args.fork_n_id(arg_id, path)).danger
            for path in get_backward_paths(args.graph, arg_id)
        ):
            return True

    return False


def evaluate(args: SymbolicEvalArgs) -> SymbolicEvaluation:
    args.evaluation[args.n_id] = False
    if val_id := args.graph.nodes[args.n_id].get("value_id"):
        args.evaluation[args.n_id] = generic(args.fork_n_id(val_id)).danger
    if args.method_evaluators and (method_evaluator := args.method_evaluators.get("parameter")):
        args.evaluation[args.n_id] = method_evaluator(args).danger

    if "parameter_propagation" in args.triggers:
        args.triggers.remove("parameter_propagation")
        args.evaluation[args.n_id] |= _propagate_to_call_sites(args)

    return SymbolicEvaluation(args.evaluation[args.n_id], args.triggers)
