"""Conservative white-background pose gate for left-facing quadruped references.
This measures projected gaps, not hidden 3D surfaces. Ambiguous poses fail closed.
"""
from pathlib import Path
import json
import numpy as np
from PIL import Image, ImageDraw

def components(mask):
    seen=np.zeros(mask.shape,bool); result=[]; h,w=mask.shape
    for y,x in zip(*np.where(mask)):
        if seen[y,x]: continue
        stack=[(y,x)];seen[y,x]=True;pts=[]
        while stack:
            yy,xx=stack.pop();pts.append((yy,xx))
            for dy,dx in ((0,1),(0,-1),(1,0),(-1,0)):
                a,b=yy+dy,xx+dx
                if 0<=a<h and 0<=b<w and mask[a,b] and not seen[a,b]:seen[a,b]=True;stack.append((a,b))
        result.append(np.array(pts))
    return sorted(result,key=len,reverse=True)

def distance(a,b):
    # Exact pixel-centre distance, bounded chunks. Image is reduced to 512 px.
    best=float('inf')
    for chunk in np.array_split(a,max(1,len(a)//128+1)):
        best=min(best,float(np.sqrt(((chunk[:,None,:]-b[None,:,:])**2).sum(2).min())))
    return best

def check(path,out):
    im=Image.open(path).convert('RGB');im.thumbnail((512,512));a=np.asarray(im)
    # White background requested explicitly; no learned segmentation or judge.
    mask=(a.min(2)<225)&((255-a.astype(float)).sum(2)>65)
    comps=components(mask); checks={}; reasons=[]
    if not comps or len(comps[0])<1000: raise ValueError('No usable animal silhouette')
    body=comps[0];sil=np.zeros(mask.shape,bool);sil[body[:,0],body[:,1]]=True
    y0,x0=body.min(0);y1,x1=body.max(0);H=int(y1-y0);W=int(x1-x0)
    cut=int(y0+H*.74);lower=sil.copy();lower[:cut]=False
    legs=[c for c in components(lower) if len(c)>H*W*.002 and c[:,0].max()>y0+.86*H]
    legs.sort(key=lambda c:c[:,1].mean());checks['four_separate_legs']=len(legs)==4
    gaps=[distance(legs[i],legs[i+1]) for i in range(len(legs)-1)]
    checks['leg_gaps']=len(gaps)==3 and min(gaps)>=max(3,H*.012)
    tail_gap=None;tail_pixels=0;tail_start=None
    if len(legs)==4:
        rear=legs[-1];tail_start=int(rear[:,1].mean()+W*.025)
        tail=sil.copy();tail[:,:tail_start]=False;tail[int(y0+H*.58):]=False
        tc=components(tail);t=tc[0] if tc else np.empty((0,2),int);tail_pixels=len(t)
        checks['tail_held_out']=len(t)>H*W*.003 and t[:,1].max()>rear[:,1].max()+W*.03
        # Include both rear legs above lower band, up to belly height. Root is
        # excluded because a tail must attach to the rump, not float in space.
        rear_mask=sil.copy();rear_mask[:int(y0+H*.52)]=False
        rear_mask[:,:int(legs[1][:,1].max()+1)]=False
        hind=np.argwhere(rear_mask)
        tail_gap=distance(t,hind) if len(t) and len(hind) else 0
        checks['tail_hindleg_gap']=tail_gap>=max(5,H*.025)
    else:checks.update(tail_held_out=False,tail_hindleg_gap=False)
    # Background must really be plain light, not a dark scene falsely segmented.
    border=np.concatenate([a[0],a[-1],a[:,0],a[:,-1]])
    checks['not_cropped']=x0>=3 and y0>=3 and x1<im.width-3 and y1<im.height-3
    checks['white_background']=float((border.min(1)>235).mean())>.98
    checks={k:bool(v) for k,v in checks.items()}
    result={'pass':all(checks.values()),'checks':checks,'measurement_image_px':list(im.size),'leg_gap_px':gaps,'tail_hindleg_gap_px':tail_gap,'tail_pixels':tail_pixels,'leg_count':len(legs),'leg_band_y':cut,'tail_roi_start_x':tail_start,'scope':'Projected silhouette; head left, tail right, raised tail, four staggered feet; visual inspection still required.'}
    out=Path(out);out.mkdir(exist_ok=True,parents=True)
    Image.fromarray((sil*255).astype('uint8')).save(out/'silhouette.png')
    draw=ImageDraw.Draw(im);draw.line((0,cut,im.width,cut),fill='blue',width=2)
    for c in legs:
        lo=c.min(0);hi=c.max(0);draw.rectangle((int(lo[1]),int(lo[0]),int(hi[1]),int(hi[0])),outline='lime',width=2)
    if tail_start is not None:draw.rectangle((tail_start,int(y0),int(x1),int(y0+H*.58)),outline='orange',width=2)
    im.save(out/'gap-measurement.png');(out/'reference-gate.json').write_text(json.dumps(result,indent=2));return result
