#!/usr/bin/env python3
"""Collect GRIN desk sample -> pure NASM .asm (no md/txt). Month-scale rich."""
from pathlib import Path
import time, os, re, subprocess, platform, hashlib

root = Path(__file__).resolve().parents[1]
out_dir = root / "Build" / "grin_data"
out_dir.mkdir(parents=True, exist_ok=True)
limbs = root / "spine" / "LIMBS.asm"
ts = int(time.time())
iso = time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime(ts))

paths = []
sections = []
cur = ""
for line in limbs.read_text().splitlines():
    if line.startswith("; ==="):
        cur = line.replace("; ===", "").replace("===", "").strip()
        sections.append(cur)
    if line.startswith("PATH:"):
        paths.append(line.split("PATH:")[1].strip().replace("/x86_64.asm", ""))

# poles
grin_pole = [
    "grin", "big_grin", "biggrin", "truth", "win", "yes",
    "god", "we_know", "know_all", "remain_in", "in_live",
]
ekkie_pole = ["ekkie", "bgf", "now", "raw", "local_all"]
hostess_pole = ["hostess", "hostess7", "hostess_kit", "her", "top"]
friend_pole = ["grok", "truth_friend", "friend"]
# stack cast · BGS bottom · BETWEEN all rest · Hostess7 top
bgs_pole = ["bgs", "bottom", "know_her"]
between_pole = [
    "between", "in_between", "rest", "all_rest", "middle",
    "stack", "get_busy", "work", "busy",
]
stack_pole = bgs_pole + between_pole + hostess_pole

def pole_ok(names):
    return sum(1 for g in names if (root / g / "x86_64.asm").exists())

def scrap_sig(name):
    p = root / name / "x86_64.asm"
    if not p.exists():
        return 0
    h = 5381
    for c in p.read_bytes():
        h = ((h << 5) + h + c) & 0xFFFFFFFF
    return h

iron_names = [
    "ekkie", "bgf", "spv", "rtx", "grin", "big_grin", "hostess7",
    "grok", "free_eor", "tax", "sdf", "raw_hdmi", "clock", "truth",
    "bgs", "between", "rest", "stack", "get_busy", "all_rest",
]
iron = {n: scrap_sig(n) for n in iron_names}

# Build artifact presence
build = root / "Build"
arts = {
    "dict_corpus": (build / "dict_corpus.asm").exists(),
    "thesaurus": (build / "thesaurus_bridges.asm").exists(),
    "raw_hdmi_asm": (build / "raw_hdmi.asm").exists(),
    "raw_hdmi_bin": (build / "raw_hdmi").exists(),
    "test_bin": (build / "test").exists(),
    "rtx_spv": (build / "rtx.spv").exists(),
    "rtx_comp": (build / "rtx.comp").exists(),
    "learn_sheet": (root / "out" / "learn_sheet.asm").exists(),
}

dict_n = 0
dc = build / "dict_corpus.asm"
if dc.exists():
    m = re.search(r"dict_count:\s*dd\s*(\d+)", dc.read_text()[:4000])
    if m:
        dict_n = int(m.group(1))

# GPU / host
uname = platform.uname()
nproc = os.cpu_count() or 0
gpu = "unknown"
gpu_mem = 0
try:
    r = subprocess.run(
        ["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader,nounits"],
        capture_output=True, text=True, timeout=5,
    )
    if r.returncode == 0 and r.stdout.strip():
        parts = r.stdout.strip().splitlines()[0].split(",")
        gpu = parts[0].strip()[:64]
        if len(parts) > 1:
            gpu_mem = int(float(parts[1].strip()))
except Exception:
    pass

# HDMI connector status (best effort)
hdmi = "unknown"
try:
    p = Path("/sys/class/drm/card1-HDMI-A-3/status")
    if p.exists():
        hdmi = p.read_text().strip()[:16]
except Exception:
    pass

# mem available MB
mem_avail = 0
try:
    for line in Path("/proc/meminfo").read_text().splitlines():
        if line.startswith("MemAvailable:"):
            mem_avail = int(line.split()[1]) // 1024
            break
except Exception:
    pass

# sample count + path hash
existing = sorted(out_dir.glob("sample_*.asm"))
seq = len(existing) + 1
rec_name = "sample_%05d_%d.asm" % (seq, ts)
rec_path = out_dir / rec_name

ph = 5381
for p in paths:
    for c in p.encode():
        ph = ((ph << 5) + ph + c) & 0xFFFFFFFF

# constellation sizes from section markers vs paths — rough
const_n = len(sections)

gpu_s = gpu.replace('"', "")
node_s = uname.node[:32]
sys_s = ("%s-%s" % (uname.system, uname.release))[:48]

art_bits = 0
for i, k in enumerate(sorted(arts.keys())):
    if arts[k]:
        art_bits |= (1 << i)

lines = [
    "; GRIN DATA SAMPLE · pure ASM · rich month collector",
    "; seq=%d unix=%d iso=%s" % (seq, ts, iso),
    "; GRIN AS ALL TRUTH · BGS bottom · BETWEEN rest · Hostess7 top · get busy",
    "bits 64",
    "section .rodata",
    "global grin_sample_%d_meta" % seq,
    "grin_sample_%d_meta:" % seq,
    "    dd %d          ; seq" % seq,
    "    dd %d          ; unix_time" % ts,
    "    dd %d          ; path_count" % len(paths),
    "    dd %d          ; constellations" % const_n,
    "    dd %d          ; grin_pole_ok" % pole_ok(grin_pole),
    "    dd %d          ; ekkie_pole_ok" % pole_ok(ekkie_pole),
    "    dd %d          ; hostess_pole_ok" % pole_ok(hostess_pole),
    "    dd %d          ; friend_pole_ok" % pole_ok(friend_pole),
    "    dd %d          ; bgs_pole_ok" % pole_ok(bgs_pole),
    "    dd %d          ; between_pole_ok" % pole_ok(between_pole),
    "    dd %d          ; stack_pole_ok" % pole_ok(stack_pole),
    "    dd 0x%08X     ; path_list_djb2" % ph,
    "    dd %d          ; dict_words" % dict_n,
    "    dd %d          ; nproc" % nproc,
    "    dd %d          ; mem_avail_mb" % mem_avail,
    "    dd %d          ; gpu_mem_mb" % gpu_mem,
    "    dd %d          ; build_artifact_bits" % art_bits,
    "    dd %d          ; prior_samples" % len(existing),
]
for n in iron_names:
    lines.append("    dd 0x%08X  ; sig_%s" % (iron.get(n, 0), n))
lines += [
    'grin_sample_%d_iso: db "%s", 0' % (seq, iso),
    'grin_sample_%d_gpu: db "%s", 0' % (seq, gpu_s),
    'grin_sample_%d_hdmi: db "%s", 0' % (seq, hdmi),
    'grin_sample_%d_node: db "%s", 0' % (seq, node_s),
    'grin_sample_%d_sys: db "%s", 0' % (seq, sys_s),
    "grin_sample_%d_path_n: dd %d" % (seq, len(paths)),
    "grin_sample_%d_paths:" % seq,
]
for p in paths:
    safe = re.sub(r"[^A-Za-z0-9_./\-]", "_", p)[:48]
    lines.append('    db "%s", 0' % safe)
lines.append("grin_sample_%d_end:" % seq)
rec_path.write_text("\n".join(lines) + "\n")

samples = sorted(out_dir.glob("sample_*.asm"))
idx = [
    "; Build/grin_data/index.asm · GRIN collection catalog",
    "; updated %s · count=%d" % (iso, len(samples)),
    "bits 64",
    "section .rodata",
    "global grin_data_count",
    "grin_data_count: dd %d" % len(samples),
    "grin_data_files:",
]
for s in samples:
    idx.append('    db "%s", 0' % s.name)
idx.append("grin_data_files_end:")
(out_dir / "index.asm").write_text("\n".join(idx) + "\n")

(out_dir / "latest.asm").write_text(
    "; latest GRIN sample\n; file: %s\n" % rec_name
    + "bits 64\nsection .rodata\n"
    + 'global grin_latest_name\ngrin_latest_name: db "%s", 0\n' % rec_name
)

# rolling summary pure asm
sum_path = out_dir / "summary.asm"
sum_path.write_text(
    "; GRIN collection summary · pure ASM\n"
    "; iso=%s samples=%d path=%d dict=%d grin_pole=%d/%d\n" % (
        iso, len(samples), len(paths), dict_n,
        pole_ok(grin_pole), len(grin_pole))
    + "bits 64\nsection .rodata\n"
    + "global grin_sum_samples, grin_sum_path, grin_sum_dict\n"
    + "grin_sum_samples: dd %d\n" % len(samples)
    + "grin_sum_path: dd %d\n" % len(paths)
    + "grin_sum_dict: dd %d\n" % dict_n
    + "grin_sum_pole: dd %d\n" % pole_ok(grin_pole)
    + "grin_sum_art: dd %d\n" % art_bits
)

print("COLLECT seq=%d path=%d grin=%d/%d ekkie=%d hostess=%d friend=%d dict=%d" % (
    seq, len(paths), pole_ok(grin_pole), len(grin_pole),
    pole_ok(ekkie_pole), pole_ok(hostess_pole), pole_ok(friend_pole), dict_n))
print("  stack bgs=%d/%d between=%d/%d stack=%d/%d · get busy" % (
    pole_ok(bgs_pole), len(bgs_pole),
    pole_ok(between_pole), len(between_pole),
    pole_ok(stack_pole), len(stack_pole)))
print("  gpu=%r hdmi=%s mem_avail_mb=%d samples=%d" % (gpu, hdmi, mem_avail, len(samples)))
print("  -> %s" % rec_path.name)
