<?php
declare(strict_types=1);
date_default_timezone_set('Asia/Shanghai');
$lane=(string)getenv('CATEGORY_LANE');$base=(string)getenv('CATEGORY_BASE');$runtime=(string)getenv('CATEGORY_RUNTIME');
if(!in_array($lane,['B','C'],true)||!preg_match('#^http://127\.0\.0\.1:1832[12]$#D',$base))exit(78);
$dm=getenv('POC_DIALECT')==='dm';
$connect=static function()use($dm):PDO{try{return new PDO($dm?'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP':
    'mysql:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';dbname='.getenv('POC_AI_SCHEMA').';charset=utf8',
    getenv('POC_AI_USER'),getenv('POC_AI_PASSWORD'),[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);
}catch(Throwable $e){throw new RuntimeException('Database connection preflight failed');}};
$db=$connect();$check=static function(bool $ok,string $reason):void{if(!$ok)throw new RuntimeException($reason);};
if($dm)$check($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()==='AIZHCT_APP','DM identity');
else $check(strpos((string)$db->query('SELECT VERSION()')->fetchColumn(),'5.6.')===0&&$db->query('SELECT DATABASE()')->fetchColumn()===getenv('POC_AI_SCHEMA'),'local MySQL identity');
$query=static function(string $sql,array $args=[])use($db):array{$s=$db->prepare($sql);$s->execute($args);$rows=$s->fetchAll(PDO::FETCH_ASSOC);
    foreach($rows as &$row)foreach($row as &$v)if(is_resource($v))$v=stream_get_contents($v);unset($row,$v);return $rows;};
$prefix='CDXHAB10F5A1';$tenants=['store'=>2147000421,'terminal'=>2147000422];$tables=['staff','habit','habit_rpe','habit_rpe_info'];
$required=[
    'staff'=>['id','name','gender','birthday','height','weight','staff_type','nation_id','career','education','marital','phone','sport_id','sport_event_id','user_id','store_id','is_delete','create_by','create_time','update_time'],
    'habit'=>['id','staff_id','habit','store_id','is_delete','create_by','create_time','update_time'],
    'habit_rpe'=>['id','staff_id','train_date','train_temperature','train_environment','store_id','is_delete','create_by','create_time','update_time'],
    'habit_rpe_info'=>['id','rpe_id','start_time','end_time','train_min','rest_min','strength','store_id','is_delete','create_by','create_time','update_time']];
$metadata=[];
foreach($required as $table=>$columns){
    $rows=$dm?$query('SELECT COLUMN_NAME,DATA_TYPE,NULLABLE,DATA_DEFAULT,DATA_LENGTH FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID',[strtoupper('yoshop_'.$table)])
        :$query('SELECT COLUMN_NAME,DATA_TYPE,IS_NULLABLE,COLUMN_DEFAULT,CHARACTER_MAXIMUM_LENGTH,EXTRA FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION',['yoshop_'.$table]);
    $names=array_map(static function(array $r):string{return strtolower($r['column_name']);},$rows);
    $check(!array_diff($columns,$names),'Required column missing '.$table);
    $extras=array_values(array_diff($names,$columns));
    if($table!=='staff')$check(!$extras,'Unexpected exposed Habit columns '.$table);
    foreach($rows as $row){$name=strtolower($row['column_name']);
        if(in_array($name,$extras,true))$check(($dm?$row['nullable']:$row['is_nullable'])!==($dm?'N':'NO')
            ||($dm?$row['data_default']:$row['column_default'])!==null,'Unknown extra required staff field '.$name);
    }
    $metadata[$table]=['required_columns_present'=>true,'constraints_defaults_types_read'=>true,'optional_extra_columns'=>$extras];
}
$list=$dm?"'YOSHOP_STAFF','YOSHOP_HABIT','YOSHOP_HABIT_RPE','YOSHOP_HABIT_RPE_INFO'":"'yoshop_staff','yoshop_habit','yoshop_habit_rpe','yoshop_habit_rpe_info'";
$triggers=$dm?$query('SELECT COUNT(*) n FROM USER_TRIGGERS WHERE TABLE_NAME IN ('.$list.')')
    :$query('SELECT COUNT(*) n FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN ('.$list.')');
$fks=$dm?$query("SELECT COUNT(*) n FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND (TABLE_NAME IN (".$list.') OR R_CONSTRAINT_NAME IN (SELECT CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME IN ('.$list.')))')
    :$query('SELECT COUNT(*) n FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND (TABLE_NAME IN ('.$list.') OR REFERENCED_TABLE_NAME IN ('.$list.'))');
$check((int)$triggers[0]['n']===0&&(int)$fks[0]['n']===0,'FK/trigger gate');
$counts=static function()use($query,$tables):array{$out=[];foreach($tables as $table)$out[$table]=(int)$query('SELECT COUNT(*) n FROM yoshop_'.$table.' WHERE store_id IN (2147000421,2147000422)')[0]['n'];return $out;};
$scanMetrics=[];
$snapshot=static function()use($connect,$dm,$tables,&$scanMetrics):array{
    // Scan incrementally. Do not materialize natural personnel/history rows in RAM.
    $scan=$connect();if(!$dm)$scan->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,false);
    $out=[];$metrics=[];foreach(array_merge($tables,['config_enum']) as $table){$started=microtime(true);
        $statement=$scan->query('SELECT * FROM yoshop_'.$table.' ORDER BY id');$hash=hash_init('sha256');$count=0;
        while($row=$statement->fetch(PDO::FETCH_ASSOC)){foreach($row as &$value)if(is_resource($value))$value=stream_get_contents($value);unset($value);
            hash_update($hash,serialize($row));++$count;}
        $statement->closeCursor();$out[$table]=['rows'=>$count,'sha'=>hash_final($hash),'streamed'=>true];
        $metrics[$table]=['rows'=>$count,'elapsed_ms'=>(int)round((microtime(true)-$started)*1000)];
    }$scanMetrics[]=$metrics;return $out;
};
$health=static function()use($query,$dm):array{$out=[];foreach(['meal_recommend','meal_recommend_history'] as $table){
    $found=$dm?$query('SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME=?',[strtoupper('yoshop_'.$table)])
        :$query('SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?',['yoshop_'.$table]);
    $out[$table]=$found?(int)$query('SELECT COUNT(*) n FROM yoshop_'.$table.' WHERE store_id IN (2147000421,2147000422)')[0]['n']:'not-present-outside-worker-scope';}return $out;};
