import posixpath
import re

from blends.models import NId
from blends.stack.edges import Edge, add_edge
from blends.stack.node_helpers import push_symbol_node_attributes
from blends.syntax.builders.utils import get_next_synthetic_node_id, get_or_create_root_nid
from blends.syntax.models import SyntaxGraphArgs

_FILE_EXTENSIONS = frozenset({".dart", ".rb", ".py", ".js", ".jsx", ".ts", ".tsx", ".php"})
_SEGMENT_SEPARATOR = re.compile(r"[./]")


def _is_file_path(expression: str) -> bool:
    return "/" in expression or expression.count(".") == 1


def _strip_file_extension(name: str) -> str:
    if not _is_file_path(name):
        return name
    _, ext = posixpath.splitext(name)
    if ext.lower() in _FILE_EXTENSIONS:
        return name[: -len(ext)]
    return name


def normalize_module_path(expression: str) -> str:
    cleaned = _strip_file_extension(expression)
    parts = [p for p in _SEGMENT_SEPARATOR.split(cleaned) if p and p != "*"]
    return ".".join(parts) if parts else expression


def _emit_import_ref_node(args: SyntaxGraphArgs, symbol: str, previous_id: NId) -> NId:
    new_id = get_next_synthetic_node_id(args)
    attrs = push_symbol_node_attributes(symbol=symbol, is_reference=False)
    if args.stack_graph is not None:
        args.stack_graph.add_node(new_id, **attrs)
    edge = Edge(source=previous_id, sink=new_id)
    if args.stack_graph is not None:
        add_edge(args.stack_graph, edge)
    return new_id


def build_import_reference_chain_to_root(
    args: SyntaxGraphArgs,
    module_name: str,
    source_node_id: NId,
) -> None:
    if not module_name:
        return
    root_nid = get_or_create_root_nid(args)
    segments = [s for s in module_name.split(".") if s]
    previous_id: NId = source_node_id
    for i, segment in enumerate(reversed(segments)):
        push_id = _emit_import_ref_node(args, segment, previous_id)
        if i < len(segments) - 1:
            previous_id = _emit_import_ref_node(args, ".", push_id)
        else:
            previous_id = push_id
    edge_to_root = Edge(source=previous_id, sink=root_nid)
    if args.stack_graph is not None:
        add_edge(args.stack_graph, edge_to_root)
