<?php
declare(strict_types=1);
require __DIR__.'/ShadowCore.php';
$n=0;
function same($e,$a): void {global $n; ++$n; if($e!==$a){throw new RuntimeException('assertion '.$n);}}
function rejects(callable $f): void {$bad=false; try{$f();}catch(InvalidArgumentException|UnexpectedValueException $e){$bad=true;} same(true,$bad);}
function rule(string $kind,int $p=0,int $s=0): array {return ['kind'=>$kind,'precision'=>$p,'scale'=>$s];}
same(PDO::FETCH_ASSOC,ShadowMode::row(null,PDO::FETCH_ASSOC));
same(PDO::FETCH_NUM,ShadowMode::row(0,PDO::FETCH_NUM));
foreach([PDO::FETCH_NUM,PDO::FETCH_ASSOC,PDO::FETCH_BOTH,PDO::FETCH_OBJ] as $mode){same($mode,ShadowMode::row($mode,PDO::FETCH_ASSOC));}
foreach([PDO::FETCH_CLASS,PDO::FETCH_COLUMN,PDO::FETCH_KEY_PAIR,PDO::FETCH_ASSOC|PDO::FETCH_GROUP,999] as $mode){rejects(function()use($mode){ShadowMode::row($mode,PDO::FETCH_ASSOC);});}
$dec=rule('decimal_text',30,2);
same('9007199254740993.01',ShadowValue::restore('9007199254740993.01',$dec));
same(null,ShadowValue::restore(null,$dec));
same('0.00',ShadowValue::restore('-0',$dec));
same('-1.20',ShadowValue::restore('-0001.2000',$dec));
same('18446744073709551615',ShadowValue::restore('18446744073709551615',rule('decimal_text',20,0)));
same('-12345678901234567890.123456789012345678',ShadowValue::restore('-12345678901234567890.123456789012345678',rule('decimal_text',38,18)));
foreach(['0','7','-7','9007199254740993',(string)PHP_INT_MAX,(string)PHP_INT_MIN] as $v){same((int)$v,ShadowValue::restore($v,rule('signed_integer')));}
same('18446744073709551615',ShadowValue::restore('18446744073709551615',rule('unsigned_integer')));
same(7,ShadowValue::restore('0007',rule('unsigned_integer')));
same(0,ShadowValue::restore('-0',rule('signed_integer')));
foreach([9007199254740994.0,1,false,'1e20','INF','1.001','12345678901234567890123456789.00'] as $v){rejects(function()use($v,$dec){ShadowValue::restore($v,$dec);});}
foreach(['9223372036854775808','-9223372036854775809','1.0'] as $v){rejects(function()use($v){ShadowValue::restore($v,rule('signed_integer'));});}
foreach(['-1','18446744073709551616','1e3'] as $v){rejects(function()use($v){ShadowValue::restore($v,rule('unsigned_integer'));});}
rejects(function(){ShadowValue::restore('1',rule('decimal_text',3,4));});
rejects(function(){ShadowValue::restore('1',rule('float'));});
rejects(function(){ShadowValue::restore('10.0',rule('decimal_text',2,1));});
same('0.001',ShadowValue::restore('0.001',rule('decimal_text',3,3)));
$sql='SELECT synthetic';
$spec=['sql'=>$sql,'names'=>['k','v','dm_shadow_0'],'visible'=>[0,1], 'restore'=>[1=>['shadow'=>2,'rule'=>$dec]]];
$plan=new ShadowPlan($spec);
same(hash('sha256',$sql),$plan->sqlHash());
same([7,'9007199254740993.01'],$plan->restore([7,9007199254740994.0,'9007199254740993.01']));
same([7,null],$plan->restore([7,null,null]));
same(['k'=>7,'v'=>'9007199254740993.01'],$plan->shape([7,'9007199254740993.01'],PDO::FETCH_ASSOC));
same([0=>7,'k'=>7,1=>'1.20','v'=>'1.20'],$plan->shape([7,'1.20'],PDO::FETCH_BOTH));
same(['k'=>7,'v'=>'1.20'],get_object_vars($plan->shape([7,'1.20'],PDO::FETCH_OBJ)));
same([7,'1.20'],$plan->shape([7,'1.20'],PDO::FETCH_NUM));
same(2,$plan->visibleCount()); same(1,$plan->rawIndex(1));
$meta=[['name'=>'k'],['name'=>'v'],['name'=>'dm_shadow_0','native_type'=>'varchar','precision'=>128]]; $plan->validateLayout($meta); same(true,true);
rejects(function()use($plan){$plan->validateLayout([['name'=>'k']]);});
rejects(function()use($plan,$meta){$meta[2]['name']='other';$plan->validateLayout($meta);});
rejects(function()use($plan,$meta){$meta[2]['native_type']='number';$plan->validateLayout($meta);});
rejects(function()use($plan,$meta){$meta[2]['precision']=8;$plan->validateLayout($meta);});
rejects(function()use($plan){$plan->restore([7,1.0,null]);});
rejects(function()use($plan){$plan->restore([7,null,'1.00']);});
rejects(function()use($plan){$plan->restore([7,1.0,1.0]);});
rejects(function()use($plan){$plan->rawIndex(2);});
rejects(function()use($plan){$plan->shape([7,'1.20'],PDO::FETCH_CLASS);});
$prefixSpec=$spec; $prefixSpec['names'][1]='dm_shadow_user'; $prefixPlan=new ShadowPlan($prefixSpec);
same(['k'=>7,'dm_shadow_user'=>'1.20'],$prefixPlan->shape([7,'1.20'],PDO::FETCH_ASSOC));
foreach([['visible'=>[0,0]],['visible'=>[0,3]],['restore'=>[1=>['shadow'=>1,'rule'=>$dec]]],['restore'=>[1=>['shadow'=>0,'rule'=>$dec]]],['names'=>['k','v','v']],['visible'=>[0]],['names'=>['k','7','dm_shadow_0']]] as $change){rejects(function()use($spec,$change){new ShadowPlan(array_replace($spec,$change));});}
$registry=new ShadowRegistry([$plan]);same($plan,$registry->find($sql));same(null,$registry->find($sql.' '));same(null,$registry->find('SELECT dm_shadow_0'));
rejects(function()use($plan){new ShadowRegistry([$plan,$plan]);});
// Pure boundary ordering only, NOT a real ORM/getter proof.
$row=$plan->shape($plan->restore([7,9007199254740994.0,'9007199254740993.01']),PDO::FETCH_ASSOC);
$upstream=function($v){same('string',gettype($v));same('9007199254740993.01',$v);};$upstream($row['v']);
echo json_encode(['status'=>'pass','php'=>PHP_VERSION,'assertions'=>$n,'database_requests'=>0]).PHP_EOL;
