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


def _add_class_attr_unscoped_definition(
    args: SyntaxGraphArgs,
    variable_name: str,
    class_scope_nid: NId,
    precedence: int,
) -> None:
    unscoped_def_id = get_next_synthetic_node_id(args)
    args.syntax_graph.add_node(
        unscoped_def_id,
        label_type="SyntheticClassAttrDef",
        **pop_symbol_node_attributes(
            symbol=variable_name, precedence=precedence, is_definition=True
        ),
    )
    ast_node = args.ast_graph.nodes.get(args.n_id, {})
    if (label_l := ast_node.get("label_l")) is not None:
        args.syntax_graph.update_node_attribute(unscoped_def_id, "label_l", label_l)
        args.syntax_graph.update_node_attribute(
            unscoped_def_id, "label_c", ast_node.get("label_c", "0")
        )
    add_edge(
        args.syntax_graph,
        Edge(source=class_scope_nid, sink=unscoped_def_id, precedence=precedence),
    )


def _wire_variable_stack_graph_node(
    args: SyntaxGraphArgs,
    variable_name: str,
    import_module_stem: str | None,
    import_source_symbol: str | None,
) -> None:
    precedence = get_next_binding_precedence(args)
    if import_module_stem is not None and import_source_symbol is not None:
        args.syntax_graph.update_node(
            args.n_id,
            import_binding_node_attributes(
                symbol=variable_name,
                precedence=precedence,
                import_module_stem=import_module_stem,
                import_source_symbol=import_source_symbol,
            ),
        )
    else:
        args.syntax_graph.update_node(
            args.n_id,
            pop_scoped_symbol_node_attributes(
                symbol=variable_name,
                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=args.n_id, precedence=precedence),
        )
    if import_module_stem is not None and import_source_symbol is not None:
        build_import_reference_chain_to_root(args, import_module_stem, args.n_id)
    class_member_scope_stack = args.metadata.get("class_member_scope_stack", [])
    if (
        class_member_scope_stack
        and scope_stack
        and class_member_scope_stack[-1][0] == scope_stack[-1]
    ):
        _add_class_attr_unscoped_definition(
            args, variable_name, class_member_scope_stack[-1][0], precedence
        )


def build_variable_declaration_node(  # noqa: PLR0913
    args: SyntaxGraphArgs,
    variable_name: str,
    variable_type: str | None,
    value_id: NId | None,
    var_id: NId | None = None,
    access_modifier: str | None = None,
    import_module_stem: str | None = None,
    import_source_symbol: str | None = None,
) -> NId:
    args.syntax_graph.add_node(
        args.n_id,
        label_type="VariableDeclaration",
        variable=variable_name,
    )

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

    if value_id:
        args.syntax_graph.update_node_attribute(args.n_id, "value_id", value_id)

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

        args.syntax_graph.add_edge(
            args.n_id,
            args.generic(args.fork_n_id(var_id)),
            label_ast="AST",
        )

    if access_modifier:
        args.syntax_graph.update_node_attribute(args.n_id, "access_modifier", access_modifier)

    if ctx.has_feature_flag("StackGraph"):
        _wire_variable_stack_graph_node(
            args, variable_name, import_module_stem, import_source_symbol
        )

    return args.n_id
