<?php
declare(strict_types=1);
final class InvGate {
 public static $runtime='';public static $scenario='';public static $recordScenario='';public static $case=[];public static $started=0;public static $call=false;public static $masterId=0;public static $masterInserts=0;public static $answerInserts=0;public static $updates=0;public static $lastTable='';
 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(?:DELETE|REPLACE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|OUTFILE|DUMPFILE|LOAD|DO|EXEC|EXECUTE|SLEEP|BENCHMARK|UTL_HTTP)\b/i',$plain))throw new RuntimeException('Phase1 forbidden SQL');if(preg_match('/^\s*SELECT\b/i',$plain)){if(preg_match('/\bINTO\b/i',$plain))throw new RuntimeException('SELECT INTO');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(preg_match('/^(INSERT INTO|UPDATE)\s+[`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?\b/i',$sql)&&in_array(self::$scenario,['PROBLEM_CREATE','MEALS_CREATE','FOOD_CREATE'],true))return stripos($sql,'INSERT')===0?'INSERT':'UPDATE';throw new RuntimeException('Unapproved phase1 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 values($sql,$bind){$pairs=[];if(preg_match('/^INSERT INTO [`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?\s+SET\s+(.+?)\s*$/is',$sql,$m)){foreach(explode(',',$m[2]) as $pair){if(!preg_match('/^\s*`([A-Za-z_][A-Za-z0-9_]*)`\s*=\s*(:[A-Za-z0-9_]+)\s*$/D',$pair,$p))throw new RuntimeException('Exact INSERT SET assignment');$pairs[]=[$p[1],$p[2]];}}elseif(preg_match('/^INSERT INTO [`"]?yoshop_(nutrition_meals|nutrition_lack)[`"]?\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)\s*$/i',$sql,$m)){$columns=explode(',',$m[2]);$tokens=explode(',',$m[3]);if(count($columns)!==count($tokens))throw new RuntimeException('INSERT lengths');foreach($columns as $i=>$c)$pairs[]=[trim($c," \t`\""),trim($tokens[$i])];}else throw new RuntimeException('Only single exact INSERT');$out=[];foreach($pairs as [$k,$v]){if(isset($out[$k]))throw new RuntimeException('Duplicate insert column');$name=ltrim($v,':');if(!array_key_exists($name,$bind))throw new RuntimeException('Named binding only');$b=$bind[$name];$out[$k]=is_array($b)?$b[0]:$b;}return [$m[1],$out];}
 public static function expectedAnswers(){ $body=self::$case['body'];$out=[];if(self::$scenario==='PROBLEM_CREATE'){foreach($body['enum_data'] as $enum)if($enum['checked']!=3)$out[]=['type'=>6,'reference_id'=>$enum['enum_key'],'checkout_status'=>$enum['checked'],'v1'=>$enum['v1'],'v2'=>$enum['v2']];foreach(['lack'=>7,'meal_type'=>8,'nutrition_type'=>9] as $key=>$type)foreach($body[$key] as $id)$out[]=['type'=>$type,'reference_id'=>$id];}elseif(self::$scenario==='MEALS_CREATE'){foreach($body['enum_data'] as $e)$out[]=['type'=>10,'reference_id'=>$e['enum_id'],'recommend'=>$e['recommend']];}else foreach($body['enum_data'] as $id)$out[]=['type'=>11,'reference_id'=>$id];return $out;}
 public static function plan($sql,$bind){$kind=self::kind($sql);if(!in_array($kind,['INSERT','UPDATE'],true))return;$body=self::$case['body'];$operator=getenv('INV_LANE')==='B'?10074:10010;$staff=getenv('INV_LANE')==='B'?46334:1851;$type=self::$scenario==='PROBLEM_CREATE'?6:(self::$scenario==='MEALS_CREATE'?10:11);
  if($kind==='UPDATE'){if(self::$scenario==='FOOD_CREATE'||self::$updates!==0||!preg_match('/^UPDATE\s+[`"]?yoshop_nutrition_lack[`"]?\s+SET\s+(.+?)\s+WHERE\s+(.+)$/is',$sql,$m))throw new RuntimeException('Only initial zero-row soft delete');$clauses=$m[1].' AND '.$m[2];preg_match_all('/(?:[`"]?yoshop_nutrition_lack[`"]?\.)?[`"]?([A-Za-z_]+)[`"]?\s*=\s*(:[A-Za-z0-9_]+|[0-9]+)(?=\s|$)/',$clauses,$pairs,PREG_SET_ORDER);$map=[];foreach($pairs as $p){$token=$p[2];$v=$token[0]===':'?($bind[substr($token,1)][0]??null):$token;$map[strtolower($p[1])][]=(string)$v;}$expected=['is_delete'=>self::$scenario==='PROBLEM_CREATE'?['1','0']:['1'],'store_id'=>['2147001111'],'staff_id'=>[(string)$staff],'set_date'=>['2097-11-11'],'user_id'=>[(string)$operator],'type'=>[(string)$type]];ksort($map);ksort($expected);if($map!==$expected||preg_match('/\b(?:OR|IN|EXISTS|SELECT)\b/i',$clauses))throw new RuntimeException('Exact zero-row UPDATE scope/binds');return;}
  [$table,$values]=self::values($sql,$bind);$expected=['staff_id'=>$staff,'set_date'=>'2097-11-11','user_id'=>$operator,'store_id'=>2147001111,'create_by'=>self::$case['create_by'],'create_time'=>null,'update_time'=>null];if($table==='nutrition_meals'){if(self::$masterInserts!==0)throw new RuntimeException('One new master only');$expected['type']=$type;$expected['other_text']=$body['other_text'];if($type===11){$expected['food_allergy']=$body['food_allergy'];$expected['food_disable']=$body['food_disable'];if(array_key_exists('food_Intolerance',$values))$expected['food_Intolerance']=$body['food_Intolerance'];elseif(array_key_exists('food_intolerance',$values))$expected['food_intolerance']=$body['food_Intolerance'];else if(getenv('INV_LANE')==='B')throw new RuntimeException('Original MySQL food camel field required');}}
  else{$answers=self::expectedAnswers();if(!self::$masterId||!isset($answers[self::$answerInserts]))throw new RuntimeException('Generated actual master/answer count');$expected['master_id']=self::$masterId;$expected=array_merge($expected,$answers[self::$answerInserts]);}
  if(array_keys(array_fill_keys(array_keys($values),true))!==array_keys(array_fill_keys(array_keys($expected),true))){$a=array_keys($values);$b=array_keys($expected);sort($a);sort($b);if($a!==$b)throw new RuntimeException('Exact phase1 INSERT columns');}
  foreach($expected as $k=>$v){if(!array_key_exists($k,$values))throw new RuntimeException('Missing approved write field');if($v===null){$time=is_string($values[$k])?strtotime($values[$k]):false;if($time===false||$time<self::$started-2||$time>time()+2)throw new RuntimeException('Original ORM datetime bound');}elseif((string)$values[$k]!== (string)$v)throw new RuntimeException('Exact INSERT value '.$k);}
 }
}
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{$ok=parent::execute($params);$r['success']=$ok;$r['rowCount']=$this->rowCount();if($kind==='INSERT'){if($r['rowCount']!==1)throw new RuntimeException('Exact INSERT affected1');InvGate::$lastTable=stripos($this->queryString,'nutrition_meals')!==false?'nutrition_meals':'nutrition_lack';if(InvGate::$lastTable==='nutrition_meals')InvGate::$masterInserts++;else InvGate::$answerInserts++;}elseif($kind==='UPDATE'){InvGate::$updates++;if($r['rowCount']!==0)throw new RuntimeException('Initial soft UPDATE affected0');}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(!in_array(InvGate::$scenario,['PROBLEM_CREATE','MEALS_CREATE','FOOD_CREATE'],true)||!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(!in_array(InvGate::$scenario,['PROBLEM_CREATE','MEALS_CREATE','FOOD_CREATE'],true))throw new RuntimeException('Only original write transaction');$ok=parent::beginTransaction();InvGate::record(['layer'=>'actual PDO.beginTransaction','success'=>$ok]);return $ok;}
 public function 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;}
