<?php
declare(strict_types=1);
require_once __DIR__.'/common.php';
const N6_TABLES=['equipment','equipment_version','recipes','dishes','dishes_cate','staff','staff_department','department','date_menu','config'];
const N6_TYPES=[2,3,4,7,10];
function n6Sha():array{$out=[];foreach(file(__DIR__.'/SOURCE_SHA256.txt',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) as $line){[$sha,$file]=preg_split('/\s+/',$line,2);if(hash_file('sha256',getenv('SM7_ROOT').'/'.$file)!==$sha){throw new RuntimeException('Frozen source changed: '.$file);}$out[$file]=$sha;}return $out;}
function n6Metadata(PDO $db):array{$dm=getenv('SM7_LANE')==='C';$out=[];foreach(N6_TABLES as $t){$out[$t]=smRows($db,$dm?'SELECT COLUMN_NAME,DATA_TYPE,DATA_PRECISION,DATA_SCALE,NULLABLE FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID':'SELECT COLUMN_NAME,DATA_TYPE,COLUMN_TYPE,NUMERIC_PRECISION,NUMERIC_SCALE,DATETIME_PRECISION,IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION',[$dm?strtoupper('ydy_'.$t):'ydy_'.$t]);}return $out;}
function n6Actor(PDO $db):array{$a=smLoad('actor-private.json');$h=smLoad('h7-preflight-private.json');$rows=smRows($db,'SELECT * FROM ydy_user WHERE id=? AND uuid=? AND account=? AND create_by=?',[$a['id'],$a['uuid'],$a['marker'],$a['marker']]);if(smDigest($rows)!==$h['actor_digest']){throw new RuntimeException('Owned actor drift');}return $rows;}
function n6Fields(string $table,string $alias=''):string{
    if(!in_array($table,N6_TABLES,true)||($alias!==''&&!preg_match('/^[abds]$/D',$alias))){throw new RuntimeException('Projection identifier boundary');}$meta=smLoad('mysql-columns-private.json');$native=smLoad('metadata-private.json');$source=[];foreach($meta[$table] as $col){$source[strtolower($col['column_name'])]=$col;}$fields=[];
    foreach($native[$table] as $col){$name=strtolower($col['column_name']);if(!preg_match('/^[a-z][a-z0-9_]*$/D',$name)){throw new RuntimeException('Unexpected column');}$q=getenv('SM7_LANE')==='C'?'"'.strtoupper($name).'"':'`'.$name.'`';$expr=($alias!==''?$alias.'.':'').$q;
        // Independent exact textual truth, never formatting an already rounded PDO float.
        if(getenv('SM7_LANE')==='C'&&isset($source[$name])&&$source[$name]['data_type']==='decimal'){$expr='CAST('.$expr.' AS VARCHAR(200))';}
        $fields[]=$expr.' AS '.$q;
    }return implode(',',$fields);
}
function n6Rows(PDO $db,string $table,string $where='',array $args=[],string $order='',int $limit=0):array{
    $sql='SELECT '.n6Fields($table).' FROM ydy_'.$table.($where!==''?' WHERE '.$where:'').($order!==''?' ORDER BY '.$order:'');if($limit){$sql=smTop($sql,$limit);}$rows=smRows($db,$sql,$args);if(count($rows)>100){throw new RuntimeException('Consumer set exceeds 100');}return n6SourceDate($table,n6SourceDecimal($table,$rows));
}
function n6SourceDecimal(string $table,array $rows):array{
    if(getenv('SM7_LANE')!=='C'){return $rows;}$contract=smLoad('mysql-columns-private.json');foreach($contract[$table] as $c){if($c['data_type']!=='decimal'){continue;}$name=strtolower($c['column_name']);$scale=(int)$c['numeric_scale'];foreach($rows as &$row){if(!array_key_exists($name,$row)||$row[$name]===null){continue;}$v=$row[$name];if(!is_string($v)||!preg_match('/^-?\d+(?:\.\d+)?$/D',$v)){throw new RuntimeException('Exact DECIMAL text truth missing');}$fixed=bcadd($v,'0',$scale);if(bccomp($fixed,$v,max($scale,strlen(strrchr($v,'.')?:'')))!==0){throw new RuntimeException('Target decimal outside source scale contract');}$row[$name]=$fixed;}unset($row);}return $rows;
}
function n6SourceDate(string $table,array $rows):array{
    if(getenv('SM7_LANE')!=='C'){return $rows;}$contract=smLoad('mysql-columns-private.json');$columns=[];foreach($contract[$table] as $c){$columns[strtolower($c['column_name'])]=$c;}
    foreach($rows as &$row){foreach($row as $k=>&$v){$c=$columns[$k]??null;if($c&&is_string($v)&&in_array($c['data_type'],['datetime','timestamp'],true)&&(int)$c['datetime_precision']===0&&preg_match('/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\.0+$/D',$v,$m)){$v=$m[1];}}unset($v);}unset($row);return $rows;
}
function n6Oss(PDO $db):array{$rows=n6Rows($db,'config',"status=1 AND config_key IN ('ossDisjunctor','ossHost','businessIdentify')",[],'id');$values=[];foreach($rows as $r){if(array_key_exists($r['config_key'],$values)){throw new RuntimeException('Ambiguous URL config');}$values[$r['config_key']]=$r['config_value'];}return ['rows'=>$rows,'prefix'=>($values['ossHost']??'').'/'.(!empty($values['ossDisjunctor'])?($values['businessIdentify']??'').'/':'')];}
function n6Url($path,string $prefix):string{return empty($path)?'':str_replace('\\','/',$prefix.$path);}
function n6Strict($a,$b,string $path='$',array &$diff=[]):array{
    if(gettype($a)!==gettype($b)){$diff[]=['path'=>$path,'kind'=>'type','expected_type'=>gettype($a),'actual_type'=>gettype($b)];return $diff;}
    if(is_object($a)){$a=get_object_vars($a);$b=get_object_vars($b);ksort($a);ksort($b);}if(is_array($a)){if(array_keys($a)!==array_keys($b)){$diff[]=['path'=>$path,'kind'=>'keys','expected_keys'=>array_keys($a),'actual_keys'=>array_keys($b)];}foreach($a as $k=>$v){if(array_key_exists($k,$b)){n6Strict($v,$b[$k],$path.'.'.$k,$diff);}}}elseif($a!==$b){$diff[]=['path'=>$path,'kind'=>'value','expected_type'=>gettype($a),'actual_type'=>gettype($b)];}return $diff;
}
