#!/usr/bin/env python3
"""One finish step: export clean pieces, crop source photos, ingest into forge-assets."""
import argparse,datetime,json,subprocess,re,sys
from pathlib import Path
from zoneinfo import ZoneInfo
import numpy as np
from PIL import Image
p=argparse.ArgumentParser();p.add_argument('room',help='finished room out/ROOM/walk');p.add_argument('--library',required=True);p.add_argument('--out',default='/tmp/wlib-room');p.add_argument('--licence-evidence',required=True);p.add_argument('--skip-export',action='store_true');p.add_argument('--no-reuse',action='store_true');a=p.parse_args()
room=Path(a.room).resolve();out=Path(a.out);lib=Path(a.library).resolve();out.mkdir(parents=True,exist_ok=True)
if not a.skip_export:subprocess.run(['node',str(Path(__file__).with_name('library_export.mjs')),str(room),str(out)],check=True)
manifest=json.loads((out/'manifest.json').read_text());raw=json.loads((room/'raw.json').read_text())
for item in manifest['kept']:
    name=item['group'].split(':',1)[1];lo=item['plan_bounds']['min'];hi=item['plan_bounds']['max'];points=np.array([[x,y,z] for x in [lo[0],hi[0]] for y in [lo[1],hi[1]] for z in [lo[2],hi[2]]]);best=None
    for c in raw['cameras']:
        q=points@np.array(c['R']).T+np.array(c['t']);
        if (q[:,2] <= .15).any():continue
        u=c['fx']*q[:,0]/q[:,2]+c['cx'];v=c['fy']*q[:,1]/q[:,2]+c['cy']
        x0,x1=max(0,float(u.min())),min(c['W'],float(u.max()));y0,y1=max(0,float(v.min())),min(c['H'],float(v.max()));area=max(0,x1-x0)*max(0,y1-y0)
        # Prefer an entire visible piece over a cut-off close-up.
        fraction=area/max(1,(u.max()-u.min())*(v.max()-v.min()));score=area if fraction >= .5 else 0
        if score>2500 and (best is None or score>best[0]):best=(score,c,(x0,y0,x1,y1))
    if best is None:raise RuntimeError('no source photo sees '+name)
    _,cam,box=best;photo=Image.open(room/cam['image']);sx=photo.width/cam['W'];sy=photo.height/cam['H'];x0,y0,x1,y1=box;pad=.06*max(x1-x0,y1-y0)
    photo=photo.crop((max(0,int((x0-pad)*sx)),max(0,int((y0-pad)*sy)),min(photo.width,int((x1+pad)*sx)),min(photo.height,int((y1+pad)*sy))));crop=out/(item['slug']+'-source.jpg');photo.thumbnail((1024,1024));photo.convert('RGB').save(crop)
    cls='prop.decor.frame' if 'painting' in name else 'prop.furniture.mirror' if 'mirror' in name else 'prop.furniture.lamp' if 'lamp' in name else 'prop.furniture.sofa' if 'sofa' in name else 'prop.furniture.chair' if 'chair' in name else 'prop.furniture.cabinet' if 'cabinet' in name else 'prop.furniture.coffee-table' if 'coffee table' in name else 'prop.furniture.dining-table' if 'dining table' in name else 'prop.furniture.table'
    prov={'method':'room-from-photos / GLTFExporter','models':['Gemini 4 room reconstruction'],'author':'Forge room reconstruction','source':str(room),'source_room':room.parent.name,'source_group':item['group'],'source_picture':cam['image'],'date':datetime.datetime.now(ZoneInfo('Europe/Paris')).date().isoformat(),'licence':'operator-source-attested','licence_evidence':a.licence_evidence,'third_party_artwork':'painting' in name,'person':False,'frame_evidence':item['front_method'],'dimensions_evidence':'metres from reconstructed room, estimated physical accuracy; bounds measured from GLB'}
    pf=out/(item['slug']+'.json');pf.write_text(json.dumps(prov))
    cmd=[sys.executable,str(lib/'tools/ingest_photo_object.py'),str(out/(item['slug']+'.glb')),'--root',str(lib),'--photo',str(crop),'--thumbnail',str(out/(item['slug']+'.jpg')),'--provenance',str(pf),'--class',cls,'--slug',room.parent.name+'-'+item['slug'],'--name',name]
    if a.no_reuse:cmd+=['--no-reuse']
    subprocess.run(cmd,check=True)
subprocess.run([sys.executable,str(lib/'tools/build_index.py')],check=True)
