from __future__ import annotations

def _metrics(x):
    ws=x.get('work_packages') or []
    total=sum(float(w.get('planned_weight') or 0) for w in ws)
    earned=sum(float(w.get('earned_weight') or 0) for w in ws)
    complete=sum(1 for w in ws if w.get('actual_completion_evidence'))
    raw=100.0*complete/len(ws) if ws else 0.0
    weighted=100.0*earned/total if total else 0.0
    critical_open=[w['work_package_id'] for w in ws if w.get('critical_milestone') and not w.get('actual_completion_evidence')]
    return {'planned_total':round(total,3),'earned_total':round(earned,3),'raw_task_count_progress':round(raw,3),'weighted_progress':round(weighted,3),'critical_open':critical_open}

def evaluate(x):
    if x.get('baseline_oracle_reuse') or not x.get('recomputed_from_changed_evidence',True): return {'state':'HOLD_BASELINE_ORACLE_REUSE'}
    if not x.get('project_id') or not x.get('baseline_id') or not x.get('scope_ref') or not x.get('baseline_revision'): return {'state':'HOLD_SCOPE_IDENTITY'}
    if not x.get('weight_basis') or not x.get('weights_locked_before_progress'): return {'state':'HOLD_WEIGHT_BASIS'}
    if x.get('weights_changed_after_progress'): return {'state':'HOLD_BASELINE_INTEGRITY'}
    ws=x.get('work_packages') or []
    if not ws: return {'state':'HOLD_INSUFFICIENT_EVIDENCE'}
    total=sum(float(w.get('planned_weight') or 0) for w in ws)
    if abs(total-100.0)>1e-6: return {'state':'HOLD_BASELINE_INTEGRITY'}
    if not any(w.get('critical_milestone') for w in ws): return {'state':'HOLD_MILESTONE_EVIDENCE'}
    for w in ws:
        pw=w.get('planned_weight'); ew=w.get('earned_weight')
        if not isinstance(pw,(int,float)) or pw<0 or not isinstance(ew,(int,float)) or ew<0 or ew>pw: return {'state':'HOLD_EARNED_WEIGHT'}
        if ew>0 and not w.get('actual_completion_evidence'): return {'state':'HOLD_COMPLETION_EVIDENCE'}
        if w.get('schedule_variance') in (None,''): return {'state':'HOLD_VARIANCE_EVIDENCE'}
    if x.get('schedule_variance') in (None,''): return {'state':'HOLD_VARIANCE_EVIDENCE'}
    cr=x.get('change_request_ref')
    if cr:
        if cr.get('status')!='APPROVED': return {'state':'HOLD_CHANGE_CONTROL'}
        if cr.get('scope_changed'):
            if int(x.get('baseline_revision',0))<=int(cr.get('from_revision',0)): return {'state':'REBASELINE_REQUIRED'}
            h=x.get('history_prior_baseline') or {}
            if not h.get('frozen') or h.get('baseline_revision')!=cr.get('from_revision'): return {'state':'REBASELINE_REQUIRED'}
    if not x.get('risk_state') or not x.get('forecast_state'): return {'state':'HOLD_FORECAST_STATE'}
    if not x.get('control_owner'): return {'state':'HOLD_CONTROL_OWNER'}
    if not x.get('provenance'): return {'state':'HOLD_AUDIT_PROVENANCE'}
    m=_metrics(x); alerts=[]
    if m['raw_task_count_progress']-m['weighted_progress']>=20: alerts.append('TASK_COUNT_OVERSTATES_PROGRESS')
    if m['critical_open']: alerts.append('CRITICAL_MILESTONE_OPEN')
    return {'state':'CONTROL_TOWER_REVIEW_REQUIRED',**m,'alerts':alerts,'baseline_revision':x['baseline_revision'],'baseline_id':x['baseline_id']}

def inspect_transfer(x):
    out=[]
    if x.get('baseline_oracle_reuse'): out.append('BASELINE_ORACLE_REUSE_FORBIDDEN')
    if not x.get('recomputed_from_changed_evidence'): out.append('TRANSFER_MUST_RECOMPUTE')
    h=x.get('history_prior_baseline') or {}
    if x.get('baseline_revision',1)>1 and not h.get('frozen'): out.append('PRIOR_BASELINE_HISTORY_REQUIRED')
    return out
