"""Input for the repaint engine from a finished space job (16 Oct 2026): the raw Blender GLB with material extensions removed
(prepare.py refuses clearcoat/transmission/volume/ior/specular; it bakes colour, so they do not matter for the paint) and a
guide written from the job (no model call): what each kind of surface should look like and what must not appear.
usage: python3 tools/space/paint_prep.py <job_dir> <out.glb> <out.guide.json>"""
import json, os, struct, sys

KEEP = {'KHR_materials_emissive_strength', 'KHR_texture_transform'}
COMPRESSED = {'KHR_draco_mesh_compression', 'EXT_meshopt_compression', 'KHR_texture_basisu', 'EXT_texture_webp'}


def read(p):
    b = open(p, 'rb').read()
    n = struct.unpack('<I', b[12:16])[0]
    doc = json.loads(b[20:20 + n])
    rest = b[20 + n:]
    binc = rest[8:8 + struct.unpack('<I', rest[0:4])[0]] if rest else b''
    return doc, binc


def write(p, doc, binc):
    j = json.dumps(doc, separators=(',', ':')).encode()
    j += b' ' * ((4 - len(j) % 4) % 4)
    binc += b'\0' * ((4 - len(binc) % 4) % 4)
    out = struct.pack('<III', 0x46546C67, 2, 12 + 8 + len(j) + (8 + len(binc) if binc else 0))
    out += struct.pack('<II', len(j), 0x4E4F534A) + j
    if binc: out += struct.pack('<II', len(binc), 0x004E4942) + binc
    open(p, 'wb').write(out)


def main(jd, out_glb, out_guide):
    j = json.load(open(f'{jd}/job.json'))
    raw = f"{jd}/{j['slug']}-raw.glb"
    if not os.path.exists(raw): raw = f"{jd}/{j['slug']}.glb"
    doc, binc = read(raw)
    used = set(doc.get('extensionsUsed', []))
    if used & {'KHR_draco_mesh_compression', 'EXT_meshopt_compression'}:   # some authors export their raw file with Draco
        import subprocess, tempfile
        dec = tempfile.mktemp(suffix='.glb', prefix='space-dec-')
        root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
        subprocess.run(['node', f'{root}/tools/pack/decode_glb.mjs', raw, dec], check=True, capture_output=True)
        doc, binc = read(dec); os.remove(dec); used = set(doc.get('extensionsUsed', []))
    if used & COMPRESSED:
        sys.exit(f'{raw} is compressed ({sorted(used & COMPRESSED)}): need the raw Blender export')
    for m in doc.get('materials', []):
        ext = {k: v for k, v in (m.get('extensions') or {}).items() if k in KEEP}
        if ext: m['extensions'] = ext
        else: m.pop('extensions', None)
    for n in doc.get('nodes', []):
        (n.get('extensions') or {}).pop('KHR_lights_punctual', None)
        if 'extensions' in n and not n['extensions']: n.pop('extensions')
    doc.pop('extensions', None)
    left = set()
    for m in doc.get('materials', []):
        left |= set(m.get('extensions', {}))
        for v in m.values():
            if isinstance(v, dict) and 'extensions' in v: left |= set(v['extensions'])
        for v in (m.get('pbrMetallicRoughness') or {}).values():
            if isinstance(v, dict) and 'extensions' in v: left |= set(v['extensions'])
    doc['extensionsUsed'] = sorted(left)
    if not doc['extensionsUsed']: doc.pop('extensionsUsed')
    doc.pop('extensionsRequired', None)
    write(out_glb, doc, binc)
    mats = sorted({m.get('name', '') for m in doc.get('materials', [])} - {''})
    alien = 'alien' in j.get('tags', [])
    look = [f"This is {j['title'].lower()}: keep its design exactly and only make every surface look like its real material, in the same colours.",
            ('The shell keeps its exact colour and gains the real look of its living material: fine natural grain, growth lines, glossy '
             'wet highlights on the plates, subtle colour depth.') if alien and 'organic' in j.get('tags', []) else
            ('The painted hull plates keep their exact colour and gain crisp fine panel seams, small access hatches, rivet and fastener '
             'lines and light wear on the edges.'),
            'Bare metal parts look like real brushed titanium and machined steel.',
            'Engine nozzles are dark heat-scorched metal with a bronze and blue heat tint toward the exit.',
            'Glass and canopies stay dark tinted glass; glowing parts keep the same glow colour and nothing new glows.']
    if j['family'] == 'speeders':
        look[3] = 'Exhausts and lift pads are dark heat-scorched metal; rubber, leather and plastic parts look like real rubber, leather and plastic.'
    avoid = ['Do not add text, numbers, insignia, emblems, flags, logos or new stripes.',
             'Do not add new parts, windows, weapons, lights or openings.',
             'No heavy dirt, rust or battle damage: only light wear.',
             'Keep the backdrop plain: no stars, planets, sky or scenery.']
    json.dump({'subject': f"{j['title']} ({j['name'][:160]})", 'kind': 'prop', 'style': 'realistic', 'skip': False, 'skip_reason': '',
               'look': look[:6], 'avoid': avoid[:4], 'spots': [], 'materials': mats}, open(out_guide, 'w'), indent=1)
    print(out_glb, len(doc.get('materials', [])), 'materials', os.path.getsize(out_glb), 'bytes')


if __name__ == '__main__':
    main(*sys.argv[1:4])
