from __future__ import annotations
REQUIRED=['utterance_id','text_version','token_id','char_start','char_length','audio_asset_ref','timing_source','start_ms','end_ms','elapsed_ms','confidence','cue_id','drift_ms','sync_state','fallback_mode','review_owner']
UNKNOWN={'UNKNOWN','STALE','INCONCLUSIVE','UNVERIFIED','MISSING',''}
CONF_MIN=0.85
DRIFT_MAX=120

def evaluate(x):
    missing=[k for k in REQUIRED if k not in x]
    if missing:return {'state':'HOLD_INSUFFICIENT_EVIDENCE','reason':'MISSING_FIELDS','missing':missing}
    if x.get('baseline_oracle_reuse'):return {'state':'HOLD_BASELINE_ORACLE_REUSE','reason':'BASELINE_ORACLE_REUSE_FORBIDDEN'}
    if x.get('text_version')!=x.get('audio_text_version'):
        return {'state':'HOLD_TEXT_VERSION_MISMATCH','reason':'TOKEN_TEXT_DOES_NOT_MATCH_AUDIO_TEXT_VERSION'}
    if str(x.get('timebase','')).upper() in UNKNOWN:
        return {'state':'HOLD_TIMEBASE_UNKNOWN','reason':'TIMING_SOURCES_NOT_NORMALIZED'}
    owner=str(x.get('review_owner','')).strip().lower()
    if owner in {'','none','unassigned','missing'}:
        return {'state':'HOLD_REVIEW_OWNER','reason':'REVIEW_OWNER_REQUIRED'}
    if str(x.get('timing_source','')).upper() in UNKNOWN or str(x.get('sync_state','')).upper() in UNKNOWN:
        return {'state':'HOLD_DECISIVE_EVIDENCE_UNKNOWN','reason':'TIMING_SOURCE_OR_SYNC_STATE_UNKNOWN'}
    source=str(x.get('timing_source','')).upper()
    conf=float(x.get('confidence',0) or 0)
    drift=abs(float(x.get('drift_ms',0) or 0))
    word_source=('FORCED_ALIGNMENT' in source) or ('TTS_WORD_BOUNDARY' in source)
    boundary_supported=bool(x.get('browser_boundary_supported',False))
    word_boundary=(x.get('browser_boundary_granularity')=='WORD') and boundary_supported
    align_available=('FORCED_ALIGNMENT' in source) and bool(x.get('alignment_available',True))
    word_evidence=(align_available and conf>=CONF_MIN and drift<=DRIFT_MAX) or (word_boundary and drift<=DRIFT_MAX and bool(x.get('boundary_fields_supported',False)))
    if word_evidence:
        if x.get('segment_requires_fallback'):
            if not x.get('cue_id') or str(x.get('cue_id')).upper() in {'NONE','MISSING',''}:
                return {'state':'HOLD_FALLBACK_UNAVAILABLE','reason':'SEGMENT_FALLBACK_REQUIRED_WITHOUT_CUE'}
            return {'state':'MIXED_GRANULARITY_REVIEW_REQUIRED','reason':'SOME_SEGMENTS_WORD_ELIGIBLE_OTHERS_CUE_FALLBACK'}
        return {'state':'WORD_SYNC_REVIEW_REQUIRED','reason':'WORD_EVIDENCE_WITHIN_LOCAL_RULES'}
    # no defensible word evidence: degrade if sentence/cue evidence exists
    cue_ok=bool(x.get('cue_id')) and str(x.get('cue_id')).upper() not in {'NONE','MISSING',''}
    sentence_ok=(x.get('browser_boundary_granularity')=='SENTENCE' and boundary_supported)
    if cue_ok or sentence_ok:
        return {'state':'FALLBACK_REQUIRED_SENTENCE_OR_CUE','reason':'WORD_EVIDENCE_NOT_DEFENSIBLE_LOWER_GRANULARITY_AVAILABLE'}
    return {'state':'HOLD_FALLBACK_UNAVAILABLE','reason':'NO_DEFENSIBLE_WORD_OR_LOWER_GRANULARITY_TIMING'}

def inspect_transfer(x):
    if x.get('old_absolute_timing_reuse'):
        return {'state':'HOLD_OLD_AUDIO_TIMING_REUSE','reason':'OLD_AUDIO_OFFSETS_ARE_NOT_VALID_FOR_NEW_AUDIO_ASSET','text_version':x.get('text_version'),'audio_text_version':x.get('audio_text_version'),'changed_segment':x.get('changed_segment'),'baseline_oracle_reuse':x.get('baseline_oracle_reuse'),'old_absolute_timing_reuse':True}
    r=evaluate(x)
    return {'state':r['state'],'reason':r['reason'],'text_version':x.get('text_version'),'audio_text_version':x.get('audio_text_version'),'changed_segment':x.get('changed_segment'),'baseline_oracle_reuse':x.get('baseline_oracle_reuse'),'old_absolute_timing_reuse':x.get('old_absolute_timing_reuse',False)}
