from blends.models import (
    Graph,
    NId,
)
from blends.query import (
    adj_ast,
    match_ast_d,
)
from blends.syntax.builders.method_invocation import (
    DirectChildren,
    build_method_invocation_node,
)
from blends.syntax.builders.symbol_lookup import (
    build_symbol_lookup_node,
)
from blends.syntax.models import (
    SyntaxGraphArgs,
)
from blends.utilities.text_nodes import (
    node_to_str,
)

SELECTORS_TWO_PART = 2
SELECTORS_THREE_PART = 3


def _try_build_two_selector_invocation(
    args: SyntaxGraphArgs,
    graph: Graph,
    symbol: str,
    value_id: NId,
    selectors: list[NId],
) -> NId | None:
    uas = match_ast_d(graph, selectors[0], "unconditional_assignable_selector")
    arg_part = match_ast_d(graph, selectors[1], "argument_part")
    if uas and arg_part:
        expr_children = adj_ast(graph, uas)
        if len(expr_children) > 1:
            method_node = expr_children[1]
            expr = symbol + "." + node_to_str(graph, method_node)
            args_id = match_ast_d(graph, arg_part, "arguments")
            children: DirectChildren = {
                "object_id": value_id,
                "expression_id": method_node,
            }
            if args_id:
                children["arguments_id"] = args_id
            return build_method_invocation_node(args, expr, children)
    return None


def _try_build_three_selector_invocation(
    args: SyntaxGraphArgs,
    graph: Graph,
    symbol: str,
    value_id: NId,
    selectors: list[NId],
) -> NId | None:
    arg_part_0 = match_ast_d(graph, selectors[0], "argument_part")
    uas = match_ast_d(graph, selectors[1], "unconditional_assignable_selector")
    arg_part_2 = match_ast_d(graph, selectors[2], "argument_part")
    if arg_part_0 and uas and arg_part_2:
        args_id = match_ast_d(graph, arg_part_0, "arguments")
        if (
            args_id
            and not match_ast_d(graph, args_id, "(")
            and not match_ast_d(graph, args_id, ")")
        ):
            return build_symbol_lookup_node(args, symbol)
        expr_children = adj_ast(graph, uas)
        if len(expr_children) > 1:
            method_node = expr_children[1]
            expr = symbol + "." + node_to_str(graph, method_node)
            args_id = match_ast_d(graph, arg_part_2, "arguments")
            children: DirectChildren = {
                "object_id": value_id,
                "expression_id": method_node,
            }
            if args_id:
                children["arguments_id"] = args_id
            return build_method_invocation_node(args, expr, children)
    return None


def reader(args: SyntaxGraphArgs) -> NId:
    graph = args.ast_graph
    value_id = graph.nodes[args.n_id]["label_field_value"]
    symbol = graph.nodes[value_id]["label_text"]
    selectors = [c for c in adj_ast(graph, args.n_id) if graph.nodes[c]["label_type"] == "selector"]

    if len(selectors) == SELECTORS_TWO_PART and (
        result := _try_build_two_selector_invocation(args, graph, symbol, value_id, selectors)
    ):
        return result

    if len(selectors) == SELECTORS_THREE_PART and (
        result := _try_build_three_selector_invocation(args, graph, symbol, value_id, selectors)
    ):
        return result

    arg_part = match_ast_d(graph, selectors[0], "argument_part") if selectors else None
    if arg_part:
        args_id = match_ast_d(graph, arg_part, "arguments")
        children: DirectChildren = {}
        if args_id:
            children["arguments_id"] = args_id
        return build_method_invocation_node(args, symbol, children)

    result = build_symbol_lookup_node(args, symbol)
    for sel in selectors:
        args.syntax_graph.add_edge(result, args.generic(args.fork_n_id(sel)), label_ast="AST")
    return result
