from datetime import datetime
REQ=['framework_id', 'competency_id', 'statement', 'taxonomy_level', 'edge_id', 'edge_type', 'prerequisite_id', 'child_id', 'strength', 'evidence_condition', 'closure_state', 'equivalent_evidence_ref', 'bridge_path', 'cycle_state', 'version', 'provenance']
ALLOWED_EDGE=['HARD_PREREQUISITE', 'RECOMMENDED', 'EQUIVALENT', 'ALIGNMENT', 'BRIDGE']
MISSING={'','UNKNOWN','MISSING',None}
SUCCESS={'ROUTE_AVAILABLE_HARD_CLOSED','ROUTE_AVAILABLE_EQUIVALENT','ROUTE_AVAILABLE_RECOMMENDATION_OPEN','GRAPH_REVIEW_READY'}

def _cycle(edges):
    g={}
    for e in edges or []:
        a=e.get('prerequisite_id'); b=e.get('child_id')
        if not a or not b: continue
        g.setdefault(a,[]).append(b)
    seen=set(); stack=set()
    def dfs(n):
        if n in stack:return True
        if n in seen:return False
        seen.add(n);stack.add(n)
        for m in g.get(n,[]):
            if dfs(m):return True
        stack.remove(n);return False
    return any(dfs(n) for n in list(g))

def evaluate(x):
    # structural required values
    for k in REQ:
        if x.get(k) in MISSING:
            return 'HOLD_REQUIRED_FIELD_MISSING'
    if x['prerequisite_id']==x['child_id']:
        return 'HOLD_UNJUSTIFIED_CYCLE'
    if x.get('edge_origin')=='DISPLAY_ORDER_INFERENCE':
        return 'HOLD_DISPLAY_ORDER_NONAUTHORITATIVE'
    if x['edge_type'] not in ALLOWED_EDGE:
        return 'HOLD_EDGE_TYPE_UNKNOWN'
    if x.get('identity_collision'):
        return 'HOLD_IDENTITY_COLLISION'
    if x.get('identity_merged'):
        return 'HOLD_IDENTITY_MERGE'
    if x.get('semantic_change_without_version_bump'):
        return 'HOLD_VERSION_NOT_BUMPED'
    if _cycle(x.get('graph_edges',[])) or x.get('cycle_state')=='CYCLIC_UNJUSTIFIED':
        return 'HOLD_UNJUSTIFIED_CYCLE'
    prev=x.get('previous_edge')
    if prev and (prev.get('prerequisite_id')!=x['prerequisite_id'] or prev.get('child_id')!=x['child_id'] or prev.get('edge_type')!=x['edge_type']):
        if not x.get('recomputed_after_edge_change'):
            return 'HOLD_EDGE_RECOMPUTE_REQUIRED'
    if x.get('transfer_case') and x.get('baseline_closure_reused'):
        return 'HOLD_TRANSFER_ORACLE_REUSE'
    # edge coherence and closure
    et=x['edge_type']; strength=x['strength']
    if et=='HARD_PREREQUISITE' and strength!='HARD':
        return 'HOLD_EDGE_STRENGTH_MISMATCH'
    if et=='RECOMMENDED' and strength not in {'RECOMMENDED','SOFT'}:
        return 'HOLD_EDGE_STRENGTH_MISMATCH'
    if et=='HARD_PREREQUISITE':
        if x.get('evidence_kind') in {'ACTIVITY_ONLY','COMPLETION_ONLY','NONE',None}:
            return 'BLOCKED_HARD_PREREQUISITE'
        if not x.get('evidence_condition_met'):
            return 'BLOCKED_HARD_PREREQUISITE'
        if x['closure_state']!='ROUTE_AVAILABLE_HARD_CLOSED':
            return 'HOLD_CLOSURE_STATE_MISMATCH'
        return 'ROUTE_AVAILABLE_HARD_CLOSED'
    if et=='RECOMMENDED':
        eq=x.get('equivalent_evidence_ref')
        if eq not in MISSING and eq!='NONE':
            if not x.get('equivalence_verified') or x.get('equivalence_basis')=='TEXT_SIMILARITY_ONLY':
                return 'HOLD_UNVERIFIED_EQUIVALENCE'
            if x.get('identity_merged'):
                return 'HOLD_IDENTITY_MERGE'
            if x['closure_state']!='ROUTE_AVAILABLE_EQUIVALENT':
                return 'HOLD_CLOSURE_STATE_MISMATCH'
            return 'ROUTE_AVAILABLE_EQUIVALENT'
        if x['closure_state']!='ROUTE_AVAILABLE_RECOMMENDATION_OPEN':
            return 'HOLD_CLOSURE_STATE_MISMATCH'
        return 'ROUTE_AVAILABLE_RECOMMENDATION_OPEN'
    # alignment/equivalence/bridge records are reviewable relationships, not prerequisite closure
    if et in {'EQUIVALENT','ALIGNMENT'}:
        if x.get('equivalence_basis')=='TEXT_SIMILARITY_ONLY' or not x.get('equivalence_verified'):
            return 'HOLD_UNVERIFIED_EQUIVALENCE'
        return 'GRAPH_REVIEW_READY'
    if et=='BRIDGE':
        if x.get('bridge_path') in MISSING or x.get('bridge_path')=='NONE':
            return 'HOLD_BRIDGE_PATH_MISSING'
        return 'GRAPH_REVIEW_READY'
    return 'HOLD_REVIEW_REQUIRED'
