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


def _reader_invocation_expression(
    graph: Graph,
    args: SyntaxGraphArgs,
    arg_part: NId | None,
    id_name: NId | None,
    pred_id: NId | None,
) -> NId | None:
    arg_list = None
    if arg_part:
        arg_list = match_ast_d(graph, arg_part, "arguments")
    if id_name:
        child_id = match_ast_d(graph, id_name, "identifier")
        if pred_id and child_id:
            selector_name = ".".join(
                graph.nodes[_id]["label_text"]
                for _id in [pred_id, child_id]
                if "label_text" in graph.nodes[_id]
            )
            children: DirectChildren = {}
            if child_id:
                children["expression_id"] = child_id
            if arg_list:
                children["arguments_id"] = arg_list
            return build_method_invocation_node(args, selector_name, children)
    return None


def _get_next_arg_part_sibling(
    graph: Graph,
    n_id: NId,
    parent_id: NId,
) -> NId | None:
    siblings = adj_ast(graph, parent_id)
    current_idx = siblings.index(n_id)
    if current_idx + 1 >= len(siblings):
        return None
    next_sibling = siblings[current_idx + 1]
    if graph.nodes[next_sibling].get("label_type") != "selector":
        return None
    if not match_ast_d(graph, next_sibling, "argument_part"):
        return None
    return next_sibling


def _resolve_child_and_selector(
    graph: Graph,
    id_name: NId | None,
) -> tuple[NId | None, str | None]:
    if not id_name:
        return None, None
    child_id = match_ast_d(graph, id_name, "identifier")
    if child_id:
        return child_id, node_to_str(graph, child_id)
    return None, None


def _try_build_from_next_sibling(
    graph: Graph,
    args: SyntaxGraphArgs,
    selector_name: str,
    child_id: NId | None,
    parent_id: NId,
) -> NId | None:
    next_sibling = _get_next_arg_part_sibling(graph, args.n_id, parent_id)
    if not next_sibling:
        return None
    next_arg_id = match_ast_d(graph, next_sibling, "argument_part")
    if not next_arg_id:
        return None
    next_al_id = match_ast_d(graph, next_arg_id, "arguments")
    if not next_al_id:
        return None
    children: DirectChildren = {"arguments_id": next_al_id}
    if child_id:
        children["expression_id"] = child_id
    method_inv_id = build_method_invocation_node(args, selector_name, children)
    graph.nodes[next_sibling]["_method_invocation_id"] = method_inv_id
    return method_inv_id


def _build_final_node(
    args: SyntaxGraphArgs,
    selector_name: str | None,
    child_id: NId | None,
) -> NId:
    graph = args.ast_graph
    arg_id = match_ast_d(graph, args.n_id, "argument_part")
    if arg_id and (al_id := match_ast_d(graph, arg_id, "arguments")):
        if selector_name:
            children: DirectChildren = {"arguments_id": al_id}
            if child_id:
                children["expression_id"] = child_id
            return build_method_invocation_node(args, selector_name, children)
        return args.generic(args.fork_n_id(al_id))
    return build_selector_node(args, selector_name, None)


def reader(args: SyntaxGraphArgs) -> NId:
    graph = args.ast_graph

    if already_built := graph.nodes[args.n_id].get("_method_invocation_id"):
        return NId(already_built)

    id_name = match_ast_d(graph, args.n_id, "unconditional_assignable_selector") or match_ast_d(
        graph,
        args.n_id,
        "conditional_assignable_selector",
    )
    parent = pred_ast(graph, args.n_id)
    parent_type = graph.nodes[parent[0]]["label_type"]
    if parent_type in {"return_statement", "if_statement"} and id_name:
        pred_id = match_ast_d(graph, parent[0], "identifier")
        arg_part = match_ast_d(graph, parent[0], "argument_part", 2)
        if built_node := _reader_invocation_expression(graph, args, arg_part, id_name, pred_id):
            return built_node

    child_id, selector_name = _resolve_child_and_selector(graph, id_name)

    if selector_name and (
        method_inv_id := _try_build_from_next_sibling(
            graph, args, selector_name, child_id, parent[0]
        )
    ):
        return method_inv_id

    return _build_final_node(args, selector_name, child_id)
