<?php
declare(strict_types=1);
function smPdo():PDO{
    $lane=getenv('SM7_LANE');$parent=getenv('SM7_PARENT');$cfg=require $parent.'/application/database.php';
    if($lane==='B'){$local=require '/workspace/zhct/zhctproject/store/application/config/local/database.php';foreach(['hostname','database','username','password'] as $k){if($cfg[$k]!==$local[$k]){throw new RuntimeException('Not code local');}}if((int)($cfg['hostport']??3306)!==(int)($local['hostport']??3306)){throw new RuntimeException('Local port drift');}$dsn='mysql:host='.$cfg['hostname'].';port='.(int)($cfg['hostport']??3306).';dbname='.$cfg['database'].';charset='.($cfg['charset']??'utf8');$options=[PDO::ATTR_STRINGIFY_FETCHES=>false,PDO::ATTR_EMULATE_PREPARES=>false];}
    else{if($cfg['hostname']!=='127.0.0.1'||(string)$cfg['hostport']!=='5237'||$cfg['database']!=='ZHCT_APP'){throw new RuntimeException('Nonproduction DM gate');}$dsn='dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';$options=[];}
    $pdo=new PDO($dsn,$cfg['username'],$cfg['password'],$options+[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);
    if($lane==='B'&&(strpos($pdo->query('SELECT VERSION()')->fetchColumn(),'5.6.')!==0||$pdo->query('SELECT DATABASE()')->fetchColumn()!==$cfg['database'])){throw new RuntimeException('Local effective identity');}
    if($lane==='C'&&$pdo->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()!=='ZHCT_APP'){throw new RuntimeException('DM effective identity');}
    return $pdo;
}
function smRows(PDO $db,string $sql,array $args=[]):array{
    if(!preg_match('/^(SELECT|SHOW)\b/i',$sql)){throw new RuntimeException('Read-only source helper');}$s=$db->prepare($sql);$s->execute($args);$out=[];
    while($r=$s->fetch(PDO::FETCH_ASSOC)){$r=array_change_key_case($r,CASE_LOWER);foreach($r as &$v){if(is_resource($v)){$v=stream_get_contents($v);}}unset($v);$out[]=$r;if(count($out)>20000){throw new RuntimeException('Bounded source cap');}}
    return $out;
}
function smTop(string $sql,int $n):string{return getenv('SM7_LANE')==='B'?$sql.' LIMIT '.$n:'SELECT * FROM ('.$sql.') WHERE ROWNUM<='.$n;}
function smDigest(array $rows):array{$parts=[];foreach($rows as $r){ksort($r);$parts[]=hash('sha256',json_encode($r,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR));}sort($parts);return ['rows'=>count($rows),'sha256'=>hash('sha256',implode("\n",$parts))];}
function smUserDigest(PDO $db,int $exclude=0):array{
    $s=$db->prepare('SELECT * FROM ydy_user'.($exclude?' WHERE id<>?':''));$s->execute($exclude?[$exclude]:[]);$parts=[];
    while($r=$s->fetch(PDO::FETCH_ASSOC)){$r=array_change_key_case($r,CASE_LOWER);foreach($r as &$v){if(is_resource($v)){$v=stream_get_contents($v);}}unset($v);ksort($r);$parts[]=hash('sha256',json_encode($r,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR));if(count($parts)>1000000){throw new RuntimeException('User digest bound');}}
    sort($parts);return ['rows'=>count($parts),'sha256'=>hash('sha256',implode("\n",$parts))];
}
function smSave(string $name,$data):void{$dir=getenv('SM7_RUNTIME');if(!is_dir($dir)){mkdir($dir,0700,true);}$path=$dir.'/'.$name;file_put_contents($path,json_encode($data,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR));chmod($path,0600);}
function smLoad(string $name):array{return json_decode(file_get_contents(getenv('SM7_RUNTIME').'/'.$name),true,512,JSON_THROW_ON_ERROR);}
function smGuid():string{$h=strtoupper(bin2hex(random_bytes(16)));return substr($h,0,8).'-'.substr($h,8,4).'-4'.substr($h,13,3).'-A'.substr($h,17,3).'-'.substr($h,20,12);}
