"""
This is a module for various lookup functions:
functions that will find a semantic node by its name.
"""

from __future__ import annotations

from mypy.nodes import MypyFile, SymbolTableNode, TypeInfo

# TODO: gradually move existing lookup functions to this module.


def lookup_fully_qualified(
    name: str, modules: dict[str, MypyFile], *, raise_on_missing: bool = False
) -> SymbolTableNode | None:
    """Find a symbol using it fully qualified name.

    The algorithm has two steps: first we try splitting the name on '.' to find
    the module, then iteratively look for each next chunk after a '.' (e.g. for
    nested classes).

    This function should *not* be used to find a module. Those should be looked
    in the modules dictionary.
    """
    # 1. Exclude the names of ad hoc instance intersections from step 2.
    i = name.find("<subclass ")
    head = name if i == -1 else name[:i]
    rest = []
    # 2. Find a module tree in modules dictionary.
    while True:
        if "." not in head:
            if raise_on_missing:
                assert "." in head, f"Cannot find module for {name}"
            return None
        # TODO: this logic is not correct as it confuses a submodule and a local symbol.
        # A potential solution may be to use format like pkg.mod:Cls.method for fullname,
        # but this is a relatively big change.
        head, tail = head.rsplit(".", maxsplit=1)
        rest.append(tail)
        mod = modules.get(head)
        if mod is not None:
            break
    names = mod.names
    # 3. Find the symbol in the module tree.
    if not rest:
        # Looks like a module, don't use this to avoid confusions.
        if raise_on_missing:
            assert rest, f"Cannot find {name}, got a module symbol"
        return None
    if i != -1:
        rest[0] += name[i:]
    while True:
        key = rest.pop()
        if key not in names:
            if raise_on_missing:
                assert key in names, f"Cannot find component {key!r} for {name!r}"
            return None
        stnode = names[key]
        if not rest:
            return stnode
        node = stnode.node
        # In fine-grained mode, could be a cross-reference to a deleted module
        # or a Var made up for a missing module.
        if not isinstance(node, TypeInfo):
            if raise_on_missing:
                assert node, f"Cannot find {name}"
            return None
        names = node.names
