import ast
import os
from pathlib import Path

ROOT = Path(r"C:\Users\User\arifOS")
TARGET_DIRS = [
    ROOT / "codebase" / "agi",
    ROOT / "codebase" / "asi",
    ROOT / "codebase" / "apex",
    ROOT / "aaa_mcp",
]
EXCLUDE_DIR_NAMES = {"tests", "test", "docs", "doc", "archive", "__pycache__"}
EXCLUDE_PATH_PARTS = {"tests", "test", "docs", "doc", "archive"}

# Map file path -> module name
module_by_path = {}
path_by_module = {}


def is_excluded(path: Path) -> bool:
    parts = {p.lower() for p in path.parts}
    if parts & EXCLUDE_PATH_PARTS:
        return True
    for p in path.parts:
        if p.lower() in EXCLUDE_DIR_NAMES:
            return True
    return False


def module_name_from_path(path: Path) -> str:
    rel = path.relative_to(ROOT)
    if rel.name == "__init__.py":
        rel = rel.parent
    else:
        rel = rel.with_suffix("")
    return ".".join(rel.parts)


# Collect python modules
for base in TARGET_DIRS:
    if not base.exists():
        continue
    for dirpath, dirnames, filenames in os.walk(base):
        # prune excluded dirs
        dirnames[:] = [d for d in dirnames if d.lower() not in EXCLUDE_DIR_NAMES]
        for fname in filenames:
            if not fname.endswith(".py"):
                continue
            fpath = Path(dirpath) / fname
            if is_excluded(fpath):
                continue
            mod = module_name_from_path(fpath)
            module_by_path[fpath] = mod
            path_by_module[mod] = fpath

# Build inbound import counts
inbound = {m: set() for m in path_by_module}


def resolve_relative(module: str, level: int, cur_module: str) -> str | None:
    if level == 0:
        return module
    cur_parts = cur_module.split(".")
    if level > len(cur_parts):
        return None
    base_parts = cur_parts[:-level]
    if module:
        base_parts += module.split(".")
    return ".".join(base_parts)


for fpath, cur_mod in module_by_path.items():
    try:
        tree = ast.parse(fpath.read_text(encoding="utf-8"))
    except Exception:
        # skip files we can't parse
        continue

    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                name = alias.name
                # direct match
                if name in inbound:
                    inbound[name].add(cur_mod)
                else:
                    # if it imports a package, count package init if present
                    if name in path_by_module and name in inbound:
                        inbound[name].add(cur_mod)
        elif isinstance(node, ast.ImportFrom):
            mod = node.module or ""
            abs_mod = resolve_relative(mod, node.level, cur_mod)
            if not abs_mod:
                continue
            # if importing specific names, map to submodules when possible
            for alias in node.names:
                if alias.name == "*":
                    # treat as importing the module/package itself
                    if abs_mod in inbound:
                        inbound[abs_mod].add(cur_mod)
                    continue
                candidate = f"{abs_mod}.{alias.name}"
                if candidate in inbound:
                    inbound[candidate].add(cur_mod)
                elif abs_mod in inbound:
                    inbound[abs_mod].add(cur_mod)

# Exclude __init__.py and __main__.py from candidates
candidates = []
for mod, fpath in path_by_module.items():
    if fpath.name in {"__init__.py", "__main__.py"}:
        continue
    if mod.endswith(".__init__"):
        continue
    if not inbound.get(mod):
        candidates.append((mod, fpath))

# Sort for stable output
candidates.sort(key=lambda x: str(x[1]))

print("CANDIDATES")
for mod, fpath in candidates:
    print(f"{mod} | {fpath}")
