<?php
declare(strict_types=1);

// B/C Store read-three source gate. This file never sends business HTTP or DML.
set_exception_handler(static function (Throwable $e): void {
    $message = $e instanceof PDOException ? 'PDO_SQLSTATE_' . (string)$e->getCode()
        : $e->getMessage();
    fwrite(STDERR, 'READ3_PRE_FAIL ' . get_class($e) . ' ' . $message . PHP_EOL);
    exit(2);
});
$mode = (string)($argv[1] ?? '');
$lane = (string)getenv('CDX_READ3_LANE');
if (!in_array($mode, ['--parent', '--child'], true) || !in_array($lane, ['B', 'C'], true)) {
    fwrite(STDERR, "READ3_PRE only supports B/C parent or child preflight\n");
    exit(78);
}
$target = $lane === 'B'
    ? ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001,
        '/usr/local/php7.3/bin/php', 'mysql']
    : ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 112175,
        '/www/server/php/73/bin/php', 'dm'];
[$root, $base, $pid, $php, $dialect] = $target;
$expectedSources = [
    'application/p/controller/SupplementMealOrder.php' => '7943d45678ee088d20441183fdf7a504df73ef357614290312d8861ff859be33',
    'application/p/logic/SupplementMealOrder.php' => '0a7511c21cfea855223e1df74c893c2e166406eab5e2d7135e7e9b23f5f1270a',
    'application/p/controller/CashRefund.php' => 'cd24ac67287603d536510ef7405b7fa3998c6bc99bc5a29399601cad0ea01172',
    'application/p/logic/CashRefund.php' => 'b0bd6f2d9a956116538ae4326cd35ab8d1b68a4576a2cb265372bd23cefd7c3d',
    'application/base/AdminController.php' => '7565b36fa4dda2c21cc4fa3b37753dbffb7c3a464bb8c11ea0f17ca00bececa5',
    'application/route.php' => '081b0186b7f40f0bdd237cecb3e2d678fc9344c656932c0d3b3052404e4f1510',
];
foreach ($expectedSources as $file => $sha) {
    $path = $root . '/' . $file;
    if (!is_file($path) || is_link($path) || hash_file('sha256', $path) !== $sha) {
        throw new RuntimeException('Read-three source drift: ' . $file);
    }
}
$preflight = $lane === 'B'
    ? '/workspace/zhct/zhctprompt/work_store/apifox/2026-09-30-dm8-interface-acceptance/parallel-store-next5-readonly/preflight_next5.php'
    : '/tmp/zhct-next5-readonly-20261004/preflight_next5.php';
if (!is_file($preflight) || is_link($preflight)
    || hash_file('sha256', $preflight) !== '8f262f3645d6c7e082dc5a2d7c32d893d32eb91a3dd2365f7d88cec0db25c4d6') {
    throw new RuntimeException('Frozen runtime preflight unavailable');
}
if ($mode === '--parent') {
    $cmdline = (string)file_get_contents('/proc/' . $pid . '/cmdline');
    if (strpos($cmdline, $root . '/public') === false
        || strpos($cmdline, (string)parse_url($base, PHP_URL_PORT)) === false) {
        throw new RuntimeException('Read-three HTTP worker drift');
    }
    $workerEnv = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $entry) {
        $at = strpos($entry, '=');
        if ($at !== false) $workerEnv[substr($entry, 0, $at)] = substr($entry, $at + 1);
    }
    if ($lane === 'C') {
        $svc = (string)($workerEnv['DM_SVC_PATH'] ?? '');
        if (substr(hash('sha256', $svc), 0, 12) !== '5a92d26aec07'
            || !is_file($svc . '/dm_svc.conf')
            || strpos((string)file_get_contents($svc . '/dm_svc.conf'), 'CHAR_CODE=(PG_UTF8)') === false) {
            throw new RuntimeException('DM UTF8 client boundary drift');
        }
    }
    $workerEnv['CDX_READ3_LANE'] = $lane;
    $workerEnv['CDX_NEXT5_LANE'] = $lane;
    $workerEnv['POC_APP_ROOT'] = $root;
    $workerEnv['POC_BASE_URL'] = $base;
    $workerEnv['POC_DIALECT'] = $dialect;
    $command = escapeshellarg($php) . ' -d display_errors=0 -d log_errors=0';
    if ($lane === 'C') {
        $command .= ' -d extension=/home/dmdba/dmdbms/drivers/php_pdo/php73_pdo_dm.so';
    }
    $command .= ' ' . escapeshellarg(__FILE__) . ' --child';
    $process = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
        $pipes, null, $workerEnv);
    if (!is_resource($process)) throw new RuntimeException('Read-three child unavailable');
    $out = stream_get_contents($pipes[1]);
    $err = stream_get_contents($pipes[2]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    $exit = proc_close($process);
    $marker = 'READ3_JSON ';
    $at = strrpos((string)$out, $marker);
    $result = $at === false ? null : json_decode(substr((string)$out, $at + strlen($marker)), true);
    if ($exit !== 0 || !is_array($result)) {
        $firstError = strtok(trim((string)$err), "\n") ?: '';
        $stageMarker = 'READ3_STAGE ';
        $stageAt = strrpos((string)$out, $stageMarker);
        $stage = $stageAt === false ? null : json_decode(
            substr((string)$out, $stageAt + strlen($stageMarker)), true);
        echo json_encode(['status' => 'preflight-failed', 'exit' => $exit,
            'error' => substr($firstError, 0, 180),
            'stage' => $stage,
            'stdout_sha12' => substr(hash('sha256', (string)$out), 0, 12),
            'stderr_sha12' => substr(hash('sha256', (string)$err), 0, 12)]), PHP_EOL;
        exit(2);
    }
    echo json_encode($result, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

$workerEnvironment = (string)@file_get_contents('/proc/' . $pid . '/environ');
$childFlags = [
    'pid_alive' => is_file('/proc/' . $pid . '/cmdline'),
    'env_present' => $workerEnvironment !== '',
    'poc_keys' => array_reduce(['POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER',
        'POC_PASSWORD', 'POC_EXTERNAL_STUBS'], static function (bool $all, string $key): bool {
            return $all && (string)getenv($key) !== '';
        }, true),
    'dm_svc_path' => (string)getenv('DM_SVC_PATH') !== '',
    'ld_library_path' => (string)getenv('LD_LIBRARY_PATH') !== '',
    'pdo_dm_loaded' => extension_loaded('pdo_dm'),
];
echo 'READ3_STAGE ', json_encode($childFlags), PHP_EOL;
$argv[1] = '--preflight';
ob_start();
require $preflight; // fixed PID/socket, code-local MySQL or DM identity, SELECT-only source
ob_end_clean();
$sql = static function (string $statement, array $args = []) use ($db): array {
    if (!preg_match('/^SELECT\b/i', $statement)) throw new RuntimeException('SELECT only');
    $query = $db->prepare($statement);
    $query->execute($args);
    return array_change_key_case($query->fetch(PDO::FETCH_ASSOC) ?: [], CASE_LOWER);
};
$counts = [
    'restaurants' => (int)($sql('SELECT COUNT(*) AS N FROM ' . $table('restaurants')
        . ' WHERE STATUS=1 AND IS_DELETE=2')['n'] ?? -1),
    'departments' => (int)($sql('SELECT COUNT(*) AS N FROM ' . $table('department')
        . ' WHERE IS_SHOW=1')['n'] ?? -1),
    'staff_cash_candidates' => (int)($sql('SELECT COUNT(*) AS N FROM ' . $table('staff')
        . ' WHERE IS_SHOW=1 AND AI_USER_ID>0')['n'] ?? -1),
    'linked_users' => (int)($sql('SELECT COUNT(*) AS N FROM ' . $table('user')
        . ' WHERE IS_DELETE=0 AND STAFF_UUID IS NOT NULL')['n'] ?? -1),
    'prior_read3_actors' => (int)($sql('SELECT COUNT(*) AS N FROM ' . $table('user')
        . " WHERE ACCOUNT LIKE 'CDXR3%'")['n'] ?? -1),
];
$candidates = $db->query('SELECT sd.DEPARTMENT_UUID AS D, COUNT(DISTINCT u.ID) AS N'
    . ' FROM ' . $table('staff_department') . ' sd'
    . ' JOIN ' . $table('user') . ' u ON u.STAFF_UUID=sd.STAFF_UUID AND u.IS_DELETE=0'
    . ' JOIN ' . $table('staff') . ' s ON s.UUID=sd.STAFF_UUID AND s.IS_SHOW=1 AND s.AI_USER_ID>0'
    . ' JOIN ' . $table('department') . ' d ON d.UUID=sd.DEPARTMENT_UUID AND d.IS_SHOW=1'
    . ' GROUP BY sd.DEPARTMENT_UUID HAVING COUNT(DISTINCT u.ID) BETWEEN 1 AND 8'
    . ' ORDER BY N, D')->fetchAll(PDO::FETCH_ASSOC);
if (!is_array($candidates) || !$candidates || count($candidates) > 5000) {
    throw new RuntimeException('Bounded natural department candidate unavailable');
}
$selected = null;
$department = '';
$small = -1;
$cash = -1;
foreach ($candidates as $candidate) {
    $row = array_change_key_case($candidate, CASE_LOWER);
    $candidateDepartment = (string)$row['d'];
    $supplementCount = (int)($sql('SELECT COUNT(DISTINCT u.ID) AS N FROM '
        . $table('staff_department') . ' sd JOIN ' . $table('user')
        . ' u ON u.STAFF_UUID=sd.STAFF_UUID AND u.IS_DELETE=0'
        . ' JOIN ' . $table('staff') . ' s ON s.UUID=sd.STAFF_UUID AND s.IS_SHOW=1'
        . ' WHERE sd.DEPARTMENT_UUID=?', [$candidateDepartment])['n'] ?? -1);
    if ($supplementCount >= 1 && $supplementCount <= 8) {
        $selected = $row;
        $department = $candidateDepartment;
        $small = $supplementCount;
        $cash = (int)$row['n'];
        break;
    }
}
$sourceReady = $selected !== null && $small >= 1 && $small <= 8
    && $cash >= 1 && $cash <= 8 && $counts['restaurants'] >= 1
    && $counts['departments'] >= 1 && $counts['prior_read3_actors'] === 0;
echo 'READ3_JSON ', json_encode([
    'status' => $sourceReady ? 'preflight-pass' : 'source-insufficient', 'lane' => $lane,
    'source_counts' => $counts,
    'bounded_department_sha12' => substr(hash('sha256', $department), 0, 12),
    'supplement_staff_rows' => $small, 'cash_staff_rows' => $cash,
    'business_http' => 0, 'dml' => 0,
], JSON_UNESCAPED_SLASHES), PHP_EOL;
exit($sourceReady ? 0 : 3);
