"""Read-only, public diagram QA. Run with --prepare or --run-api.

The model proposes semantic judgments. It cannot certify historical fidelity,
copyright, accessibility or document completeness. The original SVG is unchanged.
API credentials remain in process memory and are never included in saved evidence.
"""
from pathlib import Path
import argparse, datetime as dt, hashlib, importlib.metadata, json, re, subprocess, sys, time, urllib.request
import cairosvg
from PIL import Image as PILImage
import httpx2
from typesafe_sdk import TypeSafeClient, Image, Noul, Box, Choice, Score

ROOT=Path(__file__).resolve().parents[2]
OUT=Path(__file__).resolve().parent
EVIDENCE=OUT.parent/'evidence/typesafe'
SOURCE=ROOT/'aaig_beta_20261017/out/v3.0.1/Tonguthaisri_CBR_MEng_svg.svg'
SKILL=ROOT/'.agents/skills/typesafe-ai/SKILL.md'
ORIGINAL_GATE=ROOT/'aaig_beta_20261017/typesafe_images'
sys.path.insert(0,str(ORIGINAL_GATE))
from typesafe_latest import resolve, api_key

HEADER={'TypeSafe-Danger-Zone-Images':'true'}
def save(path,obj): path.write_text(json.dumps(obj,ensure_ascii=False,indent=2)+'\n')
def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest()

def strict_gate(key):
    """Augments the owner's original gate with mandatory Codex skill/alias checks."""
    record={'state':'CHECKING','errors':[]}
    try:
        head=subprocess.run(['git','ls-remote','https://github.com/typesafe-ai/skills.git','HEAD'],capture_output=True,text=True,check=True,timeout=40).stdout.split()[0]
        upstream=urllib.request.urlopen(f'https://raw.githubusercontent.com/typesafe-ai/skills/{head}/skills/typesafe-ai/SKILL.md',timeout=40).read()
        record['codex_skill']={'upstream_head':head,'installed_sha256':sha(SKILL),'upstream_sha256':hashlib.sha256(upstream).hexdigest(),'exact_match':SKILL.read_bytes()==upstream}
        if not record['codex_skill']['exact_match']: raise RuntimeError('Codex TypeSafe skill differs from upstream HEAD')
        # Original owner gate: resolves SDK and actual response model IDs live.
        version=resolve(key=key,allow_unverified=False,need_box=True)
        record['original_gate']=version
        for alias in ('jev-preview','jev-latest'):
            model=version['aliases_resolved'].get(alias)
            if not model or not re.fullmatch(r'jev-\d+\.\d+\.\d+',model):
                raise RuntimeError('Both required aliases must resolve to versioned IDs')
        if not version['python_sdk']['current']: raise RuntimeError('SDK is not latest on PyPI')
        record['pinned_model']=version['pinned_model'];record['state']='VERIFIED'
    except Exception as e:
        record['state']='FAILED'; record['errors'].append({'type':type(e).__name__,'message':str(e)})
        save(EVIDENCE/'currentness_gate.json',record)
        raise
    save(EVIDENCE/'currentness_gate.json',record)
    return record['pinned_model']

def prepare():
    dest=OUT/'diagram.png'
    fixed=OUT/'diagram.xml-safe.svg'
    fixed.write_text(re.sub(r'&(?!#\d+;|#x[0-9A-Fa-f]+;|amp;|lt;|gt;|quot;|apos;)', '&amp;', SOURCE.read_text()))
    cairosvg.svg2png(url=str(fixed),write_to=str(dest),output_width=1050,output_height=900)
    save(EVIDENCE/'input_manifest.json',{'class':'user-supplied public-facing reconstruction diagram','source_sha256':sha(SOURCE),'derived_svg_sha256':sha(fixed),'image_sha256':sha(dest),'image_size':[1050,900],'original_unchanged':True,'repair':'Escaped bare ampersands as XML entities in derived SVG only.','scope':'modern explanatory SVG; not original scan'})
    return dest

def questions():
    return {
        'cycle_present':Noul(instructions='Does `diagram` contain a case-based reasoning workflow cycle?'),
        'cycle_box':Box(instructions='the complete four-stage case-based reasoning workflow cycle in `diagram`, excluding the title banner and footer'),
        'knowledge_present':Noul(instructions='Does `diagram` contain a central knowledge repository or store of prior cases?'),
        'knowledge_box':Box(instructions='the central knowledge repository card containing past cases and general knowledge in `diagram`'),
        'signature_present':Noul(instructions='Does `diagram` contain a handwritten personal signature?'),
        'signature_box':Box(instructions='the handwritten personal signature in `diagram`'),
        'four_rs_visible':Noul(instructions='Are all four workflow stage labels Retrieve, Reuse, Revise, and Retain visibly represented in `diagram`?'),
        'extension_visible':Noul(instructions='Does `diagram` visibly identify Watson extensions, including Review and Refine?'),
        'clipped_text_present':Noul(instructions='Does any text in `diagram` continue beyond the visible image boundary, so words are cut off?'),
        'clipped_text_box':Box(instructions='the footer text area in `diagram` with lines cut off by the right image boundary'),
        'overlapping_text_present':Noul(instructions='Does `diagram` contain printed labels that overlap one another enough to impair reading, specifically within the Watson retention filter box?'),
        'presentation_kind':Choice(instructions='Which description best fits the visual form of `diagram`?',criteria={'modern_explanatory':'A modern digital explanatory diagram with colored shapes and added context.','archival_scan':'An unaltered black-and-white scan of an old printed page.','unknown':'Neither of these descriptions fits confidently.'}),
        'label_readability':Score(instructions='How readable are the principal stage labels and central repository label in `diagram` at the provided resolution?',criteria=['The principal labels are illegible or substantially clipped.','Some principal labels are difficult to read.','The principal labels are clear and legible.']),
        'exact_restoration_supported':Choice(instructions='How does the supplied `editorial_evidence` relate to `candidate_claim`?',criteria={'supports':'Evidence establishes the claim.','contradicts':'Evidence conflicts with the claim.','insufficient':'Evidence does not establish whether the claim is true.'}),
    }

def main():
    p=argparse.ArgumentParser();p.add_argument('--prepare',action='store_true');p.add_argument('--run-api',action='store_true');a=p.parse_args()
    if a.prepare:
        prepare();print('IMAGE_PREPARED');return
    if not a.run_api: p.error('Specify --prepare or --run-api')
    image_path=OUT/'diagram.png'
    if not image_path.exists(): raise RuntimeError('Prepare and inspect diagram.png first')
    size=PILImage.open(image_path).size
    if not all(280<=x<=1568 for x in size): raise RuntimeError('Image dimension out of documented range')
    if (EVIDENCE/'image_response.json').exists(): raise RuntimeError('Existing result preserved; choose a new run directory for another call')
    key=api_key();model=strict_gate(key)
    q=questions();state={'diagram':Image(image_path),'editorial_evidence':'The SVG is a modern redraw titled Aamodt & Plaza Model + Watson’s Extension. It adds Review and Refine explanations and colored explanatory labels. No complete visual collation with the 2003 page has been performed.','candidate_claim':'This diagram is a pixel-faithful reproduction of the original 2003 thesis figure.'}
    save(EVIDENCE/'image_questions.json',{k:v.model_dump(mode='json') for k,v in q.items()})
    save(EVIDENCE/'text_state.json',{k:v for k,v in state.items() if k!='diagram'})
    events=[]
    def hook(r):events.append({'method':r.method,'url':str(r.url),'image_header':r.headers.get('TypeSafe-Danger-Zone-Images'),'content_type':r.headers.get('Content-Type','').split(';')[0]})
    start=time.monotonic();started=dt.datetime.now(dt.timezone.utc).isoformat()
    with httpx2.Client(event_hooks={'request':[hook]}) as hc, TypeSafeClient(api_key=key,base_url='https://api.typesafe.ai',model=model,http_client=hc,timeout=90.0) as c:
        result=c.system_one(state=state,questions=q,extra_headers=HEADER).model_dump(mode='json')
    save(EVIDENCE/'image_response.json',result)
    receipt={'state':'RETURNED','started_at':started,'elapsed_seconds':round(time.monotonic()-start,3),'requested_model':model,'returned_model':result['model'],'model_matches':model==result['model'],'sdk_version':importlib.metadata.version('typesafe-sdk'),'source_sha256':sha(SOURCE),'image_sha256':sha(image_path),'events':events,'long_context_opt_in':False,'query_sharing_opt_in':False,'credential_storage':'macOS Keychain; value never saved'}
    save(EVIDENCE/'image_receipt.json',receipt)
    if not receipt['model_matches']:raise RuntimeError('Returned model differs from pin; hold result')
    routing={}
    for target in ('cycle','knowledge','signature','clipped_text'):
        presence=result['answers'][target+'_present']['noul'];b=result['answers'][target+'_box'];e=b['box']
        assert 0<=e['left']<e['right']<=1 and 0<=e['top']<e['bottom']<=1
        xyxy=[round(e['left']*size[0]),round(e['top']*size[1]),round(e['right']*size[0]),round(e['bottom']*size[1])]
        action='SUPPRESS_BOX' if presence<=.2 else ('REVIEW_PROPOSED_BOX' if presence>=.8 else 'REVIEW_PRESENCE_AND_BOX')
        routing[target]={'presence':presence,'box_confidence':b['confidence'],'pixel_xyxy':xyxy,'action':action}
    save(EVIDENCE/'routing.json',{'policy':'Illustrative 0.2/0.8 presence thresholds; not calibrated on the thesis. All proposed crops require visual review.','targets':routing})
    print(json.dumps({'receipt':receipt,'routing':routing,'other_answers':{k:v for k,v in result['answers'].items() if not k.endswith(('_present','_box'))}},indent=2))
if __name__=='__main__':main()
