from collections.abc import (
    Iterable,
)

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,
    scope_node_attributes,
)
from blends.stack.policies.registry import (
    get_export_policy,
)
from blends.stack.scope_types import ScopeType
from blends.syntax.builders.utils import (
    get_next_binding_precedence,
    get_next_synthetic_node_id,
)
from blends.syntax.models import (
    SyntaxGraphArgs,
)


def _enter_scope_stack(args: SyntaxGraphArgs, class_name: str) -> None:
    if not ctx.has_feature_flag("StackGraph"):
        return
    policy = get_export_policy(args.language)
    args.syntax_graph.update_node(
        args.n_id,
        scope_node_attributes(
            is_exported=policy.is_scope_exported(
                args.syntax_graph, args.n_id, ScopeType.CLASS.value
            ),
            scope_type=ScopeType.CLASS,
        ),
    )
    scope_stack = args.metadata.setdefault("scope_stack", [])
    if scope_stack:
        add_edge(
            args.syntax_graph,
            Edge(source=args.n_id, sink=scope_stack[-1], precedence=0),
        )
    scope_stack.append(args.n_id)
    class_member_scope_stack = args.metadata.setdefault("class_member_scope_stack", [])
    class_member_scope_stack.append((args.n_id, class_name))


def _exit_scope_stack_if_current(args: SyntaxGraphArgs) -> None:
    if not ctx.has_feature_flag("StackGraph"):
        return
    scope_stack = args.metadata.setdefault("scope_stack", [])
    if scope_stack and scope_stack[-1] == args.n_id:
        scope_stack.pop()
    class_member_scope_stack = args.metadata.setdefault("class_member_scope_stack", [])
    if class_member_scope_stack and class_member_scope_stack[-1][0] == args.n_id:
        class_member_scope_stack.pop()


def _setup_stack_graph_definition(args: SyntaxGraphArgs, name: str) -> None:
    scope_stack = args.metadata.setdefault("scope_stack", [])
    if not scope_stack:
        return
    parent_scope = scope_stack[-1]
    precedence = get_next_binding_precedence(args)
    class_def_id = get_next_synthetic_node_id(args)
    args.syntax_graph.add_node(
        class_def_id,
        label_type="Class",
        name=name,
        sg_node_type="definition",
    )
    args.syntax_graph.update_node(
        class_def_id,
        pop_scoped_symbol_node_attributes(
            symbol=name,
            precedence=precedence,
        ),
    )
    add_edge(
        args.syntax_graph,
        Edge(source=parent_scope, sink=class_def_id, precedence=precedence),
    )
    add_edge(
        args.syntax_graph,
        Edge(source=class_def_id, sink=args.n_id, precedence=0),
    )
    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(class_def_id, "label_l", label_l)
        args.syntax_graph.update_node_attribute(
            class_def_id, "label_c", ast_node.get("label_c", "0")
        )


def build_class_node(  # noqa: PLR0913
    args: SyntaxGraphArgs,
    name: str,
    block_id: NId | None,
    attrl_ids: Iterable[NId] | None,
    inherited_class: str | None = None,
    modifiers_id: NId | None = None,
    access_modifiers: str | None = None,
) -> NId:
    args.syntax_graph.add_node(
        args.n_id,
        name=name,
        block_id=block_id,
        label_type="Class",
        **({"access_modifiers": access_modifiers} if access_modifiers else {}),
    )

    if ctx.has_feature_flag("StackGraph"):
        _setup_stack_graph_definition(args, name)

    _enter_scope_stack(args, name)

    if block_id:
        args.syntax_graph.add_edge(
            args.n_id,
            args.generic(args.fork_n_id(block_id)),
            label_ast="AST",
        )

    if attrl_ids:
        for n_id in attrl_ids:
            args.syntax_graph.add_edge(
                args.n_id,
                args.generic(args.fork_n_id(n_id)),
                label_ast="AST",
            )

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

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

    if args.metadata["class_path"]:
        args.metadata["class_path"].pop()

    _exit_scope_stack_if_current(args)

    return args.n_id
