<?php
declare(strict_types=1);
class FLGate {
    public static $journal=[],$deny=false;
    public static $tables=['ydy_meal_order_supplement','ydy_user','ydy_restaurants','ydy_equipment','ydy_meal_order','ydy_config'];
    public static function sql(string $sql):string {
        if(self::$deny)throw new RuntimeException('Alias requires zero native SQL');
        if(preg_match('/^CALL SP_TABLEDEF\(\?, \?\)$/D',$sql))return 'metadata-definition';
        if(preg_match('/^SHOW (?:FULL )?COLUMNS FROM `?(ydy_[a-z_]+)`?$/iD',$sql,$m)&&in_array(strtolower($m[1]),self::$tables,true))return 'metadata-show';
        if(!preg_match('/^\s*SELECT\b/i',$sql))throw new RuntimeException('SELECT-only gate');
        $plain=preg_replace("/'(?:''|[^'])*'/s", "''",$sql);
        if(preg_match('/;|--|\/\*|\b(INSERT|UPDATE|DELETE|MERGE|DROP|ALTER|CREATE|TRUNCATE|CALL|EXEC|INTO|OUTFILE|SLEEP|BENCHMARK|DBMS_LOCK|DBLINK)\b/i',$plain))throw new RuntimeException('Unsafe SELECT');
        preg_match_all('/\b(?:FROM|JOIN)\s+([`"]?[A-Za-z_][A-Za-z_0-9]*[`"]?(?:\.[`"]?[A-Za-z_][A-Za-z_0-9]*[`"]?)?)/i',$plain,$matches);
        $allowed=array_merge(self::$tables,['user_tab_columns','user_constraints','user_cons_columns','user_objects']);
        foreach($matches[1]as $name){$name=strtolower(str_replace(['`','"'],'',$name));if(!in_array($name,$allowed,true))throw new RuntimeException('Unfrozen table');}
        if(!$matches[1])throw new RuntimeException('No frozen SELECT table');
        return 'select';
    }
    public static function callBinds(string $sql,array $binds):void {
        if(strpos($sql,'CALL ')!==0)return;
        if(count($binds)!==2||($binds['1']['value']??null)!=='ZHCT_APP'||($binds['1']['type']??null)!==PDO::PARAM_STR||($binds['2']['type']??null)!==PDO::PARAM_STR||!in_array(strtolower((string)($binds['2']['value']??'')),self::$tables,true))throw new RuntimeException('Definition call scope');
    }
}
class FLStatement extends PDOStatement {
    private $bound=[];protected function __construct(){}
    public function bindValue($parameter,$value,$type=PDO::PARAM_STR){$ok=parent::bindValue($parameter,$value,$type);$this->bound[(string)$parameter]=['value'=>$value,'type'=>$type,'php_type'=>gettype($value),'ok'=>$ok];return $ok;}
    public function execute($params=null){FLGate::sql($this->queryString);FLGate::callBinds($this->queryString,$this->bound);$r=['layer'=>'actual PDOStatement.execute','queryString'=>$this->queryString,'bound'=>$this->bound,'execute_params'=>$params,'success'=>false];try{$ok=parent::execute($params);$r['success']=$ok;$r['rowCount']=$this->rowCount();$r['errorInfo']=$this->errorInfo();FLGate::$journal[]=$r;return $ok;}catch(Throwable $e){$r['error']=['class'=>get_class($e),'message'=>$e->getMessage(),'errorInfo'=>$this->errorInfo()];FLGate::$journal[]=$r;throw $e;}}
}
class FLPdo extends PDO {
    public function prepare($sql,$options=[]){try{$op=FLGate::sql($sql);}catch(Throwable $e){FLGate::$journal[]=['layer'=>'native gate blocked before PDO.prepare','queryString'=>$sql,'error'=>$e->getMessage()];throw $e;}$r=['layer'=>'actual PDO.prepare','queryString'=>$sql,'operation'=>$op,'prepared'=>false];try{$s=parent::prepare($sql,$options);$r['prepared']=$s!==false;FLGate::$journal[]=$r;return $s;}catch(Throwable $e){$r['error']=['class'=>get_class($e),'message'=>$e->getMessage(),'errorInfo'=>$this->errorInfo()];FLGate::$journal[]=$r;throw $e;}}
    public function exec($sql){throw new RuntimeException('HTTP PDO.exec prohibited');}
    public function query($sql,...$args){FLGate::sql($sql);$r=['layer'=>'actual PDO.query','queryString'=>$sql,'success'=>false];try{$s=parent::query($sql,...$args);$r['success']=$s!==false;$r['rowCount']=$s?$s->rowCount():null;FLGate::$journal[]=$r;return $s;}catch(Throwable $e){$r['error']=['class'=>get_class($e),'message'=>$e->getMessage()];FLGate::$journal[]=$r;throw $e;}}
    public function beginTransaction(){throw new RuntimeException('HTTP transaction prohibited');}
}
class FLNoPdo extends FLPdo {public function __construct(){}}
