# helpers/import_closure.py — list dsv4 modules NOT reachable from the entry points.
# Usage: python3 helpers/import_closure.py  (run from repo root)
# NOTE: handles lazy imports inside functions (single_shot uses these heavily)
import ast, pathlib, sys
ROOT = pathlib.Path(__file__).resolve().parent.parent
ENTRYPOINTS = ["single_shot_inference.py"]  # vLLM has 0 imports of dsv4 (Step 0 confirmed)

def module_to_path(mod):
    p = ROOT / (mod.replace(".", "/") + ".py")
    if p.exists(): return p
    p = ROOT / mod.replace(".", "/") / "__init__.py"
    return p if p.exists() else None

def imports_of(path):
    """Parse ALL imports including lazy ones inside functions."""
    tree = ast.parse(path.read_text())
    out = set()
    for n in ast.walk(tree):
        if isinstance(n, ast.Import):
            out |= {a.name for a in n.names}
        elif isinstance(n, ast.ImportFrom) and n.module:
            out.add(n.module)
    return {m for m in out if m.startswith("dsv4")}

seen, stack = set(), list(ENTRYPOINTS)
stack = [ (ROOT / e) for e in stack ]
while stack:
    f = stack.pop()
    if f in seen or f is None or not f.exists(): continue
    seen.add(f)
    for m in imports_of(f):
        mp = module_to_path(m)
        if mp and mp not in seen: stack.append(mp)

all_py = set((ROOT / "dsv4").rglob("*.py"))
dead = sorted(p.relative_to(ROOT) for p in all_py - seen if "__pycache__" not in str(p))
print("REACHABLE:", len(seen), " | DEAD CANDIDATES:", len(dead))
for d in dead: print("  ", d)
