<?php
$root=dirname(__DIR__);
$seed=json_decode(file_get_contents($root.'/content/seed.json'),true);
$checks=[];$problems=[];

$books=[];$spreads=[];
foreach($seed['books']??[] as $b)$books[$b['book_id']]=$b;
foreach($seed['spreads']??[] as $s)$spreads[$s['book_id']][]=$s;
$checks['five_books']=count($books)===5;
$checks['twenty_spreads']=count($seed['spreads']??[])===20;

// Story + art invariants.
$hashes=[];$missing=[];
foreach($seed['spreads']??[] as $s){
    if(isset($s['whisper'])||isset($s['symbols'])||isset($s['portal']))$problems[]=$s['spread_id'].' leaks obsolete public metadata';
    if(empty($s['composition']))$problems[]=$s['spread_id'].' lacks composition metadata';
    $text=strtolower(implode(' ',$s['voice']??[]));
    foreach(['placeholder','lorem ipsum','what this means','the lesson is','you should understand','it is important to remember'] as $bad)
        if(strpos($text,$bad)!==false)$problems[]=$s['spread_id']." contains forbidden phrase: $bad";
    $file=$root.'/'.$s['art'];
    if(!is_file($file)){$missing[]=$s['spread_id'];continue;}
    $h=hash_file('sha256',$file);
    if(isset($hashes[$h]))$problems[]=$s['spread_id'].' duplicates artwork from '.$hashes[$h];
    else $hashes[$h]=$s['spread_id'];
}
$checks['story_surface_clean']=!array_filter($problems,fn($x)=>str_contains($x,'forbidden phrase')||str_contains($x,'obsolete'));
$checks['all_art_exists']=count($missing)===0;
$checks['twenty_unique_art_hashes']=count($hashes)===20;

// Deep architecture and new commercial constraint.
foreach(['being_kernel','pattern_library','referent_schema','world_frames','epistemic_model','lens_compiler_contract','absence_schema','demo_path_reasoning','art_briefs','atlas_layers','commerce_excerpt_contract'] as $name)
    if(!is_file($root.'/content/'.$name.'.json'))$problems[]='missing architecture contract: '.$name;
$checks['deep_architecture_present']=!array_filter($problems,fn($x)=>str_starts_with($x,'missing architecture contract'));

require $root.'/lib.php';
$checks['atlas_0014']=being_config()['atlas_version']==='ATLAS-0014';
$checks['engine_version_3']=being_public_config()['version']==='14.0';

$template=being_weave_template(0);
$checks['five_exact_excerpt_windows']=count($template)===5
 && $template[0]['book_id']==='B047' && $template[0]['start']===3 && $template[0]['end']===3
 && $template[1]['book_id']==='B083' && $template[1]['start']===2 && $template[1]['end']===2
 && $template[2]['book_id']==='B089' && $template[2]['start']===2 && $template[2]['end']===2
 && $template[3]['book_id']==='B109' && $template[3]['start']===3 && $template[3]['end']===3
 && $template[4]['book_id']==='B112' && $template[4]['start']===3 && $template[4]['end']===3;

$budget=being_validate_weave_excerpt_budget($template);
$checks['excerpt_budget_passes']=$budget['ok']??false;
foreach(array_keys($books) as $bid){
    $b=being_excerpt_budget($bid);
    if($b['total']===4 && $b['allowed']!==1)$problems[]="$bid four-spread excerpt budget should be 1, got ".$b['allowed'];
}
$checks['four_spread_books_expose_25_percent']=!array_filter($problems,fn($x)=>str_contains($x,'excerpt budget should be 1'));

// Isolated behavior state.
$cfg=being_config();$stateFile=$cfg['storage_file'];
if(is_file($stateFile))unlink($stateFile);
$reader=being_reader_id();
$q='Why did desire move from private rooms and brothels into a world where strangers watch one another online?';
$path=being_interpret($q,$reader,0);
$checks['thought_finds_page_weave_3']=($path['status']??'')==='found' && ($path['engine']??'')==='PAGE-WEAVE-3';
$j=being_start_journey($reader,$path['code']);$jid=$j['journey_id']??'';
$checks['journey_starts_house3']=($j['book_id']??'')==='B047' && (int)($j['spread']??0)===3;

// Following Thread: one spread then exact next handoff.
being_visit_page($reader,'B047',3,$jid);
$a=being_follow_thread($reader,$jid);
$checks['house3_to_door2']=($a['book_id']??'')==='B083' && (int)($a['spread']??0)===2;

// Door is touched but not owned/read, and only one free spread is exposed.
$access=being_book_access($reader,'B083',$jid);
$checks['door_free_excerpt_one_spread']=($access['mode']??'')==='thread' && (int)$access['start']===2 && (int)$access['end']===2;
$libState=being_my_library($reader);
$doorRow=null;foreach($libState as $row)if($row['book_id']==='B083')$doorRow=$row;
$checks['door_state_touched_not_read']=($doorRow['status']??'')==='TOUCHED';

// Entitlement check must not change mode.
$before=being_journey($jid);
$check=being_explore_check($reader,$jid);
$after=being_journey($jid);
$checks['unowned_explore_check_purchase_required']=($check['status']??'')==='purchase_required';
$checks['entitlement_check_does_not_leave_thread']=($before['mode']??'')==='thread' && ($after['mode']??'')==='thread' && ($after['current_spread']??0)===2;

// Cannot follow the thread while exploring; first unlock and begin from page 1.
being_grant($reader,'B083','audit');
$check2=being_explore_check($reader,$jid);
$checks['owned_explore_check']=($check2['status']??'')==='owned';
$explore=being_explore_book($reader,$jid);
$checks['exploration_starts_page1']=($explore['status']??'')==='book' && (int)($explore['spread']??0)===1;
$jbook=being_journey($jid);
$checks['book_mode_preserves_return_point']=($jbook['mode']??'')==='book'
 && (int)($jbook['current_spread']??0)===1
 && (int)($jbook['thread_return']['spread']??0)===2
 && ($jbook['thread_return']['book_id']??'')==='B083';

$blocked=being_follow_thread($reader,$jid);
$checks['book_mode_cannot_spawn_second_thread']=($blocked['status']??'')==='locked';

// Complete canonical Book, then return to original Thread point.
foreach([1,2,3,4] as $sp)being_visit_page($reader,'B083',$sp,$jid);
$resume=being_resume_thread($reader,$jid);
$checks['book_completion_returns_exact_thread_point']=($resume['status']??'')==='resume'
 && ($resume['book_id']??'')==='B083' && (int)($resume['spread']??0)===2;

// Continue exact single-spread Thread.
foreach([
 ['from'=>'B083','spread'=>2,'to'=>'B089','landing'=>2],
 ['from'=>'B089','spread'=>2,'to'=>'B109','landing'=>3],
 ['from'=>'B109','spread'=>3,'to'=>'B112','landing'=>3],
] as $hop){
    being_visit_page($reader,$hop['from'],$hop['spread'],$jid);
    $r=being_follow_thread($reader,$jid);
    if(($r['book_id']??'')!==$hop['to']||(int)($r['spread']??0)!==$hop['landing'])
        $problems[]='bad exact handoff '.$hop['from'].'→'.$hop['to'];
}
$checks['remaining_exact_handoffs']=!array_filter($problems,fn($x)=>str_starts_with($x,'bad exact handoff'));
$done=being_visit_page($reader,'B112',3,$jid);
$checks['final_thread_page_completes_path']=($done['path_complete']??false)===true;

// Map must hide future titles and mark one-page windows.
$map=being_journey_map($reader,$jid);
$onePage=true;foreach($map['nodes'] as $n)if(($n['reached']??false)&&(($n['window_end']-$n['window_start'])!==0))$onePage=false;
$checks['map_windows_one_page']=$onePage;

// UI language + mode controls.
$js=file_get_contents($root.'/assets/app.js');$css=file_get_contents($root.'/assets/styles.css');
$checks['following_thread_state_visible']=strpos($js,'FOLLOWING THREAD')!==false;
$checks['exploring_book_state_visible']=strpos($js,'EXPLORING BOOK')!==false;
$checks['center_state_is_not_button']=strpos($js,'class="reading-state')!==false;
$checks['explore_check_before_explore_book']=strpos($js,"api('explore_check'")!==false;
$checks['book_end_returns_to_thread']=strpos($js,"nextLabel='Return to Thread →'")!==false;
$checks['no_book_end_purchase_chain']=stripos($js,'next book')===false && stripos($js,'buy next')===false;
$checks['no_permanent_follow_thread_button']=strpos($js,'id="follow"')===false && strpos($js,'Follow the thread')===false;
$checks['no_portal_language']=stripos($js,'portal')===false;
$checks['live_text_overlay']=strpos($js,'story-panel')!==false && strpos($js,'s.voice.map')!==false;
$checks['portrait_artifact_page']=strpos($css,'aspect-ratio:2/3')!==false;

$pass=!in_array(false,$checks,true)&&count($problems)===0;
echo json_encode(['pass'=>$pass,'checks'=>$checks,'problems'=>$problems,'missing_art'=>$missing],JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES),PHP_EOL;
if(is_file($stateFile))unlink($stateFile);
exit($pass?0:1);
