from __future__ import annotations

from dataclasses import dataclass
from typing import Any


def _norm_path(p: str) -> str:
    p = (p or "").replace("\\", "/")
    while p.startswith("./"):
        p = p[2:]
    return p.strip("/")


def _dir_of(path: str) -> str:
    path = _norm_path(path)
    if "/" not in path:
        return ""
    return path.rsplit("/", 1)[0]


def _parent_dir(d: str) -> str:
    d = _norm_path(d)
    if not d or "/" not in d:
        return ""
    return d.rsplit("/", 1)[0]


def _dotted_from_dir(d: str) -> str:
    d = _norm_path(d)
    return d.replace("/", ".") if d else ""


@dataclass(frozen=True)
class ResolvedImport:
    from_path: str
    to_path: str
    dotted: str
    via: str


class InternalImportResolver:
    """
    Best-effort resolver:
    - Only counts "internal" imports when we can map to an existing repo file
    - Resolves both absolute (level=0) and relative (level>0) import refs
    """

    def __init__(self, *, all_files: set[str], package_dirs: set[str]):
        self._all_files = {_norm_path(p) for p in (all_files or set())}
        self._package_dirs = {_norm_path(p) for p in (package_dirs or set())}

    def _base_pkg_dir_for_file(self, from_path: str) -> str:
        """
        Returns the "current package dir" (repo-relative path) if the file is inside a package.
        For __init__.py: the directory itself is the package.
        For other .py: its containing directory must be a package dir.
        """
        p = _norm_path(from_path)
        d = _dir_of(p)
        if p.endswith("/__init__.py"):
            return d
        if d in self._package_dirs:
            return d
        return ""

    def resolve_internal(self, *, from_path: str, import_ref: dict[str, Any]) -> ResolvedImport | None:
        from_path = _norm_path(from_path)
        level = int(import_ref.get("level") or 0)
        module = str(import_ref.get("module") or "").strip()
        name = str(import_ref.get("name") or "").strip()

        dotted_base = ""
        if level > 0:
            base_dir = self._base_pkg_dir_for_file(from_path)
            if not base_dir:
                return None
            # level semantics: 1 means "current package", 2 means "parent package", etc.
            up = max(0, level - 1)
            cur = base_dir
            for _ in range(up):
                parent = _parent_dir(cur)
                if not parent:
                    cur = ""
                    break
                # stop if parent is not a package dir (avoid escaping out of packages)
                if parent not in self._package_dirs:
                    cur = ""
                    break
                cur = parent
            if not cur:
                return None
            dotted_base = _dotted_from_dir(cur)

        def join_dotted(a: str, b: str) -> str:
            a = (a or "").strip(".")
            b = (b or "").strip(".")
            if a and b:
                return f"{a}.{b}"
            return a or b

        dotted = join_dotted(dotted_base, module) if level > 0 else module

        candidates: list[tuple[str, str]] = []
        if dotted:
            candidates.append((dotted, "module"))
        # from X import Y can refer to a submodule X.Y
        if name and name != "*":
            if dotted:
                candidates.append((f"{dotted}.{name}", "module+name"))
            elif level > 0 and dotted_base:
                candidates.append((join_dotted(dotted_base, name), "base+name"))

        for cand, via in candidates:
            rel = cand.replace(".", "/")
            p1 = _norm_path(rel + ".py")
            if p1 in self._all_files:
                return ResolvedImport(from_path=from_path, to_path=p1, dotted=cand, via=via)
            p2 = _norm_path(rel + "/__init__.py")
            if p2 in self._all_files:
                return ResolvedImport(from_path=from_path, to_path=p2, dotted=cand, via=via)

        return None

