#!/usr/bin/env python3
"""Fetch chosen Poly Haven CC0 assets. Python 3.10+, no third-party dependencies."""
import argparse,hashlib,json,pathlib,shutil,tempfile,urllib.request,urllib.parse
USER_AGENT='WEBCRAFT-Library/2.0 (selected asset downloader)'
def read_url(url):
 parsed=urllib.parse.urlparse(url)
 if parsed.scheme!='https' or parsed.hostname not in {'api.polyhaven.com','dl.polyhaven.org'}:raise ValueError('Unexpected asset host')
 with urllib.request.urlopen(urllib.request.Request(url,headers={'User-Agent':USER_AGENT}),timeout=90) as response:return response.read()
def choose(data,kind,resolution):
 keys={'models':['gltf'],'environments':['hdri'],'pbr':['Diffuse','diff','nor_gl','Rough','rough','Metal','metal','AO','Displacement','disp']}[kind]
 results=[]
 for key in keys:
  group=data.get(key,{})
  if resolution not in group:continue
  version=group[resolution]
  for ext in ['gltf','hdr','jpg','png','exr']:
   if ext in version:
    f=version[ext];name=pathlib.PurePosixPath(urllib.parse.urlparse(f['url']).path).name
    results.append((name,f));results.extend(f.get('include',{}).items());break
 if not results:raise ValueError('No compatible files at requested resolution; inspect the provider page')
 return dict(results)
def main():
 parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--ids',required=True,help='Comma-separated exact IDs from assets/registry.json');parser.add_argument('--resolution',choices=['1k','2k','4k'],default='1k');parser.add_argument('--dest',type=pathlib.Path,default=pathlib.Path('downloaded-assets'));parser.add_argument('--registry',type=pathlib.Path);args=parser.parse_args()
 script=pathlib.Path(__file__).resolve();candidates=[args.registry] if args.registry else [script.parent.parent/'assets/registry.json',script.parent.parent/'lib/data/assets.json']
 registry=next((p for p in candidates if p and p.is_file()),None)
 if not registry:parser.error('Pass --registry /path/to/assets/registry.json')
 data=json.loads(registry.read_text());items=data['items'] if isinstance(data,dict) else data;assets={a['id']:a for a in items}
 args.dest.mkdir(parents=True,exist_ok=True)
 for id in args.ids.split(','):
  if id not in assets:parser.error('Unknown asset ID: '+id)
  a=assets[id];target=args.dest/id
  if target.exists():raise FileExistsError('Will not overwrite '+str(target))
  files=json.loads(read_url('https://api.polyhaven.com/files/'+urllib.parse.quote(id)));selected=choose(files,a['kind'],args.resolution)
  stage=pathlib.Path(tempfile.mkdtemp(prefix='.webcraft-',dir=args.dest))
  try:
   for name,f in selected.items():
    relative=pathlib.PurePosixPath(name)
    if relative.is_absolute() or '..' in relative.parts or '\\' in name:raise ValueError('Unsafe asset path')
    body=read_url(f['url'])
    if f.get('md5') and hashlib.md5(body).hexdigest()!=f['md5']:raise ValueError('Checksum mismatch: '+name)
    out=stage.joinpath(*relative.parts);out.parent.mkdir(parents=True,exist_ok=True);out.write_bytes(body)
   (stage/'SOURCE.json').write_text(json.dumps({'id':id,'name':a['name'],'author':a['author'],'source':'https://polyhaven.com/a/'+id,'license':'CC0-1.0','resolution':args.resolution,'files':selected},indent=2))
   if target.exists():raise FileExistsError('Destination appeared during download')
   stage.rename(target);print('Saved',id,'→',target)
  finally:
   if stage.exists():shutil.rmtree(stage)
if __name__=='__main__':main()
