"""Visible deterministic rules for MKN-027 synthetic structured-authoring lab."""
from __future__ import annotations

V1_ALLOWED={'heading','assessment','media','callout','text'}
V2_ALLOWED=V1_ALLOWED|{'interactive_code'}

def inspect_single(f):
    issues=[]
    if not isinstance(f,dict): return ['INPUT_INVALID']
    c=f.get('content') or {}; review=f.get('review') or {}; branches=f.get('branches') or []; conflict=f.get('conflict') or {}; migration=f.get('migration') or {}; audit=f.get('audit') or {}
    if not c.get('content_id') or not c.get('content_type'): issues.append('CONTENT_IDENTITY_MISSING')
    schema=c.get('schema_version')
    if not schema: issues.append('SCHEMA_VERSION_MISSING')
    if not c.get('canonical_revision'): issues.append('CANONICAL_REVISION_MISSING')
    if len(branches)<1: issues.append('BRANCH_EVIDENCE_MISSING')
    revs=[]
    for b in branches:
        if not b.get('author_ref') or not b.get('revision_id') or not b.get('base_revision_id'): issues.append('REVISION_IDENTITY_MISSING')
        if b.get('revision_id') in revs: issues.append('REVISION_ID_REUSED')
        revs.append(b.get('revision_id'))
        bt=set(b.get('block_types') or [])
        allowed=V1_ALLOWED if schema=='v1' else V2_ALLOWED if schema=='v2' else set()
        if allowed and not bt.issubset(allowed) and (migration.get('migration_state') in {None,'NOT_REQUIRED'}): issues.append('BLOCK_NOT_ALLOWED_BY_SCHEMA')
        alt=b.get('alt_text_state'); heading=b.get('heading_structure_state'); val=b.get('validation_state'); a11y=(b.get('accessibility_metadata') or {}).get('state')
        if alt=='MISSING_REQUIRED' and (val=='PASS' or a11y=='PASS'): issues.append('ACCESSIBILITY_VALIDATION_CONTRADICTION')
        if heading=='FAIL' and (val=='PASS' or a11y=='PASS'): issues.append('HEADING_VALIDATION_CONTRADICTION')
    # Explicit stale branch scenario: if a branch base is older than canonical head, it must be preserved as conflict rather than overwrite.
    head=conflict.get('canonical_head_when_b_arrived')
    if head:
        for b in branches:
            if b.get('branch_id')=='B' and b.get('base_revision_id')!=head:
                if conflict.get('silent_overwrite') or not conflict.get('stale_branch_preserved') or b.get('conflict_state')!='CONFLICT_PRESERVED': issues.append('STALE_BASE_OVERWRITE')
    if review.get('review_revision') and review.get('preview_revision')!=review.get('review_revision'): issues.append('PREVIEW_REVISION_MISMATCH')
    if review.get('preview_schema_version') and schema and review.get('preview_schema_version')!=schema: issues.append('PREVIEW_SCHEMA_MISMATCH')
    if review.get('publish_state') in {'PUBLISHED','PASS'}:
        if review.get('review_state')!='APPROVED': issues.append('PUBLISH_BEFORE_REVIEW_APPROVAL')
        if any(b.get('conflict_state')=='CONFLICT_PRESERVED' for b in branches): issues.append('PUBLISH_WITH_UNRESOLVED_CONFLICT')
        if any(b.get('validation_state')!='PASS' for b in branches): issues.append('PUBLISH_WITH_FAILED_VALIDATION')
    if not audit.get('audit_event_id'): issues.append('AUDIT_EVIDENCE_MISSING')
    return sorted(set(issues))

def expected_single(f):
    issues=inspect_single(f)
    if issues:
        if issues==['SCHEMA_VERSION_MISSING']: return 'INSUFFICIENT_EVIDENCE'
        return 'HOLD'
    review=f.get('review') or {}
    if review.get('review_state')=='APPROVED' and review.get('publish_state') not in {'PUBLISHED','PASS'}: return 'READY_FOR_REVIEW'
    return 'REVIEW_REQUIRED'

def inspect_transfer(f):
    issues=[]; hist=f.get('history') or []; c=f.get('candidate') or {}; comp=f.get('compatibility') or {}
    if not hist: issues.append('TRANSFER_HISTORY_MISSING')
    if not c.get('revision_id') or not c.get('base_revision_id') or not c.get('schema_version'): issues.append('TRANSFER_REVISION_IDENTITY_MISSING')
    if any(not h.get('immutable') or not h.get('preserved') for h in hist) or c.get('historical_rewrite'): issues.append('HISTORICAL_REVISION_MUTATION')
    if c.get('schema_version')!='v2': issues.append('TRANSFER_SCHEMA_V2_REQUIRED')
    if 'interactive_code' in set(c.get('block_types') or []) and 'v2' not in set(comp.get('new_block_allowed_versions') or []): issues.append('NEW_BLOCK_VERSION_RULE_MISSING')
    if not comp.get('v1_renderer_available') or not comp.get('v1_history_replayable') or not comp.get('v2_renderer_available'): issues.append('COMPATIBILITY_PATH_INCOMPLETE')
    if c.get('preview_revision')!=c.get('revision_id') or c.get('publish_revision')!=c.get('revision_id'): issues.append('TRANSFER_REVISION_MISMATCH')
    if c.get('preview_schema_version')!=c.get('schema_version') or c.get('publish_schema_version')!=c.get('schema_version'): issues.append('TRANSFER_SCHEMA_MISMATCH')
    if c.get('migration_state')!='COMPATIBILITY_PATH_DECLARED': issues.append('MIGRATION_STATE_INVALID')
    if not c.get('audit_event_id'): issues.append('TRANSFER_AUDIT_MISSING')
    return sorted(set(issues))

def expected_transfer(f):
    if inspect_transfer(f): return 'HOLD'
    return 'COMPATIBILITY_REVIEW_REQUIRED'
