#!/usr/bin/env python3
"""Offline, bit-aware original-raw analysis; floating numbers exported as strings."""
import hashlib
import json
import math
import struct
import sys
from pathlib import Path

R=Path(__file__).resolve().parent
PROPOSAL=R.parent/'c-literal-rint-proposal-20261011'
P=json.loads((PROPOSAL/'proposal.json').read_bytes())


def scalar(x):
    if x is None:
        return {'php_type':'NULL','native_double_hex':None,'float17g':None}
    return {'php_type':'double','native_double_hex':struct.pack('<d',x).hex(),'float17g':format(x,'.17g'),'byte_order':'little'}


def strip(t):
    out={k:v for k,v in t.items() if k!='value'}
    if t['php_type']=='NULL':out.update({'native_double_hex':None,'float17g':None})
    elif t['php_type']!='double':out['scalar_repr']=repr(t['value'])
    return out


def algebra(x):
    cap=1000000.0-1.0/100.0
    if x is None:
        return {k:scalar(cap if k=='cap' else None) for k in ['x','whole','scaled','lower_i','rounded_i','quantized','cap','clipped','stored_real','stored_double']}
    # IEEE floor(-0) remains -0; Python math.floor returns int, so retain its sign explicitly.
    whole=x if x==0 else float(math.floor(x))
    scaled=(x-whole)*100.0
    lower=float(math.floor(scaled))
    fraction=scaled-lower
    ri=lower if fraction<0.5 or (fraction==0.5 and int(lower)%2==0) else lower+1.0
    q=whole+ri/100.0
    clipped=-cap if q < -cap else cap if q > cap else q
    stored=struct.unpack('<f',struct.pack('<f',clipped))[0]
    return {k:scalar(v) for k,v in {'x':x,'whole':whole,'scaled':scaled,'lower_i':lower,'rounded_i':ri,'quantized':q,'cap':cap,'clipped':clipped,'stored_real':stored,'stored_double':stored}.items()}


def analyze():
    raw=(R/'private-evidence/C-run.jsonl').read_bytes()
    events=[json.loads(x) for x in raw.splitlines()]
    reads={e['data']['case_id']:e['data'] for e in events if e['event']=='read-observed-before-gate' and e['data']['role']=='literal_rint'}
    prior=json.loads((PROPOSAL/'formatter-results.json').read_bytes())['C']['result']['rows']
    formatter={x['input_hex']:x['output_hex'] for x in prior if x['case_id'].startswith(('old/','half/'))}
    out=[]
    diffs_lex={};diffs_x={}
    for case in P['cases']:
        o=P['oracles'][case['oracle_index']];d=reads[case['case_id']];ts=d['typed_rows'][0]
        lex=case['parameters'][0]['original_value'];model=algebra(None if lex is None else float(lex));on_x=algebra(ts['x']['value'])
        actual={k:strip(v) for k,v in ts.items()}
        compare=lambda m:[k for k in m if m[k]['native_double_hex']!=ts[k].get('native_double_hex')]
        ml=compare(model);mx=compare(on_x)
        for k in ml:diffs_lex[k]=diffs_lex.get(k,0)+1
        for k in mx:diffs_x[k]=diffs_x.get(k,0)+1
        exp=o['preferred_stored_oracle']['stored_promoted_double'];bits=ts['stored_double'].get('native_double_hex')
        assert bits in formatter,('unproven final formatter input',case['case_id'],bits)
        out.append({'case_id':case['case_id'],'original_binding':case['parameters'][0],
                    'preferred_B_oracle_provenance':o['preferred_stored_oracle']['kind'],
                    'preferred_B_reference':{k:o['preferred_stored_oracle'][k] for k in ['case_id','column']},
                    'B_stored_oracle':exp,'B_physical_getter_oracle':o['physical_getter'],
                    'actual_C_stages':actual,'metadata':d['column_metadata'],'serialized_bytes':d['serialized_bytes_before_bytecount_field'],
                    'source_algebra_original_lexeme':model,'source_algebra_observed_C_X':on_x,
                    'stage_bit_differences_original_lexeme':ml,'stage_bit_differences_observed_C_X':mx,
                    'stored_native_double_matches_B':bits==exp['native_double_hex'],
                    'contextual_physical_formatter_hex_from_prior_actual_PHP_oracle':formatter[bits],
                    'contextual_formatter_matches_B_getter':formatter[bits]==o['physical_getter']['native_double_hex']})
    osm=[]
    for c in out:
        if c['case_id'].startswith('OS-'):
            s=next(x for x in out if x['case_id'].startswith('S-') and x['original_binding']['original_value']==c['original_binding']['original_value'])
            osm.append({'S':s['case_id'],'OS':c['case_id'],'all13_typed_stage_strings_equal':s['actual_C_stages']==c['actual_C_stages']})
    mismatches=[{'case_id':x['case_id'],'original_lexeme':x['original_binding']['original_value'],
                 'C_X':x['actual_C_stages']['x'],'B_stored':x['B_stored_oracle'],'C_stored':x['actual_C_stages']['stored_double'],
                 'C_scaled':x['actual_C_stages']['scaled'],'C_half_side':x['actual_C_stages']['half_side'],
                 'C_rounded_i':x['actual_C_stages']['rounded_i'],'provenance':x['preferred_B_oracle_provenance']}
                for x in out if not x['stored_native_double_matches_B']]
    return {'status':'ACTUAL_C_LITERAL_READONLY_PARTIAL_MATCH_NOT_FIX',
            'approved_proposal_sha256':hashlib.sha256((PROPOSAL/'proposal.json').read_bytes()).hexdigest(),
            'raw_sha256':hashlib.sha256(raw).hexdigest(),'cases':out,
            'summary':{'cases':47,'distinct_lexemes':43,'stored_B_bit_matches':sum(x['stored_native_double_matches_B'] for x in out),
                       'contextual_getter_B_bit_matches':sum(x['contextual_formatter_matches_B_getter'] for x in out),
                       'stage_differences_vs_source_algebra_original_lexeme':diffs_lex,
                       'stage_differences_vs_source_algebra_observed_C_X':diffs_x,
                       'S_OS_sentinels':osm,'mismatches':mismatches,
                       'C_REAL_vs_DOUBLE_bits_equal_all':all(x['actual_C_stages']['stored_real']['native_double_hex']==x['actual_C_stages']['stored_double']['native_double_hex'] for x in out),
                       'C_warning_status':'unknown','SQL_errors':len([e for e in events if e['event']=='read-or-prepare-failure'])},
            'limitations':['No business HTTP or ORM/schema change; source algebra is offline model.',
                           'Old preferred storage oracle is explicitly decimal30 parsed-back double, not physical dump.',
                           'C observed X differs from correctly-rounded Python/binary64 source model on some lexemes; this is an entry CAST/driver/engine observation, not an independently isolated parser implementation claim.',
                           'Source algebra on already-observed C X localizes stages but does not replace original string input or prove MySQL process FENV.',
                           'Contextual formatter results reuse the prior actual PHP73 input-bit→output-bit map, not a new database getter/ORM result.',
                           'Prior sealed object0 remains the only catalog evidence; this exact SELECT-only runner created no objects and did not query catalog again.']}


if __name__=='__main__':
    out=analyze()
    if len(sys.argv)>1 and sys.argv[1]=='--header':out={k:v for k,v in out.items() if k!='cases'}
    elif len(sys.argv)>1 and sys.argv[1]=='--cases':out=out['cases'][int(sys.argv[2]):int(sys.argv[3])]
    print(json.dumps(out,ensure_ascii=False,separators=(',',':')))
