#!/usr/bin/env python3
# Deep static audit for ClaimManager: resolves every local import and verifies
# that each imported name is actually exported by the target module.
import os, re, json, sys

ROOT = "/data/app/claimmanager"
SRC = os.path.join(ROOT, "src")
EXTS = [".ts", ".tsx", ".js", ".jsx", ""]

files = []
for dirpath, dirnames, filenames in os.walk(SRC):
    dirnames[:] = [d for d in dirnames if d != "node_modules"]
    for fn in filenames:
        if fn.endswith((".ts", ".tsx")):
            files.append(os.path.join(dirpath, fn))
files.sort()

def read(p):
    with open(p, encoding="utf-8", errors="replace") as f:
        return f.read()

texts = {p: read(p) for p in files}

def resolve(spec, importer):
    if spec.startswith("@/"):
        base = os.path.join(SRC, spec[2:])
    elif spec.startswith("."):
        base = os.path.normpath(os.path.join(os.path.dirname(importer), spec))
    else:
        return None  # package import
    cands = []
    for e in EXTS:
        cands.append(base + e)
    for e in [".ts", ".tsx", ".js", ".jsx"]:
        cands.append(os.path.join(base, "index" + e))
    # allowImportingTsExtensions: spec may already end with .ts/.tsx
    for c in cands:
        if c and os.path.isfile(c):
            return c
    return False  # unresolved

# collect exported names per file
export_re = [
    re.compile(r"^export\s+(?:async\s+)?function\s+([A-Za-z0-9_$]+)", re.M),
    re.compile(r"^export\s+(?:const|let|var)\s+([A-Za-z0-9_$]+)", re.M),
    re.compile(r"^export\s+(?:abstract\s+)?class\s+([A-Za-z0-9_$]+)", re.M),
    re.compile(r"^export\s+(?:type|interface)\s+([A-Za-z0-9_$]+)", re.M),
    re.compile(r"^export\s+enum\s+([A-Za-z0-9_$]+)", re.M),
    re.compile(r"^export\s+declare\s+(?:const|function|type)\s+([A-Za-z0-9_$]+)", re.M),
]
brace_export_re = re.compile(r"^export\s*(?:type\s*)?\{([^}]*)\}", re.M)
star_export_re = re.compile(r"^export\s*\*\s*from\s*['\"]([^'\"]+)['\"]", re.M)
default_export_re = re.compile(r"^export\s+default\b", re.M)

exports = {}
star_from = {}
for p, t in texts.items():
    names = set()
    for r in export_re:
        names.update(r.findall(t))
    for m in brace_export_re.finditer(t):
        for part in m.group(1).split(","):
            part = part.strip()
            if not part:
                continue
            part = re.sub(r"^type\s+", "", part)
            if " as " in part:
                part = part.split(" as ")[-1]
            names.add(part.strip())
    if default_export_re.search(t):
        names.add("default")
    exports[p] = names
    star_from[p] = star_export_re.findall(t)

def all_exports(p, seen=None):
    if seen is None:
        seen = set()
    if p in seen:
        return set()
    seen.add(p)
    out = set(exports.get(p, set()))
    for spec in star_from.get(p, []):
        tgt = resolve(spec, p)
        if tgt:
            out |= all_exports(tgt, seen)
    return out

import_re = re.compile(
    r"import\s+(type\s+)?([^'\"]*?)\s*from\s*['\"]([^'\"]+)['\"]", re.S)
side_effect_re = re.compile(r"^import\s+['\"]([^'\"]+)['\"]", re.M)

unresolved = []
missing_names = []

for p, t in texts.items():
    specs = []
    for m in import_re.finditer(t):
        clause, spec = m.group(2), m.group(3)
        specs.append((clause, spec))
    for spec in side_effect_re.findall(t):
        specs.append(("", spec))
    for clause, spec in specs:
        tgt = resolve(spec, p)
        if tgt is None:
            continue  # external package
        if tgt is False:
            unresolved.append((os.path.relpath(p, ROOT), spec))
            continue
        avail = all_exports(tgt)
        clause = clause.strip()
        if "*" in clause:
            continue
        names = []
        bm = re.search(r"\{([^}]*)\}", clause, re.S)
        # default import
        head = clause.split("{")[0].strip().rstrip(",").strip()
        if head and not head.startswith("{"):
            names.append("default")
        if bm:
            for part in bm.group(1).split(","):
                part = part.strip()
                if not part:
                    continue
                part = re.sub(r"^type\s+", "", part).strip()
                if " as " in part:
                    part = part.split(" as ")[0]
                names.append(part.strip())
        for n in names:
            if n and n not in avail:
                missing_names.append(
                    (os.path.relpath(p, ROOT), n, os.path.relpath(tgt, ROOT)))

print("### FILES SCANNED:", len(files))
print()
print("### UNRESOLVED IMPORT PATHS:", len(unresolved))
for f, s in unresolved:
    print("  MISSING MODULE  %s  <-  %s" % (s, f))
print()
print("### IMPORTED NAMES NOT EXPORTED:", len(missing_names))
for f, n, t in missing_names:
    print("  MISSING EXPORT  '%s' from %s  <-  %s" % (n, t, f))
