<?php
declare(strict_types=1);
require_once __DIR__.'/common.php';
const HC_TABLES=['staff','department','staff_department','dishes','recipes','initial_menu','date_menu_hardware_img'];
function hcMetadata(PDO $db):array{
    $dm=getenv('SM7_LANE')==='C';$out=[];
    foreach(HC_TABLES as $t){$name=$dm?strtoupper('ydy_'.$t):'ydy_'.$t;
        $out[$t]=[
            'columns'=>smRows($db,$dm?"SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID":"SELECT COLUMN_NAME,COLUMN_TYPE,DATA_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION",[$name]),
            'foreign_keys'=>smRows($db,$dm?"SELECT CONSTRAINT_NAME,R_CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME=? AND CONSTRAINT_TYPE='R'":"SELECT CONSTRAINT_NAME,COLUMN_NAME,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND REFERENCED_TABLE_NAME IS NOT NULL",[$name]),
            'triggers'=>smRows($db,$dm?"SELECT TRIGGER_NAME,STATUS FROM USER_TRIGGERS WHERE TABLE_NAME=?":"SELECT TRIGGER_NAME,EVENT_MANIPULATION FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE=?",[$name]),
            'keys'=>smRows($db,$dm?"SELECT c.CONSTRAINT_NAME,c.CONSTRAINT_TYPE,cc.COLUMN_NAME,cc.POSITION FROM USER_CONSTRAINTS c JOIN USER_CONS_COLUMNS cc ON cc.CONSTRAINT_NAME=c.CONSTRAINT_NAME WHERE c.TABLE_NAME=? AND c.CONSTRAINT_TYPE IN ('P','U') ORDER BY c.CONSTRAINT_NAME,cc.POSITION":"SELECT CONSTRAINT_NAME,COLUMN_NAME,ORDINAL_POSITION FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND REFERENCED_TABLE_NAME IS NULL ORDER BY CONSTRAINT_NAME,ORDINAL_POSITION",[$name]),
        ];
    }return $out;
}
function hcCanonical($v){if(!is_array($v)){return $v;}if($v!==[]&&array_keys($v)!==range(0,count($v)-1)){ksort($v);}foreach($v as &$x){$x=hcCanonical($x);}unset($x);return $v;}
function hcMetaHash(array $v):string{return hash('sha256',serialize(hcCanonical($v)));}
function hcRowsForInsert(array $s,int $restaurant,string $now):array{
    $m=$s['marker'];return [
        'staff'=>['uuid'=>$s['staff'],'station_uuid'=>$s['station'],'name'=>$m.'员"测\'','birthday'=>'2000-01-01','sport_uuid'=>$s['sport'],'sport_event_uuid'=>'[]','in_time'=>$s['date'],'create_time'=>$now,'create_by'=>$m,'personnel_umber'=>$m,'permanent_address'=>'CDX_TEST','coach_name'=>'CDX_TEST','sport_year'=>'CDX_TEST','is_show'=>0,'status'=>0,'job_status'=>0,'sex'=>1,'restaurant_id'=>$restaurant],
        'department'=>['uuid'=>$s['department'],'name'=>$m.'部门','charge_by'=>$m,'leader'=>$m,'create_time'=>$now,'create_by'=>$m,'puuid'=>'0','department_path'=>$s['department'],'status'=>0,'is_show'=>0],
        'staff_department'=>['department_uuid'=>$s['department'],'staff_uuid'=>$s['staff'],'create_time'=>$now,'create_by'=>$m],
        'dishes'=>['uuid'=>$s['dishes'],'name'=>$m.'菜"测\'','category'=>1,'unit'=>1,'weight'=>'100.00','status'=>0,'is_del'=>0,'create_time'=>$now,'create_by'=>$m],
        'recipes'=>['uuid'=>$s['recipes'],'dishes_uuid'=>$s['dishes'],'menu_date'=>$s['date'],'meal_times'=>1,'type'=>1,'restaurant_id'=>$restaurant,'create_time'=>$now,'create_by'=>$m],
        'date_menu_hardware_img'=>['uuid'=>$s['image'],'staff_uuid'=>$s['staff'],'menu_date'=>$s['date'],'meal_times'=>'1','img'=>'CDX_TEST_METADATA/no-real-file','upload_time'=>$now,'create_time'=>$now],
        'initial_menu'=>['uuid'=>$s['menu'],'recommend_uuid'=>$s['recommend'],'staff_uuid'=>$s['staff'],'hardware_img_uuid'=>$s['image'],'dishes_uuid'=>$s['dishes'],'menu_date'=>$s['date'],'meal_times'=>'1','number'=>'1','weight'=>'12.34','code'=>1,'select_adjust'=>0,'meal_type'=>4,'create_time'=>$now,'create_by'=>$m],
    ];
}
function hcSnapshot(PDO $db,array $seed,array $ids=[],bool $skipNaturalStaff=false):array{
    $unbuffered=getenv('SM7_LANE')==='B';$oldBuffer=$unbuffered?$db->getAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY):null;
    if($unbuffered){$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,false);}
    try{$out=[];foreach(HC_TABLES as $t){if($skipNaturalStaff&&$t==='staff'){continue;}$sql='SELECT * FROM ydy_'.$t;$args=[];$where=[];
        if($t==='initial_menu'){$where[]='menu_date=?';$args[]=$seed['date'];}
        if(isset($ids[$t])){$where[]='id<>?';$args[]=$ids[$t];}if($where){$sql.=' WHERE '.implode(' AND ',$where);}
        $s=$db->prepare($sql);$s->execute($args);$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',serialize($r));if(count($parts)>1000000){throw new RuntimeException('Natural stream bound');}}
        $s->closeCursor();sort($parts);$out[$t]=['rows'=>count($parts),'sha256'=>hash('sha256',implode("\n",$parts))];
    }$out['initial_menu_global_count']=(int)$db->query('SELECT COUNT(*) FROM ydy_initial_menu')->fetchColumn()- (isset($ids['initial_menu'])?1:0);return $out;
    }finally{if($unbuffered){$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,$oldBuffer);}}
}
function hcOwnRows(PDO $db,array $seed,array $ids):array{
    $out=[];foreach($ids as $t=>$id){$row=smRows($db,'SELECT * FROM ydy_'.$t.' WHERE id=?',[$id]);if(count($row)!==1){throw new RuntimeException('Owned PK vanished');}$r=$row[0];
        if($t==='staff_department'){if($r['staff_uuid']!==$seed['staff']||$r['department_uuid']!==$seed['department']||$r['create_by']!==$seed['marker']){throw new RuntimeException('Owned pair drift');}}
        else{$k=['date_menu_hardware_img'=>'image','initial_menu'=>'menu'][$t]??$t;if($r['uuid']!==$seed[$k]){throw new RuntimeException('Owned UUID drift');}if(isset($r['create_by'])&&$r['create_by']!==$seed['marker']){throw new RuntimeException('Owned marker drift');}}
        $out[$t]=$r;
    }return $out;
}
// Root-approved exact consumer set; unrelated concurrent global rows are telemetry.
function hcConsumedSource(PDO $db,array $s,array $ids,array $pre,array $actor,int $meal):array{
    $own=hcOwnRows($db,$s,$ids);$expected=$own;$expected['initial_menu']['meal_type']=$meal;
    if(smDigest($own)!==smDigest($expected)){throw new RuntimeException('Unexpected owned meal state');}
    $queries=[
        'staff'=>['SELECT * FROM ydy_staff WHERE uuid=?',[$s['staff']]],
        'department'=>['SELECT * FROM ydy_department WHERE uuid=? OR department_path LIKE ? OR puuid=?',[$s['department'],'%'.$s['department'].'%',$s['department']]],
        'staff_department'=>['SELECT * FROM ydy_staff_department WHERE department_uuid=? OR staff_uuid=?',[$s['department'],$s['staff']]],
        'dishes'=>['SELECT * FROM ydy_dishes WHERE uuid=?',[$s['dishes']]],
        'recipes'=>['SELECT * FROM ydy_recipes WHERE (menu_date=? AND meal_times=?) OR dishes_uuid=?',[$s['date'],'1',$s['dishes']]],
        'date_menu_hardware_img'=>['SELECT * FROM ydy_date_menu_hardware_img WHERE staff_uuid=? OR uuid=?',[$s['staff'],$s['image']]],
        'initial_menu'=>['SELECT * FROM ydy_initial_menu WHERE staff_uuid=? OR hardware_img_uuid=? OR dishes_uuid=? OR uuid=?',[$s['staff'],$s['image'],$s['dishes'],$s['menu']]],
    ];$out=[];foreach($queries as $t=>$q){$rows=smRows($db,$q[0],$q[1]);if(smDigest($rows)!==smDigest([$own[$t]])){throw new RuntimeException('Consumed relation set changed: '.$t);}$out[$t]=smDigest($rows);}
    $children=smRows($db,'SELECT uuid FROM ydy_department WHERE department_path LIKE ? AND status=1 AND is_show=1',['%'.$s['department'].'%']);if($children!==[]){throw new RuntimeException('Owned department child set changed');}
    $logs=smRows($db,'SELECT id FROM ydy_initial_menu_log WHERE staff_uuid=? AND menu_date=? AND meal_times=? AND user_type=1 AND terminal=1',[$s['staff'],$s['date'],'1']);if($logs!==[]){throw new RuntimeException('Owned consumption logs appeared');}
    $ar=smRows($db,'SELECT * FROM ydy_user WHERE id=? AND uuid=? AND account=? AND create_by=?',[$actor['id'],$actor['uuid'],$actor['marker'],$actor['marker']]);$rr=smRows($db,'SELECT * FROM ydy_restaurants WHERE id=? AND uuid=? AND name=?',[$pre['restaurant_id'],$pre['restaurant_uuid'],$pre['restaurant_marker']]);
    if(smDigest($ar)!==$pre['actor_digest']||smDigest($rr)!==$pre['restaurant_digest']){throw new RuntimeException('Actor or restaurant changed');}
    $out['child_uuids']=[$s['department']];$out['log_count']=0;$out['actor']=$pre['actor_digest'];$out['restaurant']=$pre['restaurant_digest'];return $out;
}
