"""Validate delivered manifest, GLB attributes and external shelf. Requires numpy/Pillow."""
import json,struct,hashlib
from pathlib import Path
import numpy as np
from PIL import Image
exec(Path('uv_check.py').read_text())
kit=json.loads(Path('kit.json').read_text());shelf=json.loads(Path('materials.json').read_text())['materials']
expected='Render_white Render_grey Concrete_boardmarked Larch_cladding Larch_cladding_charred Bronze_aluminium Glass_clear Steel_dark Zinc_coping Roof_gravel Paving_concrete Door_paint'.split()
assert list(shelf)==expected
assert len(kit['atoms'])==19 and all(a['triangles']<=a['triangle_budget'] for a in kit['atoms'])
assert sum(a['triangles'] for a in kit['atoms'])==kit['total_triangles']
assert not g.get('images') and not g.get('textures')
roots=[g['nodes'][i] for i in g['scenes'][g.get('scene',0)]['nodes']]
assert {x['name'] for x in roots}=={a['node_name'] for a in kit['atoms']}
assert all(not any(k in x for k in ['translation','scale','rotation','matrix']) for x in roots)
for m in g['meshes']:
 for p in m['primitives']:
  assert set(['POSITION','NORMAL','TEXCOORD_0','TANGENT','COLOR_0'])<=set(p['attributes'])
  for name,i in p['attributes'].items():assert np.isfinite(acc(i)).all()
  normals=acc(p['attributes']['NORMAL']);assert np.max(abs(np.linalg.norm(normals,axis=1)-1))<1e-4
  if m['name']=='planter_concrete' and g['materials'][p['material']]['name']=='Door_paint':
   grass=acc(p['attributes']['COLOR_0']);assert np.unique(grass,axis=0).shape[0]>=4,'Grass COLOR_0 lost'
size=0;decoded=0;edges={};hashes={}
for name,spec in shelf.items():
 for kind,path in {**spec['maps'],**({'Height':spec['height_map']} if 'height_map' in spec else {})}.items():
  f=Path(path);im=Image.open(f);a=np.array(im).astype(float);assert im.size==(spec['resolution'],)*2
  size+=f.stat().st_size;decoded+=im.width*im.height*4*4/3
  # Normal has derivative direction at seam; record every map honestly.
  edges[path]={'opposite_column_max_delta':float(abs(a[:,0]-a[:,-1]).max()),'opposite_row_max_delta':float(abs(a[0]-a[-1]).max())}
  if kind!='Normal':assert max(edges[path].values())==0,(path,edges[path])
  hashes[path]=hashlib.sha256(f.read_bytes()).hexdigest()
assert size<32*1024**2 and decoded<160*1024**2
assert Path('kit.glb').stat().st_size<1024**2
report={'pass':True,'atoms':19,'materials':12,'triangles':kit['total_triangles'],'glb_bytes':len(b),'shelf_png_bytes':size,'decoded_rgba8_with_mips_bytes_including_height':int(decoded),'glb_primitives':sum(len(m['primitives']) for m in g['meshes']),'grass_color0_variants':int(np.unique(grass,axis=0).shape[0]),'texture_seams':edges,'sha256':{'kit.glb':hashlib.sha256(b).hexdigest(),**hashes}}
Path('validation.json').write_text(json.dumps(report,indent=2));print({k:v for k,v in report.items() if k not in ['texture_seams','sha256']})
