<?php
/* Bounded native recorder adapted from sealed Core NativePdo and Store Activity reflection recorder. */
final class PPGate {
 public static $generated=[],$orderNo='',$last=[],$counts=[];
 public static $tables=['AI'=>['store','store_setting','upload_file','user','user_oauth','user_subsidy','account_settlement','user_balance_log'],'ZHCT'=>['equipment','user','staff','restaurants','prepay_order','meal_order','staff_department','department','config','one_card_account_map','consume_limit','consume_limit_department','consume_limit_scenario']];
 public static function kind($db,$sql){$plain=preg_replace("/'(?:''|\\\\.|[^'\\\\])*'/s",'?',trim($sql));pp_need(!preg_match('/;|--|\/\*|\*\/|#|\b(?:DELETE|REPLACE|MERGE|CREATE|ALTER|DROP|TRUNCATE|GRANT|REVOKE|OUTFILE|DUMPFILE|LOAD|EXEC|EXECUTE|SLEEP|BENCHMARK|UTL_HTTP)\b/i',$plain),'Forbidden native SQL');if(preg_match('/^SELECT\b/i',$sql)){pp_need(!preg_match('/\bINTO\b/i',$plain),'No SELECT INTO');preg_match_all('/\b(yoshop|ydy)_([a-z0-9_]+)/i',$sql,$m,PREG_SET_ORDER);foreach($m as $r)pp_need(($db==='AI'?strtolower($r[1])==='yoshop':strtolower($r[1])==='ydy')&&in_array(strtolower($r[2]),self::$tables[$db],true),'Known native SELECT table');return 'READ';}if(preg_match('/^SHOW FULL COLUMNS FROM `((yoshop|ydy)_([a-z_]+))`$/D',$sql,$m)){pp_need(($db==='AI'?$m[2]==='yoshop':$m[2]==='ydy')&&in_array($m[3],self::$tables[$db],true),'Exact source column metadata');return 'READ';}if($sql==='CALL SP_TABLEDEF(?, ?)')return 'READ';if(preg_match('/^(INSERT INTO|UPDATE)\s+[`"]?((?:yoshop|ydy)_[a-z_]+)[`"]?/i',$sql,$m)){pp_need($db==='ZHCT'&&in_array(strtolower($m[2]),['ydy_prepay_order','ydy_meal_order'],true),'Only own code/scan write tables');return strtoupper($m[1])==='UPDATE'?'UPDATE':'INSERT';}throw new RuntimeException('Unknown native SQL shape');}
 public static function plan($db,$sql,$bind){$kind=self::kind($db,$sql);if($kind==='READ'){if($sql==='CALL SP_TABLEDEF(?, ?)'){$values=array_map(static function($v){return is_array($v)?$v[0]:$v;},array_values($bind));pp_need(count($values)===2&&$values[0]===($db==='AI'?'AIZHCT_APP':'ZHCT_APP')&&in_array(strtolower($values[1]),array_map(static function($t)use($db){return ($db==='AI'?'yoshop_':'ydy_').$t;},self::$tables[$db]),true),'Exact source metadata CALL');}return $kind;}
  $phase=getenv('PP_PHASE');pp_need(in_array($phase,['code','scan'],true),'No native DML in warm/Auth');$clean=preg_replace('/[`"]([a-zA-Z_][a-zA-Z0-9_]*)[`"]/','$1',$sql);$clean=preg_replace('/\s+/',' ',trim($clean));$ids=pp_input('baseline.json')['ids'];$f=$phase==='code'?pp_saved('DISPATCH_1.json')['ordered_fields']:null;$v=[];
  if($kind==='INSERT'){pp_need(preg_match('/^INSERT INTO (\w+) (?:SET (.+)|\(([^)]+)\) VALUES \(([^)]+)\))$/i',$clean,$m)===1,'Single bound INSERT');$table=strtolower($m[1]);$pairs=[];if(!empty($m[2]))foreach(explode(',',$m[2])as $p){pp_need(preg_match('/^\s*(\w+)\s*=\s*(:\w+)\s*$/D',$p,$a),'Bound SET INSERT');$pairs[]=[$a[1],$a[2]];}else{$ks=explode(',',$m[3]);$vs=explode(',',$m[4]);pp_need(count($ks)===count($vs),'INSERT arity');foreach($ks as $i=>$k)$pairs[]=[trim($k),trim($vs[$i])];}foreach($pairs as [$k,$token]){pp_need(preg_match('/^:\w+$/D',$token)&&isset($bind[ltrim($token,':')])&&!isset($v[strtolower($k)]),'Single named native bind');$v[strtolower($k)]=$bind[ltrim($token,':')][0];}
   if($phase==='code'){$e=pp_saved('equipment.json')['id'];pp_need($table==='ydy_prepay_order','Code only Prepay INSERT');$expected=['source'=>1,'device_order_no'=>$f['orderid'],'machid'=>$f['machid'],'equipment_id'=>$e,'restaurant_id'=>$ids['STORE_R1'],'trackno'=>$f['trackno'],'goods_name'=>$f['name'],'price'=>1,'channelid'=>$f['channelid'],'prepay_status'=>2,'ship_status'=>0,'raw_request_a'=>json_encode($f,JSON_UNESCAPED_UNICODE),'qrcode_payload'=>'','expire_time'=>'@expire','create_by'=>'api','update_by'=>'api'];}
   else{$actor=pp_input('baseline.json')['own16']['STORE_U1'][0];pp_need($table==='ydy_meal_order','Scan only Meal INSERT');$expected=['order_no'=>'@no','total_price'=>'0.01','ori_total_price'=>'0.01','order_price'=>'0.01','pay_price'=>'0.01','pay_method'=>'balance','pay_status'=>10,'pay_time'=>'2000-01-01 00:00:00','meal_date'=>date('Y-m-d'),'meal_times'=>((int)date('H')<10?1:((int)date('H')<16?2:3)),'meal_number'=>1,'bind_id'=>0,'bind_time'=>'@time','equipment_code'=>'CDXPPFORM11_E1','last_meal_time'=>'@time','source'=>8,'user_id'=>$ids['STORE_U1'],'restaurant_id'=>$ids['STORE_R1'],'user_mobile'=>(string)$actor['mobile'],'user_name'=>(string)($actor['name']??$actor['nickname']??''),'restaurant_method'=>1,'remark'=>'CDXPPFORM11_COFFEE','order_status'=>10,'create_time'=>'@time','update_time'=>'@time'];self::$orderNo=$v['order_no'];}pp_fields($v,$expected);
  }else{pp_need(preg_match('/^UPDATE (\w+) SET (.+?) WHERE (.+)$/i',$clean,$m),'Single own UPDATE');$table=strtolower($m[1]);foreach(explode(',',$m[2])as $p){pp_need(preg_match('/^\s*(\w+)\s*=\s*(:\w+)\s*$/D',$p,$a)&&isset($bind[ltrim($a[2],':')]),'Bound UPDATE only');$v[strtolower($a[1])]=$bind[ltrim($a[2],':')][0];}$where=pp_where($m[3],$bind);pp_need($table==='ydy_prepay_order','Only new Prepay UPDATE');if($phase==='code'){pp_fields($where,['id'=>self::$generated['ydy_prepay_order']]);pp_fields($v,['raw_request_a'=>json_encode($f,JSON_UNESCAPED_UNICODE),'qrcode_payload'=>'type=200&torderid='.self::$generated['ydy_prepay_order'],'update_by'=>'api']);}else{pp_fields($where,['id'=>pp_saved('code-checkpoint.json')['id'],'meal_order_id'=>0]);pp_fields($v,['meal_order_id'=>self::$generated['ydy_meal_order'],'meal_order_no'=>self::$orderNo,'user_id'=>$ids['STORE_U1'],'update_by'=>'miniapp']);}}
  pp_need(empty(self::$counts[$kind.'.'.$table]),'No source DML retry/replay');self::$last[$db]=$table;return $kind;
 }
}
class PPStatement extends PDOStatement {
 private $bound=[],$db='';protected function __construct($db){$this->db=$db;}
 public function bindValue($p,$v,$t=PDO::PARAM_STR){$ok=parent::bindValue($p,$v,$t);$this->bound[ltrim((string)$p,':')]=[$v,$t];return $ok;}
 public function bindParam($p,&$v,$t=PDO::PARAM_STR,$len=0,$opts=null){throw new RuntimeException('Unapproved bindParam');}
 public function execute($params=null){pp_need($params===null||$params===[]||$this->queryString==='CALL SP_TABLEDEF(?, ?)','Only exact original metadata execute-array');$effective=($params!==null&&$params!==[])?$params:$this->bound;$kind=PPGate::plan($this->db,$this->queryString,$effective);$phase=getenv('PP_PHASE');$budget=null;if($kind==='READ'){$caps=getenv('PP_SIDE')==='B'?['auth'=>16,'code'=>24,'scan'=>30]:['auth'=>32,'code'=>48,'scan'=>60];$budget=pp_budget($phase,$caps[$phase]??100);}else{PPGate::$counts[$kind.'.'.PPGate::$last[$this->db]]=1;}pp_event(['event'=>'native-before','db'=>$this->db,'SQL'=>$this->queryString,'native_binds'=>$this->bound,'execute_array'=>$params,'kind'=>$kind,'budget'=>$budget]);try{$ok=parent::execute($params);pp_event(['event'=>'native-result','db'=>$this->db,'SQL'=>$this->queryString,'kind'=>$kind,'success'=>$ok,'errorInfo'=>$this->errorInfo(),'rowCount'=>$this->rowCount()]);if($kind!=='READ')pp_need($this->rowCount()===1,'Exactly one source affected row');return $ok;}catch(Throwable $e){pp_event(['event'=>'native-error','db'=>$this->db,'SQL'=>$this->queryString,'class'=>get_class($e),'message'=>$e->getMessage(),'errorInfo'=>$this->errorInfo()]);throw $e;}}
}
class PPPDO extends PDO {
 public $db;
 public function query($sql,...$args){$s=$this->prepare($sql);$s->execute();if($args)$s->setFetchMode(...$args);return $s;}
 public function prepare($sql,$options=[]){PPGate::kind($this->db,$sql);return parent::prepare($sql,$options);}
 public function exec($sql){pp_need(getenv('PP_PHASE')==='scan'&&preg_match('/^(SAVEPOINT trans[2-9]|ROLLBACK TO SAVEPOINT trans[2-9])$/D',$sql),'Only original scan savepoint');pp_event(['event'=>'txn-before','SQL'=>$sql]);$n=parent::exec($sql);pp_event(['event'=>'txn-result','SQL'=>$sql,'rowCount'=>$n]);return $n;}
 public function beginTransaction(){pp_need(getenv('PP_PHASE')==='scan'&&$this->db==='ZHCT','Only scan native transaction');pp_event(['event'=>'txn-before','operation'=>'begin']);$ok=parent::beginTransaction();pp_event(['event'=>'txn-result','operation'=>'begin','success'=>$ok]);return $ok;}
 public function commit(){pp_event(['event'=>'txn-before','operation'=>'commit']);$ok=parent::commit();pp_event(['event'=>'txn-result','operation'=>'commit','success'=>$ok]);return $ok;}
 public function rollBack(){pp_event(['event'=>'txn-before','operation'=>'rollback']);$ok=parent::rollBack();pp_event(['event'=>'txn-result','operation'=>'rollback','success'=>$ok]);return $ok;}
 public function lastInsertId($name=null){$id=parent::lastInsertId($name);pp_need(ctype_digit((string)$id)&&(int)$id>0,'Actual generated identity');$table=PPGate::$last[$this->db];PPGate::$generated[$table]=(int)$id;pp_event(['event'=>'generated-ID','db'=>$this->db,'table'=>$table,'value'=>$id,'PHP_type'=>gettype($id)]);return $id;}
}
