<?php
declare(strict_types=1);
date_default_timezone_set('Asia/Shanghai');define('CDX_CATEGORY_CONTEXT',true);require __DIR__.'/preflight.php';
$marker='CDXCAT10'.$lane.strtoupper(bin2hex(random_bytes(3)));$name=$marker;$id=0;$enabled=false;$stage='boundary';$out=['lane'=>$lane,'marker'=>$marker,'cases'=>[],'failure'=>null,'business_source'=>[]];$bodies=[];
foreach(['p/controller/DishCustomCategory.php','p/logic/DishCustomCategory.php','model/DishesCustomCategory.php']as $file)$out['business_source'][$file]=hash_file('sha256',__DIR__.'/source/application/'.$file);
$shape=static function($v)use(&$shape){if(is_object($v)){$r=[];foreach(get_object_vars($v)as $k=>$x)$r[$k]=$shape($x);ksort($r);return ['object'=>$r];}return is_array($v)?['array'=>array_map($shape,$v)]:(is_int($v)||is_float($v)?'number':gettype($v));};
$admin=$q('SELECT ID,UUID FROM '.$p.'user WHERE ID=(SELECT MIN(ID) FROM '.$p.'user WHERE TYPE=0 AND IS_DELETE=0)')[0];require_once $root.'/application/base/Security.php';$token=\app\base\Security::encrypt(implode("\t",[(string)$admin['id'],(string)$admin['uuid'],'CDXCAT10ADMIN','0','','','CDXCAT10ADMIN']));
$rq=static function(string $label,string $action,array $params,int $expected)use($lane,$token,&$out,&$bodies,$shape,$check):array{$path='/p/dishCustomCategory/'.$action;$raw=@file_get_contents('http://127.0.0.1:'.($lane==='B'?18301:18302).$path,false,stream_context_create(['http'=>['method'=>'POST','header'=>'token: '.$token."\r\nContent-Type: application/x-www-form-urlencoded\r\n",'content'=>http_build_query($params),'ignore_errors'=>true,'timeout'=>30]]));preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);$wire=json_decode((string)$raw);$body=json_decode((string)$raw,true);$bodies[$label]=$wire;$out['cases'][]=['scenario'=>$label,'method'=>'POST','path'=>$path,'http_status'=>(int)($m[1]??0),'code'=>$body['code']??null,'wire_shape'=>$shape($wire),'raw_wire_sha256'=>hash('sha256',(string)$raw)];$check((int)($m[1]??0)===200&&($body['code']??null)===$expected,'Unexpected result '.$label);return $body;};
$own=static function()use(&$id,$marker,$q,$p,$check):array{$rows=$q('SELECT * FROM '.$p.'dishes_custom_category WHERE ID=?',[$id]);$check(count($rows)===1&&strpos((string)$rows[0]['name'],$marker)===0&&$rows[0]['create_by']==='CDXCAT10ADMIN','Exact owned source');return $rows[0];};
$refs=static function()use(&$id,$q,$p):int{return (int)$q('SELECT COUNT(*) AS N FROM '.$p.'dishes WHERE CUSTOM_CATEGORY_ID=?',[$id])[0]['n'];};
$verify=static function(string $label)use($rq,&$id,&$name,$own,$check,&$out):void{$body=$rq($label,'list',['keyword'=>$name,'page'=>1,'page_size'=>100],0);$row=$own();$rows=$body['data']['rows'];$check(count($rows)===1&&(int)$rows[0]['id']===$id&&$body['data']['total']===1,'Owned list unique source');$project=array_intersect_key($row,array_flip(['id','code','name','status','is_system','is_delete','create_by','create_time','update_time']));$diff=[];foreach($project as $k=>$v){$actual=$rows[0][$k]??null;$same=$actual===$v;if(!$same&&is_numeric($actual)&&is_numeric($v))$same=(string)$actual===(string)$v;if(!$same)$diff[]=$k;}$check($diff===[]&&$rows[0]['dish_count']===0,'Owned list every formal source field');$n=count($out['cases'])-1;$out['cases'][$n]['all_formal_fields_source_equal']=true;$out['cases'][$n]['source_column_count']=count($row);$out['cases'][$n]['create_time_length']=strlen((string)$rows[0]['create_time']);$out['cases'][$n]['update_time_length']=strlen((string)$rows[0]['update_time']);};
try{
 $boundary=json_decode((string)file_get_contents('http://127.0.0.1:'.($lane==='B'?18301:18302).'/__poc_boundary'),true);$check($boundary['allowlist']==='owned-category-only'&&$boundary['external_stubs']&&$boundary['original_debug']===$report['original_debug'],'Boundary original config');
 $stage='create';$body=$rq('create','add',['name'=>$name,'status'=>2],0);$id=(int)$body['data']['id'];$check($id>0,'Actual created id');file_put_contents(__DIR__.'/private-own.json',json_encode(['id'=>$id,'marker'=>$marker]));chmod(__DIR__.'/private-own.json',0600);$row=$own();$check($row['code']===null&&(int)$row['status']===1&&(int)$row['is_system']===0&&(int)$row['is_delete']===0&&$refs()===0,'Original add default1 no references');$enabled=true;$rq('immediate_disable','changeStatus',['id'=>$id,'status'=>2],0);$enabled=false;$check((int)$own()['status']===2&&$refs()===0,'Own disabled');$verify('list_created_disabled');
 $stage='rejections';$before=$own();$count=(int)$q('SELECT COUNT(*) AS N FROM '.$p.'dishes_custom_category')[0]['n'];$rq('duplicate_name','add',['name'=>$name],1);$rq('empty_name','add',['name'=>''],1);$rq('overlong_name','add',['name'=>$marker.str_repeat('长',21)],1);$check($count===(int)$q('SELECT COUNT(*) AS N FROM '.$p.'dishes_custom_category')[0]['n']&&$own()===$before,'Rejected creates no mutation');
 $stage='options';foreach(['form'=>['usage'=>'form'],'include'=>['usage'=>'form','include_id'=>$id],'filter'=>['usage'=>'filter']]as $label=>$params){$body=$rq('options_'.$label,'options',$params,0);$matching=array_values(array_filter($body['data'],static function($r)use($id){return (int)$r['id']===$id;}));$check(count($matching)===($label==='form'?0:1),'Own options presence '.$label);if($matching)$check($matching[0]===['id'=>$id,'code'=>null,'name'=>$name,'status'=>2,'is_system'=>0],'Own options all fields/types');$out['cases'][count($out['cases'])-1]['owned_projection_source_equal']=true;}
 $stage='update';$old=$own();$name=$marker.'中\'';$rq('update_name','update',['id'=>$id,'name'=>$name],0);$row=$own();$check($row['name']===$name&&(int)$row['status']===2&&$row['create_time']===$old['create_time']&&$row['create_by']===$old['create_by']&&$refs()===0,'Update input/source and immutable fields');$verify('list_updated');$rq('update_repeat','update',['id'=>$id,'name'=>$name],0);$verify('list_repeated');
 $stage='status';$rq('enable_own','changeStatus',['id'=>$id,'status'=>1],0);$enabled=true;$check((int)$own()['status']===1&&$refs()===0,'Enabled own with no dishes');$rq('disable_own','changeStatus',['id'=>$id,'status'=>2],0);$enabled=false;$check((int)$own()['status']===2,'Disabled again');$before=$own();$rq('invalid_status','changeStatus',['id'=>$id,'status'=>3],1);$check($own()===$before,'Invalid status no mutation');$rq('status_filter','list',['keyword'=>$name,'status'=>2,'page_size'=>1,'page'=>1],0);
 $stage='delete';$check($refs()===0,'No unknown reference before deletion');$rq('delete','delete',['id'=>$id],0);$check((int)$own()['is_delete']===1&&$refs()===0,'Soft delete source and no dish writes');$rq('delete_repeat','delete',['id'=>$id],1);$body=$rq('list_deleted','list',['keyword'=>$marker],0);$check($body['data']===['rows'=>[],'total'=>0],'Deleted list empty original shape');$body=$rq('options_deleted','options',['usage'=>'form','include_id'=>$id],0);$check(array_filter($body['data'],static function($r)use($id){return (int)$r['id']===$id;})===[],'Deleted no options');$check($q('SELECT ID FROM '.$p.'dishes_custom_category WHERE ID=2147000811')===[],'Missing id genuinely absent');$rq('update_missing','update',['id'=>2147000811,'name'=>$marker],1);
}catch(Throwable $e){$out['failure']=['stage'=>$stage,'class'=>get_class($e),'reason'=>$e->getMessage()];}
finally{
 if($enabled&&$id>0){try{$rq('safety_disable','changeStatus',['id'=>$id,'status'=>2],0);}catch(Throwable $e){$out['safety_failure']=get_class($e);}}
 $out['retained']=$id?['id'=>$id,'marker'=>$marker,'status'=>(int)$own()['status'],'is_delete'=>(int)$own()['is_delete'],'dish_references'=>$refs()]:null;
 $out['category_natural_source']=$hashRows('SELECT * FROM '.$p.'dishes_custom_category WHERE ID<>? ORDER BY ID',[$id]);$out['category_natural_unchanged']=$out['category_natural_source']===$report['category_source'];$out['dishes_source']=$hashRows('SELECT ID,UUID,CUSTOM_CATEGORY_ID,IS_DELETE,IS_DEL FROM '.$p.'dishes ORDER BY ID');$out['dishes_projection_unchanged']=$out['dishes_source']===$report['dishes_source'];$out['dishes_audit_scope']='all rows exact ID/UUID/custom_category_id/is_delete/is_del projection, not all dish columns';$out['business_dml_tables']=['dishes_custom_category'];
 foreach(['private-bodies.json'=>$bodies,'private-crud.json'=>$out,'private-baseline.json'=>$report]as $f=>$v){file_put_contents(__DIR__.'/'.$f,json_encode($v,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));chmod(__DIR__.'/'.$f,0600);}
}echo json_encode($out),"\n";exit($out['failure']?2:0);
