import logging
from pathlib import Path

from blends.ast.build_graph import get_ast_graph_from_content
from blends.content.content import get_content_by_path
from blends.ctx import ctx
from blends.models import Content, Graph, NId, SyntaxGraph
from blends.stack.validation import validate_stack_graph_graph
from blends.syntax.cfg.generate import add_syntax_cfg
from blends.syntax.dispatchers import DISPATCHERS_BY_LANG
from blends.syntax.models import ReaderLogicError, SyntaxGraphArgs, SyntaxMetadata
from blends.syntax.readers.common.missing_node import reader as missing_node_reader

LOGGER = logging.getLogger(__name__)


def generic(args: SyntaxGraphArgs) -> NId:
    node_type = args.ast_graph.nodes[args.n_id]["label_type"]
    lang_dispatchers = DISPATCHERS_BY_LANG[args.language]
    if dispatcher := lang_dispatchers.get(node_type):
        try:
            return dispatcher(args)
        except ReaderLogicError as e:
            LOGGER.debug("Reader logic error for %s in %s: %s", node_type, args.language.name, e)
            return missing_node_reader(args, node_type)

    LOGGER.debug("Missing syntax reader for %s in %s", node_type, args.language.name)

    return missing_node_reader(args, node_type)


def get_syntax_graph(
    ast_graph: Graph, content: Content, *, with_cfg: bool = True, with_metadata: bool = False
) -> SyntaxGraph | None:
    syntax_graph = SyntaxGraph()

    *_, last_n_id = ast_graph.nodes
    max_ast_id = int(last_n_id)

    syntax_metadata = SyntaxMetadata(
        class_path=[],
        scope_stack=[NId(1)] if ctx.has_feature_flag("StackGraph") else [],
        next_synthetic_id=max_ast_id + 1,
    )
    if with_metadata:
        syntax_graph.add_node(
            NId(0),
            label_type="Metadata",
            structure={},
            instances={},
            imports=[],
            path=str(content.path),
        )

    generic(
        SyntaxGraphArgs(
            generic,
            str(content.path),
            content.language,
            ast_graph,
            syntax_graph,
            NId(1),
            syntax_metadata,
        ),
    )

    if ctx.has_feature_flag("StackGraph"):
        errors = validate_stack_graph_graph(syntax_graph)
        if errors:
            max_errors = 20
            head = errors[:max_errors]
            extra = len(errors) - len(head)
            details = "; ".join(head)
            if extra > 0:
                details = f"{details}; and {extra} more"
            LOGGER.warning("Stack graph validation errors in %s: %s", content.path, details)

    if with_cfg:
        syntax_graph = add_syntax_cfg(syntax_graph, is_multifile=with_metadata)

    return syntax_graph


def get_syntax_graph_from_path(
    path: Path, *, with_cfg: bool = True, with_metadata: bool = False
) -> SyntaxGraph | None:
    content = get_content_by_path(path)
    if content is None:
        return None

    ast_graph = get_ast_graph_from_content(content)
    if ast_graph is None:
        return None

    return get_syntax_graph(ast_graph, content, with_cfg=with_cfg, with_metadata=with_metadata)
