"""Original offline alpha conversion fixture. No SAM inference or network access.
Run with Python 3, numpy==2.3.5 and Pillow==12.3.0:
    python alpha-fixture.py --output ./alpha-output
The PNGs are tiny numerical fixtures, not sampled model outputs.
"""
import argparse
import hashlib
import json
import platform
from pathlib import Path
import numpy as np
from PIL import Image, __version__ as pillow_version


def cutout(rgb, mask):
    """Accept one same-resolution binary 0/1 mask; reject logits/soft mattes."""
    rgb, mask = np.asarray(rgb), np.asarray(mask)
    if rgb.ndim != 3 or rgb.shape[2] != 3 or rgb.dtype != np.uint8:
        raise ValueError('rgb must be an H x W x 3 uint8 array')
    if mask.shape != rgb.shape[:2]:
        raise ValueError('mask must be one H x W array matching rgb')
    if not np.all(np.isfinite(mask)) or not np.all((mask == 0) | (mask == 1)):
        raise ValueError('mask must contain only finite binary 0/1 values')
    alpha = mask.astype(np.uint8) * 255
    out = Image.fromarray(rgb)
    out.putalpha(Image.fromarray(alpha))
    return out


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--output', type=Path, default=Path('alpha-output'))
    args = parser.parse_args()
    args.output.mkdir(parents=True, exist_ok=True)
    # Original 6 x 4 RGB grid, deliberately easy to inspect and reproduce.
    rgb = np.array([[[20 + 30*x, 40 + 40*y, 180 - 10*x] for x in range(6)] for y in range(4)], dtype=np.uint8)
    mask = np.array([[0,0,0,0,0,0],[0,1,1,1,1,0],[0,1,1,1,1,0],[0,0,0,0,0,0]], dtype=np.float32)
    good = cutout(rgb, mask)
    bad = Image.fromarray(rgb)
    bad.putalpha(Image.fromarray(mask.astype(np.uint8)))
    paths = {}
    for name, im in [('source',Image.fromarray(rgb)),('correct',good),('unscaled',bad)]:
        p = args.output / (name+'.png'); im.save(p)
        paths[name] = {'sha256': hashlib.sha256(p.read_bytes()).hexdigest(), 'bytes':p.stat().st_size}
    a = np.array(Image.open(args.output/'correct.png'))
    b = np.array(Image.open(args.output/'unscaled.png'))
    assert np.array_equal(a[:,:,:3],rgb)
    assert set(np.unique(a[:,:,3])) == {0,255}
    assert set(np.unique(b[:,:,3])) == {0,1}
    assert int((a[:,:,3] == 255).sum()) == 8
    assert int((a[:,:,3] == 0).sum()) == 16
    rejection = {}
    for name, invalid in [('wrong_size',mask[:2]),('candidate_stack',mask[None]),('soft_value',mask.copy()),('logits',mask.copy()),('nan',mask.copy())]:
        if name == 'soft_value': invalid[1,1] = 0.5
        if name == 'logits': invalid[1,1] = -2.0
        if name == 'nan': invalid[1,1] = np.nan
        try: cutout(rgb,invalid)
        except ValueError as e: rejection[name] = str(e)
        else: raise AssertionError(name+' was not rejected')
    all_zero = np.asarray(cutout(rgb,np.zeros_like(mask)))[:,:,3]
    all_one = np.asarray(cutout(rgb,np.ones_like(mask)))[:,:,3]
    assert np.all(all_zero==0) and np.all(all_one==255)
    selected = {'xy':[1,1],'source_rgb':rgb[1,1].tolist(),'correct_rgba':a[1,1].tolist(),'unscaled_rgba':b[1,1].tolist()}
    report = {'scope':'Synthetic 6x4 alpha-export fixture only. No SAM inference, model evaluation, matting, renderer, or game-engine test.',
      'versions':{'python':platform.python_version(),'numpy':np.__version__,'pillow':pillow_version},
      'source_rgb':rgb.tolist(),'binary_mask':mask.tolist(),'selected_pixel':selected,
      'correct_alpha_values':np.unique(a[:,:,3]).tolist(),'unscaled_alpha_values':np.unique(b[:,:,3]).tolist(),
      'opaque_pixels':8,'transparent_pixels':16,'rgb_preserved':True,'empty_and_full_mask_checks':True,
      'invalid_inputs_rejected':rejection,'png_files':paths}
    (args.output/'alpha-results.json').write_text(json.dumps(report,indent=2)+'\n')
    print(json.dumps({k:report[k] for k in ['scope','versions','selected_pixel','opaque_pixels','transparent_pixels','rgb_preserved','invalid_inputs_rejected']},indent=2))

if __name__ == '__main__': main()
