"""A bounded, whole-memory gzip JSON-to-CSV recipe, Python 3.12+.
Accepts an array of objects with exactly id and note, both UTF-8 strings.
python gzip-to-csv.py gzip-input.json.gz new-output.csv --limit-bytes 256
"""
import argparse,csv,gzip,io,json,sys,zlib
from pathlib import Path

MAX_COMPRESSED=2000000
MAX_EXPANDED=2000000

def unique(pairs):
    out={}
    for key,value in pairs:
        if key in out:raise ValueError('duplicate JSON key: '+repr(key))
        out[key]=value
    return out

def constant(value):raise ValueError('nonstandard JSON constant: '+value)

def convert(source,target,limit):
    if not 1<=limit<=MAX_EXPANDED:raise ValueError('limit must be 1..2000000 bytes')
    with Path(source).open('rb') as f:compressed=f.read(MAX_COMPRESSED+1)
    if len(compressed)>MAX_COMPRESSED:raise ValueError('compressed input exceeds 2000000-byte policy')
    with gzip.GzipFile(fileobj=io.BytesIO(compressed),mode='rb') as f:
        raw=f.read(limit+1)
    if len(raw)>limit:raise ValueError('decompressed input exceeds '+str(limit)+'-byte policy')
    rows=json.loads(raw.decode('utf-8',errors='strict'),object_pairs_hook=unique,parse_constant=constant)
    if not isinstance(rows,list):raise ValueError('expected a JSON array')
    for row in rows:
        if not isinstance(row,dict) or set(row)!={'id','note'}:raise ValueError('expected id/note only')
        if not all(isinstance(row[k],str) for k in ('id','note')):raise ValueError('string fields required')
        for value in row.values():value.encode('utf-8',errors='strict')
    # Open output only after decompression, decoding and schema validation.
    with Path(target).open('x',encoding='utf-8',newline='') as f:
        w=csv.writer(f);w.writerow(['id','note']);w.writerows([r['id'],r['note']] for r in rows)
    return {'compressed_bytes':len(compressed),'decompressed_bytes':len(raw),'records':len(rows),'limit_bytes':limit}

if __name__=='__main__':
    p=argparse.ArgumentParser(description=__doc__)
    p.add_argument('source');p.add_argument('target');p.add_argument('--limit-bytes',type=int,default=MAX_EXPANDED)
    a=p.parse_args()
    try:print(json.dumps(convert(a.source,a.target,a.limit_bytes)))
    except (ValueError,OSError,EOFError,zlib.error,RecursionError) as e:
        print(type(e).__name__+': '+str(e),file=sys.stderr);sys.exit(1)
