<?php
declare(strict_types=1);
final class InvGate {
 public static $runtime='',$scenario='',$recordScenario='',$case=[],$started=0,$call=false,$masterId=0,$masterInserts=0,$answerInserts=0,$updates=0,$deletes=0,$lastTable='',$ops=[],$index=0,$pending=null;
 public static $tables=['nutrition_meals','nutrition_lack','staff','store_user','config_enum'];
 public static function record($r){$r['scenario']=self::$recordScenario?:self::$scenario;$j=json_encode($r,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PRESERVE_ZERO_FRACTION);if($j===false)throw new RuntimeException('Native encoding');file_put_contents(self::$runtime.'/pdo-native.jsonl',$j."\n",FILE_APPEND|LOCK_EX);chmod(self::$runtime.'/pdo-native.jsonl',0600);}
 public static function kind($sql){$plain=preg_replace("/'(?:''|\\\\.|[^'\\\\])*'/s",'?', $sql);if(preg_match('/;|--|\/\*|\*\/|#|\b(?:REPLACE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|OUTFILE|DUMPFILE|LOAD|DO|EXEC|EXECUTE|SLEEP|BENCHMARK|UTL_HTTP)\b/i',$plain))throw new RuntimeException('Forbidden SQL');if(preg_match('/^\s*SELECT\b/i',$plain)){if(preg_match('/\b(?:INTO|INSERT|UPDATE|DELETE)\b/i',$plain))throw new RuntimeException('SELECT side effect');if(preg_match_all('/\byoshop_([a-z0-9_]+)/i',$plain,$m))foreach($m[1] as $table)if(!in_array(strtolower($table),self::$tables,true))throw new RuntimeException('Unlisted SELECT table');return 'SELECT';}if(preg_match('/^SHOW FULL COLUMNS FROM `yoshop_([a-z_]+)`$/D',$sql,$m)&&in_array($m[1],self::$tables,true))return 'metadata_SHOW';if($sql==='CALL SP_TABLEDEF(?, ?)'&&self::$call)return 'metadata_CALL';if(self::$case['kind']==='write'&&preg_match('/^(INSERT INTO|UPDATE|DELETE FROM)\s+[`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?(?=\s|\()/i',$sql,$m))return strpos($m[1],'INSERT')===0?'INSERT':(strpos($m[1],'DELETE')===0?'DELETE':'UPDATE');throw new RuntimeException('Unapproved SQL');}
 public static function metadata($sql,$bind){self::$call=false;if($sql!=='CALL SP_TABLEDEF(?, ?)')return;$b=[];foreach($bind as $v){if(is_array($v)){if(count($v)!==2||$v[1]!==PDO::PARAM_STR)throw new RuntimeException('Metadata STR');$v=$v[0];}$b[]=$v;}if(count($b)!==2||$b[0]!=='AIZHCT_APP'||!is_string($b[1])||strpos($b[1],'YOSHOP_')!==0||!in_array(strtolower(substr($b[1],7)),self::$tables,true))throw new RuntimeException('Exact original metadata scope');self::$call=true;}
 public static function value($token,$bind){$token=trim($token);if(preg_match('/^:[A-Za-z0-9_]+$/D',$token)){$key=substr($token,1);if(!array_key_exists($key,$bind)||!is_array($bind[$key])||count($bind[$key])!==2||!in_array($bind[$key][1],[PDO::PARAM_STR,PDO::PARAM_INT,PDO::PARAM_BOOL,PDO::PARAM_NULL],true))throw new RuntimeException('Exact named typed binding');return $bind[$key][0];}if(preg_match('/^[01]$/D',$token))return (int)$token;throw new RuntimeException('Unapproved token');}
 public static function assignments($text,$bind){$out=[];foreach(explode(',',$text) as $pair){if(!preg_match('/^\s*[`"]?([A-Za-z_][A-Za-z0-9_]*)[`"]?\s*=\s*(:[A-Za-z0-9_]+)\s*$/D',$pair,$m)||array_key_exists($m[1],$out))throw new RuntimeException('Exact assignment');$out[$m[1]]=self::value($m[2],$bind);}return $out;}
 public static function where($text,$bind){$text=preg_replace('/(?:[`"]yoshop_(?:nutrition_meals|nutrition_lack)[`"]|yoshop_(?:nutrition_meals|nutrition_lack))\./','',$text);$text=str_replace(['`','"'],'',$text);$parts=preg_split('/\s+AND\s+/i',trim($text));$out=[];foreach($parts as $part){$part=trim($part);if(preg_match('/^\(?\s*([a-z_]+)\s*=\s*(:[A-Za-z0-9_]+|[01])\s*\)?$/Di',$part,$m)){$key=strtolower($m[1]);$value=self::value($m[2],$bind);}elseif(preg_match('/^([a-z_]+)\s+IN\s*\(\s*(:[A-Za-z0-9_]+(?:\s*,\s*:[A-Za-z0-9_]+)*)\s*\)$/Di',$part,$m)){$key=strtolower($m[1]);$value=[];foreach(explode(',',$m[2]) as $t)$value[]=(string)self::value($t,$bind);sort($value);}else throw new RuntimeException('Exact AND equality/IN where');if(array_key_exists($key,$out))throw new RuntimeException('Duplicate WHERE');$out[$key]=$value;}return $out;}
 public static function same($actual,$expected,$timed=false){ksort($actual);ksort($expected);if(array_keys($actual)!==array_keys($expected))throw new RuntimeException('Exact columns/where keys');foreach($expected as $k=>$v){if($v==='@master')$v=self::$masterId;if($timed&&$v==='@time'){$t=is_string($actual[$k])?strtotime($actual[$k]):false;if($t===false||$t<self::$started-2||$t>time()+2)throw new RuntimeException('Original ORM datetime');}elseif(is_array($v)){$a=$actual[$k];if(!is_array($a)&&count($v)===1)$a=[(string)$a];if($a!==$v)throw new RuntimeException('Exact IN values');}elseif((string)$actual[$k]!== (string)$v)throw new RuntimeException('Exact value '.$k);}}
 public static function plan($sql,$bind){$kind=self::kind($sql);if(!in_array($kind,['INSERT','UPDATE','DELETE'],true))return;if(!isset(self::$ops[self::$index]))throw new RuntimeException('Statement budget exhausted');$op=self::$ops[self::$index];if($kind!==$op['kind'])throw new RuntimeException('Exact original operation order');$table='';$values=[];$where=[];
  if($kind==='INSERT'){if(preg_match('/^INSERT INTO [`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?\s+SET\s+(.+?)\s*$/is',$sql,$m)){$table=$m[1];$values=self::assignments($m[2],$bind);}elseif(preg_match('/^INSERT INTO [`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)\s*$/i',$sql,$m)){$table=$m[1];$cols=explode(',',$m[2]);$tokens=explode(',',$m[3]);if(count($cols)!==count($tokens))throw new RuntimeException('INSERT lengths');foreach($cols as $i=>$c){$k=trim($c," \t`\"");if(!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/D',$k)||array_key_exists($k,$values))throw new RuntimeException('INSERT columns');$values[$k]=self::value($tokens[$i],$bind);}}else throw new RuntimeException('Single bound INSERT only');}
  elseif($kind==='UPDATE'){if(!preg_match('/^UPDATE [`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?\s+SET\s+(.+?)\s+WHERE\s+(.+?)\s*$/is',$sql,$m))throw new RuntimeException('Exact UPDATE');$table=$m[1];$values=self::assignments($m[2],$bind);$where=self::where($m[3],$bind);}
  else {if(!preg_match('/^DELETE FROM [`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?\s+WHERE\s+(.+?)\s*$/is',$sql,$m))throw new RuntimeException('Exact DELETE');$table=$m[1];$where=self::where($m[2],$bind);}
  if($table!==$op['table'])throw new RuntimeException('Exact write table');self::same($values,$op['values']??[],true);self::same($where,$op['where']??[]);self::$pending=$op;self::record(['layer'=>'exact scope and row lease approved','queryString'=>$sql,'operation'=>$kind,'operation_index'=>self::$index,'lease'=>$op]);
 }
 public static function lease($case,$before){self::$case=$case;$body=$case['body'];$group=$case['group']??'';$type=['PROBLEM'=>6,'MEALS'=>10,'FOOD'=>11][$group]??0;$create=strpos(self::$scenario,'CREATE')!==false;self::$ops=[];self::$index=0;self::$masterId=0;self::$pending=null;if($case['kind']!=='write')return;
  if(!preg_match('/^(?:PROBLEM|MEALS|FOOD)_(?:UPDATE|CREATE_RETRY|FRESH_CREATE)$/D',self::$scenario))throw new RuntimeException('Only remaining approved write case');$staff=getenv('INV_LANE')==='B'?46334:1851;$operator=getenv('INV_LANE')==='B'?10074:10010;$date=$body['set_date'];if(($date!=='2097-11-11'&&!(getenv('INV_LANE')==='C'&&self::$scenario==='FOOD_FRESH_CREATE'&&$date==='2097-11-12'))||$body['staff_id']!==$staff)throw new RuntimeException('Exact new scope');$scope=['store_id'=>2147001111,'staff_id'=>$staff,'set_date'=>$date,'user_id'=>$operator];$masters=array_values(array_filter($before['nutrition_meals'],static function($r)use($type,$date){return (int)$r['type']===$type&&$r['set_date']===$date;}));$answers=array_values(array_filter($before['nutrition_lack'],static function($r)use($date){return $r['set_date']===$date;}));foreach(array_merge($masters,$answers) as $r)foreach($scope as $k=>$v)if((string)$r[$k]!== (string)$v)throw new RuntimeException('Independent fullrow scope lease');
  $stamp=['create_by'=>$case['create_by'],'create_time'=>'@time','update_time'=>'@time'];$master=array_merge($scope,['type'=>$type,'other_text'=>$body['other_text']]);if($type===11){$master['food_allergy']=$body['food_allergy'];$master[getenv('POC_DIALECT')==='dm'?'food_intolerance':'food_Intolerance']=$body['food_Intolerance'];$master['food_disable']=$body['food_disable'];}
  if($create){if($masters!==[]||array_filter($answers,static function($r)use($type){return $type===6?in_array((int)$r['type'],[6,7,8,9],true):(int)$r['type']===$type;}))throw new RuntimeException('Failed/fresh CREATE independent scope0');if(getenv('POC_DIALECT')==='dm'&&$type!==11)$master['food_allergy']='';self::$ops[]=['kind'=>'INSERT','table'=>'nutrition_meals','values'=>array_merge($master,$stamp),'affected'=>1,'lease_ids'=>[]];}
  else {if(count($masters)!==1||(int)$masters[0]['is_delete']!==0||strpos($masters[0]['other_text'],'CDXIW11C8F2_')!==0)throw new RuntimeException('Existing marked master lease');self::$masterId=(int)$masters[0]['id'];$values=['other_text'=>$body['other_text'],'update_time'=>'@time'];if($type===11){$values['food_allergy']=$body['food_allergy'];$values[getenv('POC_DIALECT')==='dm'?'food_intolerance':'food_Intolerance']=$body['food_Intolerance'];$values['food_disable']=$body['food_disable'];}self::$ops[]=['kind'=>'UPDATE','table'=>'nutrition_meals','values'=>$values,'where'=>['store_id'=>2147001111,'id'=>self::$masterId],'affected'=>1,'lease_ids'=>[self::$masterId],'full_scope'=>$scope+['type'=>$type]];}
  $add=static function($extra)use($scope,$stamp){self::$ops[]=['kind'=>'INSERT','table'=>'nutrition_lack','values'=>array_merge($scope,$stamp,['master_id'=>'@master'],$extra),'affected'=>1,'lease_ids'=>[]];};
  if($type===6||$type===10){$rows=array_values(array_filter($answers,static function($r)use($type){return (int)$r['type']===$type&&($type===10||(int)$r['is_delete']===0);}));if(count($rows)!==($create?0:($type===6?3:2)))throw new RuntimeException('Exact soft delete row lease count');$where=$scope+['type'=>$type];if($type===6)$where['is_delete']=0;self::$ops[]=['kind'=>'UPDATE','table'=>'nutrition_lack','values'=>['is_delete'=>1],'where'=>$where,'affected'=>count($rows),'lease_ids'=>array_map(static function($r){return (int)$r['id'];},$rows)];if($type===6){foreach($body['enum_data'] as $e)if($e['checked']!=3)$add(['type'=>6,'reference_id'=>$e['enum_key'],'checkout_status'=>$e['checked'],'v1'=>$e['v1'],'v2'=>$e['v2']]);}else foreach($body['enum_data'] as $e)$add(['type'=>10,'reference_id'=>$e['enum_id'],'recommend'=>$e['recommend']]);}
  $sets=$type===6?['lack'=>7,'meal_type'=>8,'nutrition_type'=>9]:($type===11?['enum_data'=>11]:[]);foreach($sets as $key=>$answerType){$old=[];foreach($answers as $row)if((int)$row['type']===$answerType&&(int)$row['is_delete']===0){if(!self::$masterId&& !$create)throw new RuntimeException('No master');if((int)$row['master_id']!==self::$masterId||array_key_exists((string)$row['reference_id'],$old))throw new RuntimeException('Unique answer/master lease');$old[(string)$row['reference_id']]=$row;}$want=array_map('strval',$body[$key]);$gone=array_diff(array_keys($old),$want);if($gone){$ids=[];foreach($gone as $ref)$ids[]=(int)$old[$ref]['id'];$refs=array_map('strval',array_values($gone));sort($refs);self::$ops[]=['kind'=>'DELETE','table'=>'nutrition_lack','where'=>$scope+['type'=>$answerType,'reference_id'=>$refs],'affected'=>count($ids),'lease_ids'=>$ids];}foreach(array_diff($want,array_keys($old)) as $id)$add(['type'=>$answerType,'reference_id'=>$id]);}
 }
}
class InvStatement extends PDOStatement {private $bound=[];protected function __construct(){}public function bindValue($p,$v,$type=PDO::PARAM_STR){$ok=parent::bindValue($p,$v,$type);$this->bound[(string)$p]=['value'=>$v,'pdo_type'=>$type,'php_type'=>gettype($v),'ok'=>$ok];return $ok;}public function bindParam($p,&$v,$type=PDO::PARAM_STR,$len=0,$opts=null){throw new RuntimeException('bindParam denied');}public function execute($params=null){$kind=InvGate::kind($this->queryString);$r=['layer'=>'actual PDOStatement.execute','queryString'=>$this->queryString,'bindValue'=>$this->bound,'execute_array'=>$params,'operation'=>$kind];try{if(in_array($kind,['INSERT','UPDATE','DELETE'],true)){$b=[];foreach($this->bound as $k=>$v)$b[ltrim($k,':')]=[$v['value'],$v['pdo_type']];if($params!==null)throw new RuntimeException('No execute array for write');InvGate::plan($this->queryString,$b);}$ok=parent::execute($params);$r['success']=$ok;$r['rowCount']=$this->rowCount();if(in_array($kind,['INSERT','UPDATE','DELETE'],true)){if($r['rowCount']!==InvGate::$pending['affected'])throw new RuntimeException('Exact leased affected rows');InvGate::$index++;if($kind==='INSERT'){InvGate::$lastTable=InvGate::$pending['table'];if(InvGate::$lastTable==='nutrition_meals')InvGate::$masterInserts++;else InvGate::$answerInserts++;}elseif($kind==='UPDATE')InvGate::$updates++;else InvGate::$deletes++;}InvGate::record($r);return $ok;}catch(Throwable $e){$r['error']=['class'=>get_class($e),'code'=>$e->getCode(),'message'=>$e->getMessage()];InvGate::record($r);throw $e;}}}
class InvPDO extends PDO {
 public function prepare($sql,$options=[]){$kind=InvGate::kind($sql);try{$s=parent::prepare($sql,$options);InvGate::record(['layer'=>'actual PDO.prepare','queryString'=>$sql,'operation'=>$kind,'prepared'=>$s!==false]);return $s;}catch(Throwable $e){InvGate::record(['layer'=>'actual PDO.prepare','queryString'=>$sql,'operation'=>$kind,'error'=>['class'=>get_class($e),'code'=>$e->getCode(),'message'=>$e->getMessage()]]);throw $e;}}
 public function exec($sql){if(InvGate::$case['kind']!=='write'||!preg_match('/^(?:SAVEPOINT trans[2-9]|ROLLBACK TO SAVEPOINT trans[2-9])$/D',$sql))throw new RuntimeException('Only original nested transaction savepoint');$n=parent::exec($sql);InvGate::record(['layer'=>'actual PDO.exec transaction','queryString'=>$sql,'rowCount'=>$n]);return $n;}
 public function query($sql,...$args){InvGate::kind($sql);$s=$this->prepare($sql);$s->execute();if($args)$s->setFetchMode(...$args);return $s;}
 public function beginTransaction(){if(InvGate::$case['kind']!=='write')throw new RuntimeException('Only original write transaction');$ok=parent::beginTransaction();InvGate::record(['layer'=>'actual PDO.beginTransaction','success'=>$ok]);return $ok;}
 public function commit(){if(InvGate::$index!==count(InvGate::$ops))throw new RuntimeException('Incomplete exact write plan before commit');$ok=parent::commit();InvGate::record(['layer'=>'actual PDO.commit','success'=>$ok]);return $ok;}
 public function rollBack(){$ok=parent::rollBack();InvGate::record(['layer'=>'actual PDO.rollBack','success'=>$ok]);return $ok;}
 public function lastInsertId($name=null){$id=parent::lastInsertId($name);InvGate::record(['layer'=>'actual PDO.lastInsertId','table'=>InvGate::$lastTable,'value'=>$id,'php_type'=>gettype($id)]);if(InvGate::$lastTable==='nutrition_meals')InvGate::$masterId=(int)$id;return $id;}
}
trait InvConnection {protected function createPdo($dsn,$username,$password,$params){$dm=getenv('POC_DIALECT')==='dm';$expected=$dm?'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP':'mysql:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';dbname='.getenv('POC_AI_SCHEMA').';charset='.getenv('POC_AI_CHARSET');if($dsn!==$expected||$username!==getenv('POC_AI_USER')||$password!==getenv('POC_AI_PASSWORD')||isset($params[1002]))throw new RuntimeException('Exact approved AI connection');$p=new InvPDO($dsn,$username,$password,$params);if(!$p->setAttribute(PDO::ATTR_STATEMENT_CLASS,[InvStatement::class,[]]))throw new RuntimeException('Native capture unavailable');return $p;}public function getPDOStatement(string $sql,array $bind=[],bool $master=false,bool $procedure=false):PDOStatement{InvGate::metadata($sql,$bind);try{InvGate::kind($sql);InvGate::plan($sql,$bind);InvGate::record(['layer'=>'framework original bind plan','queryString'=>$sql,'bind_plan'=>$bind,'procedure'=>$procedure]);return parent::getPDOStatement($sql,$bind,$master,$procedure);}catch(Throwable $e){InvGate::record(['layer'=>'framework intercepted failure; may precede PDO.prepare','queryString'=>$sql,'bind_plan'=>$bind,'error'=>['class'=>get_class($e),'code'=>$e->getCode(),'message'=>$e->getMessage()]]);throw $e;}finally{InvGate::$call=false;}}}
class InvMysql extends \think\db\connector\Mysql {use InvConnection;public function getBuilderClass():string{return \think\db\builder\Mysql::class;}}
class InvDm extends \app\common\database\Dm {use InvConnection;}
