#!/usr/bin/env python3
"""Emission-site census, run against a GIT SHA (never the dirty worktree).

Requirements agreed with hertz (v0.65.0 arc):
  (1) every output file TERMINATES WITH A NEWLINE
  (2) the LOGICAL record count is asserted in the generator, never via wc -l
  (3) the known-positive sentinel SUBSCRIBE_DECISION must be present, or the count is unfalsifiable
  (4) LOOKBACK WINDOW for multi-line macro calls; truncate ONLY at a #[cfg(test)] that opens a module
"""
import re, subprocess, sys, hashlib, os

SHA = sys.argv[1]
OUT = sys.argv[2]
LOOKBACK = 4
MACROS = ("eprintln!", "println!", "eprint!", "print!")
TOKEN_RE = re.compile(r'"([A-Z][A-Z0-9_]+):')
MACRO_RE = re.compile(r'\b(eprintln!|println!|eprint!|print!)')
SENTINEL = "SUBSCRIBE_DECISION"
SURFACES = ("stderr", ".log", "read_to_string", "daemon_stderr", "sink_path", "from_utf8_lossy")

def git(*a):
    return subprocess.run(["git"] + list(a), capture_output=True, check=True).stdout

def blob(path):
    return git("show", f"{SHA}:{path}").decode("utf-8", "replace")

def truncate_at_test_module(lines):
    """(4) cut ONLY at a #[cfg(test)] that OPENS A MODULE — an inline-helper cfg(test)
    (broker.rs:2771) must not discard ~6,800 lines of shipping code below it."""
    for i, ln in enumerate(lines):
        if ln.strip() == "#[cfg(test)]":
            for nxt in lines[i+1:i+4]:
                s = nxt.strip()
                if not s:
                    continue
                if s.startswith("mod ") or s.startswith("pub mod "):
                    return lines[:i]
                break
    return lines

files = git("ls-tree", "-r", "--name-only", SHA, "--", "crates/").decode().splitlines()
src = [f for f in files if re.match(r"crates/[^/]+/src/.*\.rs$", f)]
tst = [f for f in files if re.match(r"crates/[^/]+/tests/.*\.rs$", f)]

sites = []
for path in sorted(src):
    lines = truncate_at_test_module(blob(path).split("\n"))
    for j, ln in enumerate(lines):
        for tok in TOKEN_RE.findall(ln):
            macro = None
            for k in range(j, max(-1, j - LOOKBACK - 1), -1):   # (4) lookback window
                m = MACRO_RE.search(lines[k])
                if m:
                    macro = m.group(1)
                    break
            if macro:
                sites.append((f"{path}:{j+1}", tok, macro))

tokens = sorted({t for _, t, _ in sites})

consumers = []
for path in sorted(tst):
    text = blob(path)
    if not any(s in text for s in SURFACES):
        continue
    # precut predicate, recovered by measurement at 8e098377: the token must appear
    # followed by a COLON (the emitted "TOKEN:" prefix a test parses), not bare —
    # bare matching pulls in prose ("the IDLE case") and inflated 26 files to 72.
    hit = sorted({t for t in tokens if (t + ":") in text})
    if hit:
        consumers.append((path, len(hit), ",".join(hit)))

# (3) sentinel: assert the known-positive is in the population
assert any(t == SENTINEL for _, t, _ in sites), f"SENTINEL {SENTINEL} ABSENT — census is unfalsifiable"

def write(name, rows, render):
    p = os.path.join(OUT, name)
    with open(p, "w", encoding="utf-8", newline="\n") as fh:
        for r in rows:
            fh.write(render(r) + "\n")          # (1) every record newline-TERMINATED
    # (2) logical record count asserted here, in the generator
    with open(p, "rb") as fh:
        data = fh.read()
    assert data.endswith(b"\n"), f"{name} does not end with a newline"
    assert data.count(b"\n") == len(rows), f"{name} logical count {len(rows)} != {data.count(b'\n')} terminators"
    print(f"{name}\t{len(rows)} records\tsha256 {hashlib.sha256(data).hexdigest()}")
    return len(rows)

os.makedirs(OUT, exist_ok=True)
n_sites = write("census_sites.txt", sites, lambda r: f"{r[0]}\t{r[1]}\t{r[2]}")
n_tok   = write("census_tokens.txt", tokens, lambda r: r)
n_cons  = write("census_consumers.txt", consumers, lambda r: f"{r[0]}\t{r[1]}\t{r[2]}")

from collections import Counter
print(f"\nsha={SHA}")
print(f"sites={n_sites} tokens={n_tok} consumers={n_cons}")
print("by crate: " + ", ".join(f"{c} {n}" for c, n in sorted(Counter(p.split('/')[1] for p, _, _ in sites).items())))
print("by macro: " + ", ".join(f"{m} {n}" for m, n in sorted(Counter(m for _, _, m in sites).items())))
