from collections.abc import (
    Iterator,
)

from blends.ctx import ctx
from blends.models import (
    NId,
)
from blends.stack.edges import (
    Edge,
    add_edge,
)
from blends.stack.node_helpers import (
    pop_scoped_symbol_node_attributes,
    pop_symbol_node_attributes,
)
from blends.syntax.builders.utils import (
    get_next_binding_precedence,
    get_next_synthetic_node_id,
)
from blends.syntax.models import (
    SyntaxGraphArgs,
)


def _resolve_class_scope_for_parameter(
    variable: str,
    annotation_class_names: set[str] | None,
    class_member_scope_stack: list[tuple[NId, str]],
    *,
    is_classmethod: bool = False,
) -> NId | None:
    if not class_member_scope_stack:
        return None
    if variable == "self":
        return class_member_scope_stack[-1][0]
    if variable == "cls" and is_classmethod:
        return class_member_scope_stack[-1][0]
    if annotation_class_names:
        for scope_nid, class_name in reversed(class_member_scope_stack):
            if class_name in annotation_class_names:
                return scope_nid
    return None


def _setup_stack_graph_parameter(
    args: SyntaxGraphArgs,
    node_id: NId,
    variable: str,
    annotation_class_names: set[str] | None = None,
) -> None:
    if node_id not in args.syntax_graph.nodes:
        return

    precedence = get_next_binding_precedence(args)
    args.syntax_graph.update_node(
        node_id,
        pop_scoped_symbol_node_attributes(
            symbol=variable,
            precedence=precedence,
        ),
    )
    scope_stack = args.metadata.setdefault("scope_stack", [])
    if scope_stack and (parent_scope := scope_stack[-1]):
        add_edge(
            args.syntax_graph,
            Edge(source=parent_scope, sink=node_id, precedence=precedence),
        )
    class_member_scope_stack = args.metadata.get("class_member_scope_stack", [])
    is_classmethod = args.metadata.get("is_classmethod", False)
    target_scope_nid = _resolve_class_scope_for_parameter(
        variable, annotation_class_names, class_member_scope_stack, is_classmethod=is_classmethod
    )
    if target_scope_nid is not None:
        dot_gateway_id = get_next_synthetic_node_id(args)
        args.syntax_graph.add_node(
            dot_gateway_id,
            label_type="SyntheticDotGateway",
            **pop_symbol_node_attributes(symbol=".", is_definition=False),
        )
        add_edge(args.syntax_graph, Edge(source=node_id, sink=dot_gateway_id))
        add_edge(args.syntax_graph, Edge(source=dot_gateway_id, sink=target_scope_nid))


def build_parameter_node(  # noqa: PLR0913
    *,
    args: SyntaxGraphArgs,
    variable: str | None,
    variable_type: str | None,
    value_id: NId | None,
    c_ids: Iterator[NId] | list[NId] | None = None,
    variable_id: NId | None = None,
    modifier: NId | None = None,
    annotation_class_names: set[str] | None = None,
) -> NId:
    _id = variable_id if variable_id else args.n_id
    args.syntax_graph.add_node(
        _id,
        label_type="Parameter",
    )

    if variable:
        args.syntax_graph.update_node_attribute(_id, "variable", variable)

    if variable_type:
        args.syntax_graph.update_node_attribute(_id, "variable_type", variable_type)

    if value_id:
        args.syntax_graph.update_node_attribute(_id, "value_id", value_id)
        args.syntax_graph.add_edge(
            _id,
            args.generic(args.fork_n_id(value_id)),
            label_ast="AST",
        )

    if c_ids:
        for c_id in c_ids:
            args.syntax_graph.add_edge(
                _id,
                args.generic(args.fork_n_id(c_id)),
                label_ast="AST",
            )

    if modifier:
        args.syntax_graph.update_node_attribute(_id, "parameter_mode", modifier)

    if ctx.has_feature_flag("StackGraph") and variable:
        _setup_stack_graph_parameter(
            args, _id, variable, annotation_class_names=annotation_class_names
        )

    return _id
