<?php
declare(strict_types=1);
final class ActivityGate {
 public static $journal=[],$case=[],$dml=0,$dry=false,$lane='';
 public static function sql($sql,$schema){
  if(preg_match('/^CALL SP_TABLEDEF\(\?, \?\)$/D',$sql))return 'metadata';
  if(preg_match('/^SHOW (?:FULL )?COLUMNS FROM [`"]?(ydy_user|ydy_config|yoshop_activity|yoshop_activity_type|yoshop_activity_register)[`"]?$/iD',$sql))return 'metadata';
  $plain=preg_replace("/'(?:''|[^'])*'/s","''",$sql);if(preg_match('/;|--|\/\*|\b(MERGE|DROP|ALTER|CREATE|TRUNCATE|CALL|EXEC|OUTFILE|SLEEP|BENCHMARK|DBLINK)\b/i',$plain))throw new RuntimeException('Unsafe native SQL');
  if(preg_match('/^\s*SELECT\b/i',$sql)){$matches=[];preg_match_all('/\b(?:FROM|JOIN)\s+([`"]?[a-z_][a-z_0-9]*[`"]?)/i',$plain,$matches);$tables=['ydy_user','ydy_config','yoshop_activity','yoshop_activity_type','yoshop_activity_register','user_tab_columns','user_constraints','user_cons_columns','user_objects','dual'];foreach($matches[1]as $t)if(!in_array(strtolower(str_replace(['`','"'],'',$t)),$tables,true))throw new RuntimeException('Native SELECT table');return 'read';}
  if($schema!=='ai')throw new RuntimeException('No Store DML');return 'write';
 }
 public static function execute($sql,$binds,$params,$schema){
  if($params!==null)throw new RuntimeException('Explicit native binds');$op=self::sql($sql,$schema);
  if($op==='metadata'&&strpos($sql,'CALL ')===0){$v=array_column(array_values($binds),'value');$allow=$schema==='ai'?['AIZHCT_APP',['YOSHOP_ACTIVITY','YOSHOP_ACTIVITY_TYPE','YOSHOP_ACTIVITY_REGISTER']]:['ZHCT_APP',['YDY_USER','YDY_CONFIG']];if(count($v)!==2||strtoupper($v[0])!==$allow[0]||!in_array(strtoupper($v[1]),$allow[1],true))throw new RuntimeException('Metadata scope');}
  if($op!=='write'){if(!self::$dry)($GLOBALS['ACT_BUDGET'])('business-read',600);return $op;}
  if(self::$dml!==0||empty(self::$case['allowed']))throw new RuntimeException('Single planned DML only');
  $s=strtolower(preg_replace('/\s+/',' ',str_replace(['`','"'],'',trim($sql))));$data=[];$w=null;$kind='';$used=[];
  $lookup=array_change_key_case($binds,CASE_LOWER);$resolve=static function($v)use($lookup,&$used){$v=trim($v);if(!preg_match('/^:[a-z_0-9]+$/D',$v)||!array_key_exists($v,$lookup))throw new RuntimeException('Only bound values');$used[]=$v;return $lookup[$v]['value'];};
  if(preg_match('/^insert into yoshop_activity \(([^)]+)\) values \(([^)]+)\)$/D',$s,$m)){$kind='insert';$keys=array_map('trim',explode(',',$m[1]));$values=array_map($resolve,explode(',',$m[2]));if(count($keys)!==count($values))throw new RuntimeException('Insert arity');$data=array_combine($keys,$values);}
  elseif(preg_match('/^update yoshop_activity set (.+) where uuid = (:[a-z_0-9]+)(?: and status <> (:[a-z_0-9]+))?$/D',$s,$m)){$kind='update';foreach(explode(',',$m[1])as $p){if(!preg_match('/^\s*([a-z_]+)\s*=\s*(:[a-z_0-9]+)$/D',$p,$x))throw new RuntimeException('Update pair');$data[$x[1]]=$resolve($x[2]);}$w=$resolve($m[2]);$needs=self::$lane==='C'&&(self::$case['method']??'')==='able';if($needs!==isset($m[3]))throw new RuntimeException('Canonical DM able predicate required only there');if($needs&&$resolve($m[3])!==0)throw new RuntimeException('Exact integer status exclusion');}
  elseif(preg_match('/^delete from yoshop_activity where uuid in \((:[a-z_0-9]+)\)$/D',$s,$m)){$kind='delete';$w=$resolve($m[1]);}
  else throw new RuntimeException('Exact activity native grammar');
  $a=self::$case['allowed'];if($a['kind']!==$kind||($kind!=='insert'&&$w!==$a['uuid']))throw new RuntimeException('Own exact UUID predicate');
  if($kind==='insert'){if(!isset($data['uuid'],$data['create_time'])||!preg_match('/^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$/D',$data['uuid'])||abs(time()-strtotime($data['create_time']))>90)throw new RuntimeException('Native generated lifecycle');unset($data['uuid'],$data['create_time']);}
  $expected=$a['data'];ksort($expected);ksort($data);if($data!==$expected)throw new RuntimeException('Exact planned field values');
  if(count($used)!==count($binds)||count(array_unique($used))!==count($binds))throw new RuntimeException('All native binds accounted');
  foreach($binds as $key=>$b){$f=preg_replace('/^:data__|^:where_AND_/','',$key);$want=in_array($f,['type_id','status','store_id'],true)?PDO::PARAM_INT:PDO::PARAM_STR;if($b['type']!==$want||!in_array($b['php_type'],['integer','string'],true)||$b['ok']!==true)throw new RuntimeException('Original typed native bind');}
  if(!self::$dry)($GLOBALS['ACT_BUDGET'])('DML-'.$kind,['insert'=>1,'update'=>3,'delete'=>1][$kind]);self::$dml++;return 'write';
 }
}
class ActStatement extends PDOStatement {
 private $bound=[],$schema;protected function __construct($schema){$this->schema=$schema;}
 public function bindValue($p,$v,$t=PDO::PARAM_STR){$ok=parent::bindValue($p,$v,$t);$this->bound[(string)$p]=['value'=>$v,'type'=>$t,'php_type'=>gettype($v),'ok'=>$ok];return $ok;}
 public function execute($params=null){$r=['schema'=>$this->schema,'sql'=>$this->queryString,'binds'=>$this->bound,'execute_parameters'=>$params,'success'=>false];try{ActivityGate::execute($this->queryString,$this->bound,$params,$this->schema);$ok=parent::execute($params);$r['success']=$ok;$r['affected']=$this->rowCount();$r['errorInfo']=$this->errorInfo();return $ok;}catch(Throwable $e){$r['error']=['class'=>get_class($e),'message'=>$e->getMessage()];throw $e;}finally{ActivityGate::$journal[]=$r;}}
}
class ActPDO extends PDO {
 public $schema;
 public function prepare($sql,$options=[]){ActivityGate::sql($sql,$this->schema);return parent::prepare($sql,$options);}
 public function query($sql,...$args){ActivityGate::execute($sql,[],null,$this->schema);$s=parent::query($sql,...$args);ActivityGate::$journal[]=['schema'=>$this->schema,'sql'=>$sql,'query'=>true,'success'=>$s!==false];return $s;}
 public function exec($sql){throw new RuntimeException('No direct exec');}
 public function beginTransaction(){throw new RuntimeException('No added transaction');}
 public function lastInsertId($name=null){$value=parent::lastInsertId($name);ActivityGate::$journal[]=['layer'=>'PDO.lastInsertId','schema'=>$this->schema,'sequence'=>$name,'value'=>$value,'php_type'=>gettype($value),'connection_id'=>spl_object_hash($this)];return $value;}
}
