"""Neutral candidate lattice. No constructor imports or executable task state."""
import random
from common import seed, delay
def neighbors(h):
    adj={u:[] for u in h['positions']}
    for e in h['bonds']:
        adj[e['u']].append((e['v'],e['delay']))
        adj[e['v']].append((e['u'],e['delay']))
    return {u:sorted(v) for u,v in adj.items()}
def admissible(h):
    adj=neighbors(h); root=h['terminals'][0]['site']; seen={root}; todo=[root]
    while todo:
        for v,_ in adj[todo.pop()]:
            if v not in seen: seen.add(v); todo.append(v)
    return all(t['site'] in seen for t in h['terminals'])
def generate(namespace,group,L,K,p):
    # A finite 16-candidate procedure; no constructor success is consulted.
    rejected=[]
    for attempt in range(16):
        jitter=random.Random(seed(namespace,'jitter',group,L,attempt))
        obstacle=random.Random(seed(namespace,'obstacle',group,L,attempt))
        ids=random.Random(seed(namespace,'opaque',group,L,attempt))
        coords=[(x,y,z) for x in range(L) for y in range(L) for z in range(L)]
        labels=[f'n{i:03d}' for i in range(len(coords))]; ids.shuffle(labels)
        mapping=dict(zip(coords,labels)); pos={}; unavailable=[]
        for c in coords:
            xyz=[round(v+jitter.uniform(-.08,.08),6) for v in c]
            draw=obstacle.random()
            interior=all(0<v<L-1 for v in c)
            if interior and draw<p: unavailable.append(dict(id=mapping[c],position=xyz)); continue
            pos[mapping[c]]=xyz
        bonds=[]
        for c in coords:
            u=mapping[c]
            if u not in pos: continue
            for axis in range(3):
                d=list(c); d[axis]+=1; v=mapping.get(tuple(d))
                if v in pos: bonds.append(dict(u=u,v=v,delay=delay(pos[u],pos[v])))
        boundary=[mapping[c] for c in coords if any(v in (0,L-1) for v in c)]
        rng=random.Random(seed(namespace,'terminals',group,L,attempt)); rng.shuffle(boundary)
        # Prefix coupling across K: query,response,then 8 potential public writes.
        terminals=[dict(kind='query',site=boundary[0]),dict(kind='response',site=boundary[1])]
        terminals += [dict(kind='write',a=a,site=boundary[a+2]) for a in range(K)]
        h=dict(L=L,K=K,p=p,positions=pos,bonds=bonds,unavailable=unavailable,
               terminals=terminals,terminal_delay=1,attempt=attempt)
        if admissible(h): return h,rejected
        rejected.append(dict(attempt=attempt,reason='terminals_not_connected'))
    return None,rejected
def cues(h):
    out={u:[] for u in h['positions']}
    for t in h['terminals']: out[t['site']].append({k:v for k,v in t.items() if k!='site'})
    return out
