<?php
declare(strict_types=1);

// Read-only metadata and source gate. No write or HTTP phase is implemented.
$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
if ($phase !== '--preflight' || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXSPEC[0-9A-F]{12}$/', $marker)) {
    fwrite(STDERR, "DENIED: read-only --preflight B|C exact marker required\n");
    exit(78);
}
$root = $lane === 'B' ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$pid = $lane === 'B' ? 13127 : 53772;
$port = $lane === 'B' ? 18101 : 18102;
$sourceFiles = [
    'app/store/controller/goods/Spec.php',
    'app/common/model/GoodsSpecRel.php',
    'app/common/model/Spec.php',
    'app/common/model/SpecValue.php',
];
foreach ($sourceFiles as $file) {
    if (!is_file($root . '/' . $file)) throw new RuntimeException('Worker source absent');
}
if ($lane === 'B') {
    $cfg = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW)['DATABASE'] ?? null;
    $ps = (string)shell_exec('/bin/ps eww -p ' . $pid . ' -o command=');
    $owner = trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:' . $port . ' -sTCP:LISTEN 2>/dev/null'));
    if (!is_array($cfg) || strtolower((string)($cfg['TYPE'] ?? '')) !== 'mysql'
        || (string)($cfg['HOSTPORT'] ?? '') !== '3306' || $owner !== (string)$pid
        || strpos($ps, 'POC_DIALECT=mysql') === false
        || strpos($ps, 'POC_EXTERNAL_STUBS=1') === false) {
        throw new RuntimeException('B worker/config drift');
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
              'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        $v = (string)($cfg[$from] ?? '');
        if ($v === '' || strpos($ps, 'POC_AI_' . $to . '=' . $v) === false) {
            throw new RuntimeException('B worker differs from code local config');
        }
    }
    $db = new PDO('mysql:host=' . $cfg['HOSTNAME'] . ';port=' . $cfg['HOSTPORT']
        . ';dbname=' . $cfg['DATABASE'] . ';charset=utf8', $cfg['USERNAME'], $cfg['PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
} else {
    $env = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $item) {
        $pair = explode('=', $item, 2);
        if (count($pair) === 2) $env[$pair[0]] = $pair[1];
    }
    if (($env['POC_TARGET_ROOT'] ?? '') !== $root
        || ($env['POC_BASE_URL'] ?? '') !== 'http://127.0.0.1:' . $port
        || ($env['POC_DIALECT'] ?? '') !== 'dm'
        || ($env['POC_EXTERNAL_STUBS'] ?? '') !== '1'
        || ($env['POC_AI_HOST'] ?? '') !== '127.0.0.1'
        || ($env['POC_AI_PORT'] ?? '') !== '5238'
        || ($env['POC_AI_SCHEMA'] ?? '') !== 'AIZHCT_APP'
        || ($env['POC_AI_USER'] ?? '') !== 'AIZHCT_APP'
        || !in_array('dm', PDO::getAvailableDrivers(), true)) {
        throw new RuntimeException('C worker/config drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
        $env['POC_AI_USER'], $env['POC_AI_PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'AIZHCT_APP') {
        throw new RuntimeException('C schema drift');
    }
}
$tables = ['goods' => 'GOODS_ID', 'goods_sku' => 'ID',
    'goods_image' => 'ID', 'goods_category_rel' => 'ID',
    'category' => 'CATEGORY_ID', 'upload_file' => 'FILE_ID',
    'spec' => 'SPEC_ID', 'spec_value' => 'SPEC_VALUE_ID',
    'goods_spec_rel' => 'ID'];
$physical = [];
foreach ($tables as $logical => $pk) {
    $physical[$logical] = 'yoshop_' . ($lane === 'B' ? $logical : strtoupper($logical));
}
$source = [];
foreach ($tables as $logical => $pk) {
    $stmt = $db->query('SELECT * FROM ' . $physical[$logical]
        . ' WHERE STORE_ID=10001 ORDER BY ' . $pk);
    $h = hash_init('sha256');
    $n = 0;
    while (($row = $stmt->fetch(PDO::FETCH_NUM)) !== false) {
        hash_update($h, serialize($row));
        if (++$n > 30000) throw new RuntimeException('Natural source cap exceeded');
    }
    $source[$logical] = ['count' => $n, 'sha256' => hash_final($h)];
}
$fixtureTables = ['goods', 'goods_sku', 'goods_image', 'goods_category_rel',
    'category', 'upload_file', 'spec', 'spec_value', 'goods_spec_rel'];
$columns = [];
$indexes = [];
$identity = [];
foreach ($fixtureTables as $logical) {
    $name = $physical[$logical];
    if ($lane === 'B') {
        $s = $db->prepare('SELECT COLUMN_NAME,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION');
        $s->execute([$name]);
        $columns[$logical] = $s->fetchAll(PDO::FETCH_ASSOC);
        $s = $db->prepare('SELECT INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,COLUMN_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY INDEX_NAME,SEQ_IN_INDEX');
        $s->execute([$name]);
        $indexes[$logical] = $s->fetchAll(PDO::FETCH_ASSOC);
        $identity[$logical] = [];
        foreach ($columns[$logical] as $col) {
            if (stripos((string)$col['extra'], 'auto_increment') !== false) {
                $identity[$logical][] = strtoupper((string)$col['column_name']);
            }
        }
    } else {
        $s = $db->prepare('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');
        $s->execute([$name]);
        $columns[$logical] = $s->fetchAll(PDO::FETCH_ASSOC);
        $s = $db->prepare('SELECT INDEX_NAME,COLUMN_POSITION,COLUMN_NAME FROM USER_IND_COLUMNS WHERE TABLE_NAME=? ORDER BY INDEX_NAME,COLUMN_POSITION');
        $s->execute([$name]);
        $indexes[$logical] = $s->fetchAll(PDO::FETCH_ASSOC);
        $s = $db->prepare('CALL SP_TABLEDEF(?, ?)');
        $s->execute(['AIZHCT_APP', $name]);
        $definition = $s->fetchColumn();
        if (is_resource($definition)) $definition = stream_get_contents($definition);
        if (!is_string($definition) || $definition === '') {
            throw new RuntimeException('DM table definition unavailable');
        }
        preg_match_all('/"([A-Za-z_][A-Za-z0-9_]*)"\s+[A-Za-z0-9_]+(?:\([^)]*\))?\s+(?:AUTO_INCREMENT|IDENTITY\b)/i',
            $definition, $matches);
        $identity[$logical] = array_map('strtoupper', $matches[1]);
    }
    if (count($columns[$logical]) < 3) throw new RuntimeException('Schema missing');
}
$names = array_values($physical);
$marks = implode(',', array_fill(0, count($names), '?'));
if ($lane === 'B') {
    $s = $db->prepare('SELECT EVENT_OBJECT_TABLE,TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (' . $marks . ') ORDER BY EVENT_OBJECT_TABLE,TRIGGER_NAME');
    $s->execute($names);
    $triggers = $s->fetchAll(PDO::FETCH_ASSOC);
    $s = $db->prepare('SELECT TABLE_NAME,COLUMN_NAME,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND (TABLE_NAME IN (' . $marks . ') OR REFERENCED_TABLE_NAME IN (' . $marks . ')) ORDER BY TABLE_NAME,COLUMN_NAME');
    $s->execute(array_merge($names, $names));
    $fks = $s->fetchAll(PDO::FETCH_ASSOC);
    $s = $db->prepare('SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE ? AND UPPER(COLUMN_NAME) IN (?,?,?,?) ORDER BY TABLE_NAME,COLUMN_NAME');
    $s->execute(['yoshop_%', 'GOODS_ID', 'SPEC_ID', 'SPEC_VALUE_ID', 'GOODS_SPEC_REL_ID']);
    $refs = $s->fetchAll(PDO::FETCH_ASSOC);
} else {
    $quoted = "'" . implode("','", $names) . "'";
    $triggers = $db->query('SELECT TABLE_NAME,TRIGGER_NAME FROM USER_TRIGGERS WHERE TABLE_NAME IN (' . $quoted . ') ORDER BY TABLE_NAME,TRIGGER_NAME')->fetchAll(PDO::FETCH_ASSOC);
    $fks = $db->query("SELECT TABLE_NAME,CONSTRAINT_NAME,R_CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND (TABLE_NAME IN (" . $quoted . ") OR R_CONSTRAINT_NAME IN (SELECT CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME IN (" . $quoted . '))) ORDER BY TABLE_NAME,CONSTRAINT_NAME')->fetchAll(PDO::FETCH_ASSOC);
    $s = $db->prepare('SELECT TABLE_NAME,COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME LIKE ? AND COLUMN_NAME IN (?,?,?,?) ORDER BY TABLE_NAME,COLUMN_NAME');
    $s->execute(['YOSHOP_%', 'GOODS_ID', 'SPEC_ID', 'SPEC_VALUE_ID', 'GOODS_SPEC_REL_ID']);
    $refs = $s->fetchAll(PDO::FETCH_ASSOC);
}
if (count($refs) > 250) throw new RuntimeException('Reference metadata cap exceeded');
$markerRows = [];
foreach (['goods' => 'GOODS_NAME', 'spec' => 'SPEC_NAME', 'spec_value' => 'SPEC_VALUE'] as $logical => $field) {
    $s = $db->prepare('SELECT COUNT(*) FROM ' . $physical[$logical]
        . ' WHERE STORE_ID=10001 AND ' . $field . ' LIKE ?');
    $s->execute([$marker . '%']);
    $markerRows[$logical] = (int)$s->fetchColumn();
}
$sourceSha = [];
foreach ($sourceFiles as $file) $sourceSha[$file] = hash_file('sha256', $root . '/' . $file);
echo json_encode(['phase' => $phase, 'lane' => $lane, 'marker' => $marker,
    'worker_port' => $port, 'worker_source_sha256' => $sourceSha,
    'natural_source' => $source, 'columns' => $columns, 'identity_columns' => $identity,
    'indexes' => $indexes, 'triggers' => $triggers, 'foreign_keys' => $fks,
    'possible_reference_columns' => $refs, 'marker_rows' => $markerRows,
    'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
