"""Scan all .py files for missing type hints. Writes scan_report.txt."""
import ast
import os

SKIP: set[str] = {'.git', '__pycache__', '.venv', 'venv', 'node_modules', '.claude', '.gemini'}
BASE: str = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
os.chdir(BASE)

results: dict[str, tuple[int, int, int, int]] = {}
for root, dirs, files in os.walk('.'):
 dirs[:] = [d for d in dirs if d not in SKIP]
 for f in files:
  if not f.endswith('.py'):
   continue
  path: str = os.path.join(root, f).replace('\\', '/')
  try:
   with open(path, 'r', encoding='utf-8-sig') as fh:
    tree = ast.parse(fh.read())
  except Exception:
   continue
  counts: list[int] = [0, 0, 0]  # nr, up, uv

  def scan(scope: ast.AST, prefix: str = '') -> None:
  # Iterate top-level nodes in this scope
   for node in ast.iter_child_nodes(scope):
    if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
     if node.returns is None:
      counts[0] += 1
     for arg in node.args.args:
      if arg.arg not in ('self', 'cls') and arg.annotation is None:
       counts[1] += 1
    elif isinstance(node, ast.Assign):
     for t in node.targets:
      if isinstance(t, ast.Name):
       counts[2] += 1
    elif isinstance(node, ast.ClassDef):
     scan(node, prefix=f'{node.name}.')
  scan(tree)
  nr, up, uv = counts
  total: int = nr + up + uv
  if total > 0:
   results[path] = (nr, up, uv, total)

lines: list[str] = []
lines.append(f'Files with untyped items: {len(results)}')
lines.append('')
lines.append(f'{"File":<58} {"NoRet":>6} {"Params":>7} {"Vars":>5} {"Total":>6}')
lines.append('-' * 85)
gt: int = 0
for path in sorted(results, key=lambda x: results[x][3], reverse=True):
 nr, up, uv, t = results[path]
 lines.append(f'{path:<58} {nr:>6} {up:>7} {uv:>5} {t:>6}')
 gt += t
lines.append('-' * 85)
lines.append(f'{"TOTAL":<58} {"":>6} {"":>7} {"":>5} {gt:>6}')

report: str = '\n'.join(lines)
with open('scan_report.txt', 'w', encoding='utf-8') as f:
 f.write(report)
