<?php
declare(strict_types=1);
ini_set('display_errors','0');ini_set('log_errors','0');error_reporting(E_ALL);
set_error_handler(static function($severity){throw new ErrorException('Sanitized PHP warning',0,$severity);});
$stage='start';$checks=0;$executions=0;$sqlHashes=[];
function sameIter($expected,$actual):void{global $checks;++$checks;if($expected!==$actual)throw new RuntimeException('Iterator assertion '.$checks);}
function rowsIter(PDOStatement $s):array{
    $out=[];foreach($s as $key=>$row){$out[$key]=is_object($row)?['object'=>get_class($row),'fields'=>get_object_vars($row)]:$row;}return $out;
}
function shapeIter(array $values,array $names,int $mode){
    if($mode===PDO::FETCH_NUM)return $values;
    $out=[];foreach($names as $i=>$name){if($mode===PDO::FETCH_BOTH)$out[$i]=$values[$i];$out[$name]=$values[$i];}
    return $mode===PDO::FETCH_OBJ?['object'=>'stdClass','fields'=>$out]:$out;
}
function executeIter(PDOStatement $s,array $values=[]):void{
    global $executions;foreach($values as $i=>$v)$s->bindValue($i+1,$v,$v===null?PDO::PARAM_NULL:PDO::PARAM_STR);
    sameIter(true,$s->execute());++$executions;
}
try{
    require __DIR__.'/../old-driver-shadow-fetch-prototype/ProbeRuntime.php';
    require __DIR__.'/../old-driver-shadow-fetch-prototype/SyntheticFixtures.php';
    require __DIR__.'/../old-driver-shadow-fetch-prototype/ShadowStatement.php';
    require __DIR__.'/IteratorShadowStatement.php';
    $class=($argv[1]??'')==='old'?ShadowStatement::class:IteratorShadowStatement::class;
    $stage='runtime';$identity=runtimeIdentity();$fixtures=syntheticFixtures();$rejections=syntheticRejections();
    $registry=new ShadowRegistry(array_merge(array_column($fixtures,'plan'),array_values($rejections)));
    $pdo=probePdo($class,[$registry,PDO::FETCH_ASSOC]);$native=probePdo();
    $identity['mapped']=mappedComponents($identity);$target=independentTarget($pdo);$nativeTarget=independentTarget($native);
    $prepare=static function(string $sql)use($pdo):PDOStatement{global $sqlHashes;$sqlHashes[]=hash('sha256',$sql);return $pdo->prepare($sql);};
    $modes=[PDO::FETCH_ASSOC,PDO::FETCH_NUM,PDO::FETCH_BOTH,PDO::FETCH_OBJ];
    foreach($fixtures as $name=>$fixture){if(!isset($fixture['cases']))continue;
        foreach($fixture['cases'] as $i=>$pair){foreach($modes as $mode){
            $stage='planned_'.$name.'_'.$i.'_'.$mode;$s=$prepare($fixture['plan']->sql());executeIter($s,[$pair[0]]);sameIter(true,$s->setFetchMode($mode));
            sameIter([shapeIter([$pair[1]],['value'],$mode)],rowsIter($s));sameIter(false,$s->fetch());sameIter([],rowsIter($s));
        }}
    }
    $values=['9007199254740993.01',null];$pairSql=$fixtures['pair']['plan']->sql();
    foreach($modes as $mode){
        $stage='pair_mode_'.$mode;$s=$prepare($pairSql);executeIter($s,$values);$s->setFetchMode($mode);
        sameIter([shapeIter([1,$values[0]],['k','v'],$mode),shapeIter([2,null],['k','v'],$mode)],rowsIter($s));
        $stage='empty_mode_'.$mode;$s=$prepare($fixtures['empty']['plan']->sql());executeIter($s,$values);$s->setFetchMode($mode);sameIter([],rowsIter($s));
    }
    $stage='partial_fetch';$s=$prepare($pairSql);executeIter($s,$values);sameIter(['k'=>1,'v'=>$values[0]],$s->fetch());sameIter([['k'=>2,'v'=>null]],rowsIter($s));
    $stage='break_then_fetch';executeIter($s,$values);foreach($s as $row){sameIter(['k'=>1,'v'=>$values[0]],$row);break;}sameIter(['k'=>2,'v'=>null],$s->fetch());sameIter(false,$s->fetch());
    $stage='close_repeat_execute';sameIter(true,$s->closeCursor());executeIter($s,$values);sameIter([['k'=>1,'v'=>$values[0]],['k'=>2,'v'=>null]],rowsIter($s));
    executeIter($s,['1.20','-2.30']);sameIter([['k'=>1,'v'=>'1.20'],['k'=>2,'v'=>'-2.30']],rowsIter($s));
    $stage='concurrent_statements';$other=$prepare($pairSql);executeIter($s,$values);executeIter($other,['1.20','-2.30']);sameIter([['k'=>1,'v'=>'1.20'],['k'=>2,'v'=>'-2.30']],rowsIter($other));sameIter([['k'=>1,'v'=>$values[0]],['k'=>2,'v'=>null]],rowsIter($s));
    $stage='unplanned_native_passthrough';$unknown="SELECT 7 AS K,'007' AS V FROM DUAL UNION ALL SELECT 8 AS K,'008' AS V FROM DUAL";
    foreach(array_merge($modes,[PDO::FETCH_NAMED,PDO::FETCH_COLUMN,PDO::FETCH_CLASS]) as $mode){
        $stage='unplanned_mode_'.$mode;$a=$native->prepare($unknown);$b=$prepare($unknown);executeIter($a);executeIter($b);
        foreach([$a,$b] as $x){if($mode===PDO::FETCH_COLUMN)$x->setFetchMode($mode,1);elseif($mode===PDO::FETCH_CLASS)$x->setFetchMode($mode,'stdClass');else $x->setFetchMode($mode);}
        sameIter(rowsIter($a),rowsIter($b));sameIter($a->fetch(),$b->fetch());sameIter(rowsIter($a),rowsIter($b));
        executeIter($a);executeIter($b);$a->fetch();$b->fetch();sameIter(rowsIter($a),rowsIter($b));
    }
    $stage='invalid_plans_fail_closed';foreach($rejections as $fixture){$s=$prepare($fixture->sql());executeIter($s);$rejected=false;try{rowsIter($s);}catch(InvalidArgumentException|UnexpectedValueException $e){$rejected=true;}sameIter(true,$rejected);}
    echo json_encode(['status'=>'pass','stage'=>'complete','class'=>$class,'assertions'=>$checks,'execute_calls'=>$executions,'identity'=>$identity,'target'=>$target,'native_target'=>$nativeTarget,'prepared_sql_sha256'=>$sqlHashes,'business_reads'=>0,'dml'=>0,'ddl'=>0,'global_precision_gate_closed'=>false],JSON_UNESCAPED_SLASHES).PHP_EOL;
}catch(Throwable $e){echo json_encode(['status'=>'fail','stage'=>$stage,'class'=>get_class($e),'assertions'=>$checks,'execute_calls'=>$executions,'business_reads'=>0,'dml'=>0,'ddl'=>0,'global_precision_gate_closed'=>false]).PHP_EOL;exit(1);}
