<?php
declare(strict_types=1);
final class CoreGate{
 public static $scenario='';public static $ids=[];public static $started=0;public static $meta=false;public static $total=0;public static $counts=[];public static $generated=[];public static $last=[];public static $mainCommitted=false;public static $metadataMap=null;
 public static $tables=['AI'=>['user','staff','user_weight','user_weight_status','user_course','user_score','user_score_log','user_bind','user_health_metric_record','store','store_setting','upload_file','user_oauth'],'ZHCT'=>['user']];
 const COLUMNS='SELECT COLUMN_NAME,DATA_TYPE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID';const PRIMARY="SELECT cc.COLUMN_NAME FROM USER_CONSTRAINTS c JOIN USER_CONS_COLUMNS cc ON c.CONSTRAINT_NAME=cc.CONSTRAINT_NAME WHERE c.TABLE_NAME=? AND c.CONSTRAINT_TYPE='P'";
 public static function rec($r){$r['scenario']=self::$scenario;event('business-native.jsonl',$r);}
 public static function a(){global $runtime;return json_decode(file_get_contents($runtime.'/owned-actor.json'),true);}
 public static function known($db,$table){global $runtime;if(self::$metadataMap===null)self::$metadataMap=json_decode(file_get_contents($runtime.'/metadata-tables.json'),true);ck(self::$metadataMap[$db]['schema']===getenv('POC_'.$db.'_SCHEMA'),'Exact metadata schema');ck(is_string($table)&&preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/D',$table)===1&&strpos(strtolower($table),$db==='AI'?'yoshop_':'ydy_')===0&&in_array(strtolower($table),self::$metadataMap[$db]['tables'],true),'Frozen exact existing metadata table');}
 public static function kind($db,$sql){$plain=preg_replace("/'(?:''|\\\\.|[^'\\\\])*'/s",'?', $sql);ck(!preg_match('/;|--|\/\*|\*\/|#|\b(?:DELETE|REPLACE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|OUTFILE|DUMPFILE|LOAD|EXEC|EXECUTE|SLEEP|BENCHMARK|UTL_HTTP|NEXTVAL)\b/i',$plain),'No destructive/unknown SQL');if($sql===self::COLUMNS||$sql===self::PRIMARY)return 'metadata_SELECT';if(preg_match('/^SHOW FULL COLUMNS FROM `([A-Za-z_][A-Za-z0-9_]*)`$/D',$sql,$m)){self::known($db,$m[1]);return 'metadata_SHOW';}if($sql==='CALL SP_TABLEDEF(?, ?)'&&self::$meta)return 'metadata_CALL';ck(!preg_match('/\bUSER_(?:TAB_COLUMNS|CONSTRAINTS|CONS_COLUMNS|TABLES)\b/i',$sql),'Exact original metadata shapes only');if(preg_match('/^\s*SELECT\b/i',$sql)){ck(!preg_match('/\bINTO\b/i',$plain),'No SELECT INTO');if(preg_match('/\bFOR\s+UPDATE\s*$/i',$sql))ck($db==='AI'&&in_array(self::$scenario,['W1_MANUAL','W2_GOAL'],true)&&preg_match('/\byoshop_(?:user|user_weight_status)\b/i',$sql),'Only original own owner/goal locks');preg_match_all('/\b(yoshop|ydy)_([a-z0-9_]+)/i',$sql,$m,PREG_SET_ORDER);ck(count($m)>0,'Scoped data SELECT');foreach($m as $r)ck(($db==='AI'?strtolower($r[1])==='yoshop':strtolower($r[1])==='ydy')&&in_array(strtolower($r[2]),self::$tables[$db],true),'Approved related read table '.$r[0]);return 'SELECT';}if(preg_match('/^(INSERT INTO|UPDATE)\s+[`"]?(yoshop_[a-z_]+)[`"]?/i',$sql,$m)){ck($db==='AI'&&in_array(self::$scenario,['W1_MANUAL','W2_GOAL'],true),'Only two original AI writers');return strtoupper($m[1])==='UPDATE'?'UPDATE':'INSERT';}throw new RuntimeException('Unknown original SQL');}
 public static function timeValue($v,$table,$field){global $runtime;$schema=json_decode(file_get_contents($runtime.'/schema.json'),true);$type='';foreach($schema[substr($table,7)]['columns'] as $c)if(strtolower($c['column_name'])===$field)$type=strtolower($c['data_type']);if(preg_match('/int/',$type)){ck(is_int($v)&&$v>=self::$started-2&&$v<=time()+2,'Original integer clock');}else{ck(is_string($v)&&preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}$/D',$v),'Original automatic six-microsecond clock');$d=DateTimeImmutable::createFromFormat('!Y-m-d H:i:s.u',$v);ck($d!==false&&$d->format('Y-m-d H:i:s.u')===$v&&$d->getTimestamp()>=self::$started-2&&$d->getTimestamp()<=time()+2,'Actual current ORM clock');}}
 public static function equal($v,$expected,$table){$a=array_keys($v);$b=array_keys($expected);sort($a);sort($b);ck($a===$b,'Exact original fields '.$table);foreach($expected as $k=>$want){if($want==='@clock'){self::timeValue($v[$k],$table,$k);continue;}if($want==='@measuretime'){ck(is_string($v[$k])&&preg_match('/^\d{2}:\d{2}:\d{2}$/D',$v[$k])&&abs(strtotime('2026-10-11 '.$v[$k])-time())<=45,'Original measure time bytes');continue;}if(is_array($want)&&isset($want['php']))ck(gettype($v[$k])===$want['php']&&(string)$v[$k]===(string)$want['value'],'Exact original value/PHPtype '.$table.'.'.$k);else ck($v[$k]===null?$want===null:(string)$v[$k]===(string)$want,'Exact original value '.$table.'.'.$k);}}
 public static function parse($sql,$bind){$clean=preg_replace('/[`"]([A-Za-z_][A-Za-z0-9_]*)[`"]/', '$1',$sql);$v=[];$pairs=[];$where=[];$table='';if(preg_match('/^INSERT INTO (\w+) SET (.+)$/is',$clean,$m)){$table=strtolower($m[1]);foreach(explode(',',$m[2]) as $p){ck(preg_match('/^\s*(\w+)\s*=\s*(:\w+|NULL)\s*$/Di',$p,$q),'Only original single-row bound INSERT');$pairs[]=[$q[1],$q[2]];}}elseif(preg_match('/^INSERT INTO (\w+)\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)\s*$/is',$clean,$m)){$table=strtolower($m[1]);$ks=explode(',',$m[2]);$vs=explode(',',$m[3]);ck(count($ks)===count($vs),'INSERT column lengths');foreach($ks as $i=>$k)$pairs[]=[trim($k),trim($vs[$i])];}elseif(preg_match('/^UPDATE\s+(\w+)\s+SET\s+(.+?)\s+WHERE\s+(.+)$/is',$clean,$m)){$table=strtolower($m[1]);foreach(explode(',',$m[2]) as $p){ck(preg_match('/^\s*(\w+)\s*=\s*(:\w+|NULL)\s*$/Di',$p,$q),'Only original UPDATE bound field');$pairs[]=[$q[1],$q[2]];}$w=trim(str_replace(['(',')'],'',$m[3]));foreach(preg_split('/\s+AND\s+/i',$w) as $p){ck(preg_match('/^\s*((?:\w+\.)?)(\w+)\s*=\s*:(\w+)\s*$/D',$p,$q),'Only exact own equality WHERE');$b=$bind[$q[3]]??null;ck(is_array($b)&&count($b)===2,'Exact original WHERE bind');$where[strtolower($q[2])]=$b[0];$pairs[]=[$q[2],':'.$q[3],$q[1]!==''?'qualified_where':'where'];}}else throw new RuntimeException('Unknown DML structure');foreach($pairs as $p){[$field,$token]=$p;$field=strtolower($field);if(count($p)===3)continue;ck(!array_key_exists($field,$v),'No duplicate field');if(strtoupper($token)==='NULL')$v[$field]=null;else{ck(preg_match('/^:\w+$/D',$token)&&isset($bind[substr($token,1)]),'Named original field bind');$v[$field]=$bind[substr($token,1)][0];}}return [$table,$v,$where,$pairs];}
 public static function types($table,$pairs,$bind){global $runtime;$schema=json_decode(file_get_contents($runtime.'/schema.json'),true)[substr($table,7)]['columns'];$types=[];foreach($schema as $c)$types[strtolower($c['column_name'])]=strtolower($c['data_type']);foreach($pairs as $p){[$field,$token]=$p;$f=strtolower($field);ck(isset($types[$f]),'Actual metadata field');if(strtoupper($token)==='NULL'){ck($f==='secondary_value','Only original nullable secondary value literal');continue;}$b=$bind[substr($token,1)];$expected=preg_match('/int|bool|bit/',$types[$f])?1:(preg_match('/decimal|numeric|number|float|double|real/',$types[$f])?21:2);if(($p[2]??'')==='qualified_where'&&$f==='store_id'){$expected=2;ck($b[0]===10001,'Exact qualified tenant PHP int');}ck($b[1]===$expected,'Original schema/PDO bind type '.$table.'.'.$f);}}
 public static function plan($db,$sql,$bind){$kind=self::kind($db,$sql);$values=array_values(array_map(static function($b){return is_array($b)?$b[0]:$b;},$bind));if($kind==='metadata_SELECT'){ck(count($values)===1,'One source metadata bind');self::known($db,$values[0]);return;}if($kind==='metadata_SHOW'){ck($values===[],'Original SHOW no binds');return;}if($kind==='metadata_CALL'){ck(count($values)===2&&$values[0]===strtoupper((string)getenv('POC_'.$db.'_SCHEMA')),'Exact metadata procedure schema');self::known($db,$values[1]);return;}
  if($kind==='SELECT'){$a=self::a();$flat=array_map('strval',$values);if(preg_match('/\b(?:yoshop_(?:user|staff|user_weight|user_weight_status|user_course|user_score|user_score_log|user_bind|user_health_metric_record|user_oauth)|ydy_user)\b/i',$sql))ck(in_array((string)$a['user_id'],$flat,true),'Only own actor data read');if(preg_match('/\byoshop_store(?:_setting)?\b/i',$sql))ck(in_array('10001',$flat,true),'Only tenant config data');if(stripos($sql,'yoshop_upload_file')!==false){global $runtime;$lease=json_decode(file_get_contents($runtime.'/tenant-file-lease.json'),true);$clean=preg_replace('/[`"]([A-Za-z_][A-Za-z0-9_]*)[`"]/', '$1',$sql);ck(preg_match('/file_id\s*=\s*:(\w+)/i',$clean,$m)&&isset($bind[$m[1]]),'Exact related file predicate');$b=$bind[$m[1]];$id=is_array($b)?$b[0]:$b;ck(in_array((string)$id,$lease['allowed_file_ids'],true)&&in_array('10001',$flat,true),'Exact tenant/actor file lease');}return;}
  $a=self::a();[$table,$v,$where,$pairs]=self::parse($sql,$bind);self::types($table,$pairs,$bind);$s=self::$scenario;$manual=$s==='W1_MANUAL';$expected=[];$limit=[];$u=$a['user_id'];$t=static function($php,$value){return ['php'=>$php,'value'=>$value];};
  if($kind==='INSERT'){$limit=$manual?['yoshop_user_weight'=>1,'yoshop_user_bind'=>1,'yoshop_user_score_log'=>1,'yoshop_user_score'=>1,'yoshop_user_health_metric_record'=>2]:['yoshop_user_weight_status'=>1];ck(isset($limit[$table])&&(self::$counts['INSERT.'.$table]??0)<$limit[$table],'Exact INSERT table/count');
   if($table==='yoshop_user_weight')$expected=['user_id'=>$u,'store_id'=>10001,'date'=>1791648000,'weight'=>$t('string','72.34'),'height'=>$t('double',181.25),'bmi'=>$t('double',22.02),'create_time'=>'@clock','update_time'=>'@clock'];
   elseif($table==='yoshop_user_bind')$expected=['business'=>'zhct','zhct_user_id'=>$t('integer',0),'user_id'=>$u,'store_id'=>10001,'create_time'=>'@clock','update_time'=>'@clock'];
   elseif($table==='yoshop_user_score_log')$expected=['user_id'=>$u,'store_id'=>10001,'type'=>1,'meal_times'=>0,'course'=>'','score'=>1,'date'=>'2026-10-11','create_time'=>'@clock','update_time'=>'@clock'];
   elseif($table==='yoshop_user_score')$expected=['user_id'=>$u,'store_id'=>10001,'type'=>1,'score'=>1,'create_time'=>'@clock','update_time'=>'@clock'];
   elseif($table==='yoshop_user_weight_status')$expected=['user_id'=>$u,'store_id'=>10001,'start_date'=>1791648000,'initial_weight'=>$t('double',72.5),'target_weight'=>$t('string','71.50'),'adjust_speed'=>$t('integer',2),'status'=>3,'end_date'=>1792771200,'create_time'=>'@clock','update_time'=>'@clock'];
   else{ck(self::$mainCommitted&&isset(self::$generated['yoshop_user_weight']),'Health only after actual main commit');$code=$v['metric_code']??'';ck(in_array($code,['weight','bmi'],true)&&empty(self::$counts['metric.'.$code]),'Exactly one health metric each');$isW=$code==='weight';$expected=['user_id'=>$u,'store_id'=>10001,'metric_code'=>$code,'value'=>$t('double',$isW?72.34:22.02),'secondary_value'=>null,'unit'=>$isW?'kg':'','measure_date'=>'2026-10-11','measure_time'=>'@measuretime','measure_site'=>'','source_type'=>1,'source_key'=>'user-weight:'.self::$generated['yoshop_user_weight'],'evaluation_code'=>$isW?'recorded':'normal','evaluation_name'=>$isW?'已记录':'正常','evaluation_tone'=>$isW?'recorded':'normal','disease_type'=>0,'rule_version'=>'v1','is_delete'=>0,'create_time'=>'@clock','update_time'=>'@clock'];self::$counts['metric.'.$code]=1;}
  }else{ck(in_array($table,['yoshop_staff','yoshop_user_weight'],true),'Only own Staff/weight updates');$pk=$table==='yoshop_staff'?$a['staff_id']:self::$generated['yoshop_user_weight']??json_decode(file_get_contents(__DIR__.'/private/flow-ids.json'),true)['weight_id'];$keys=array_keys($where);sort($keys);ck(in_array($keys,[['id'],['id','store_id']],true)&&(string)$where['id']===(string)$pk&&(!isset($where['store_id'])||$where['store_id']===10001),'Exact indexed own UPDATE predicate');ck(empty(self::$counts['UPDATE.'.$table]),'At most one each UPDATE per writer');if($table==='yoshop_staff')$expected=['weight'=>$t('string',$manual?'72.34':'72.5'),'update_time'=>'@clock'];else{ck(!$manual,'No manual existing-row update');$expected=['weight'=>$t('double',72.5),'height'=>$t('string','180.25'),'bmi'=>$t('double',22.31),'update_time'=>'@clock'];}}
  self::equal($v,$expected,$table);self::$last[$db]=$table;
 }
}
