<?php
declare(strict_types=1);

// Independent new-process readback/recovery triage only. Never mutates state.
const CAT8_READBACK_APPROVAL = 'CDX_CAT8_READBACK_APPROVED_20261002_V1';
$stage = 'default_deny';
set_exception_handler(static function (Throwable $error) use (&$stage): void {
    echo json_encode(['status' => 'redacted_error', 'stage' => $stage,
        'class' => get_class($error), 'writes' => false],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(2);
});
set_error_handler(static function (int $severity): bool {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException('redacted_warning', 0, $severity);
});
if (!hash_equals(CAT8_READBACK_APPROVAL,
    (string)getenv('CDX_CAT8_READBACK_APPROVAL'))) {
    echo json_encode(['status' => 'default_deny', 'database_connected' => false,
        'cache_accessed' => false, 'http_sent' => false,
        'writes' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(78);
}
$stage = 'exact_source_review';
if (!hash_equals((string)hash_file('sha256', __FILE__),
        (string)getenv('CDX_CAT8_READBACK_SHA256'))
    || !hash_equals((string)hash_file('sha256', __DIR__
        . '/catalog8_fixture_lib.draft.php'),
        (string)getenv('CDX_CAT8_LIB_SHA256'))) {
    throw new RuntimeException('Exact reviewed readback and library SHA required');
}
require __DIR__ . '/catalog8_fixture_lib.draft.php';
$stage = 'private_journal';
$manifest = cat8_manifest((string)getenv('CDX_CAT8_FIXTURE_MANIFEST'));
$target = (string)getenv('CDX_CAT8_TARGET');
if ($target !== $manifest['target']
    || ($manifest['fixture_mode'] ?? null) !== 'synthetic_role_catalog_read'
    || ($manifest['menu_path'] ?? null)
        !== '/__codex/catalog8/' . strtolower($manifest['user_name'])) {
    throw new RuntimeException('Fixture target and marker mismatch');
}
$stage = 'independent_runtime_identity';
$ctx = cat8_context($target);
if ($manifest['schema'] !== $ctx['schema']) {
    throw new RuntimeException('Fixture schema drift');
}
/** @var PDO $pdo */
$pdo = $ctx['pdo'];
$marker = $manifest['user_name'];
$userRows = cat8_rows($pdo, 'SELECT STORE_USER_ID,USER_NAME,REAL_NAME,'
    . ' IS_SUPER,IS_DELETE,STORE_ID FROM yoshop_store_user'
    . ' WHERE USER_NAME=? AND STORE_ID=10001', [$marker]);
$roleRows = cat8_rows($pdo, 'SELECT ROLE_ID,ROLE_NAME,STORE_ID'
    . ' FROM yoshop_store_role WHERE ROLE_NAME=? AND STORE_ID=10001', [$marker]);
$menuRows = cat8_rows($pdo, 'SELECT MENU_ID,NAME,PATH'
    . ' FROM yoshop_store_menu WHERE PATH=?', [$manifest['menu_path']]);
$apiRows = cat8_rows($pdo, 'SELECT API_ID,NAME,URL'
    . ' FROM yoshop_store_api WHERE NAME=? AND URL=?',
    [$marker, '/dishes.dishes/dishLibrary']);
$cacheDriver = \think\facade\Cache::store('file');
$cacheFile = $cacheDriver->getCacheKey($manifest['token']);
$cacheFileExists = is_file($cacheFile);
$cacheHeaderTtl = null;
$cacheMtime = null;
if ($cacheFileExists) {
    $header = (string)file_get_contents($cacheFile, false, null, 0, 32);
    clearstatcache(true, $cacheFile);
    $cacheMtime = filemtime($cacheFile);
    if (preg_match('/^<\?php\n\/\/(\d{12})\n/', $header, $ttlMatch) === 1) {
        $cacheHeaderTtl = (int)$ttlMatch[1];
    }
}
// File-cache get() removes expired files; do not call it on an expired entry
// in this read-only recovery checker.
$cache = $cacheFileExists && $cacheHeaderTtl === 300
    && $cacheMtime !== false && $cacheMtime + 300 > time() + 2
        ? \think\facade\Cache::get($manifest['token']) : null;
$cachePresent = is_array($cache);
$counts = ['user' => count($userRows), 'role' => count($roleRows),
    'menu' => count($menuRows), 'created_api' => count($apiRows)];
$state = 'partial_or_ambiguous_manual_review';
$detail = [];

if (count($userRows) === 1 && count($roleRows) === 1
    && count($menuRows) === 1) {
    $uid = (int)$userRows[0]['store_user_id'];
    $rid = (int)$roleRows[0]['role_id'];
    $mid = (int)$menuRows[0]['menu_id'];
    $granted = cat8_rows($pdo,
        'SELECT DISTINCT a.URL FROM yoshop_store_user_role ur'
        . ' INNER JOIN yoshop_store_role_menu rm ON rm.ROLE_ID=ur.ROLE_ID'
        . ' INNER JOIN yoshop_store_menu_api ma ON ma.MENU_ID=rm.MENU_ID'
        . ' INNER JOIN yoshop_store_api a ON a.API_ID=ma.API_ID'
        . ' WHERE ur.STORE_USER_ID=? AND ur.ROLE_ID=? AND ur.STORE_ID=10001'
        . ' AND rm.MENU_ID=? AND rm.STORE_ID=10001 ORDER BY a.URL',
        [$uid, $rid, $mid]);
    $actualUrls = array_map(static function (array $row): string {
        return (string)$row['url'];
    }, $granted);
    $expectedUrls = CAT8_URLS;
    sort($expectedUrls);
    $roleUserCount = cat8_count($pdo,
        'SELECT COUNT(*) FROM yoshop_store_user_role'
        . ' WHERE STORE_USER_ID=? AND ROLE_ID=? AND STORE_ID=10001', [$uid, $rid]);
    $roleMenuCount = cat8_count($pdo,
        'SELECT COUNT(*) FROM yoshop_store_role_menu'
        . ' WHERE ROLE_ID=? AND MENU_ID=? AND STORE_ID=10001', [$rid, $mid]);
    $menuApiCount = cat8_count($pdo,
        'SELECT COUNT(*) FROM yoshop_store_menu_api WHERE MENU_ID=?', [$mid]);
    $markerOwned = (int)$userRows[0]['is_super'] === 0
        && (int)$userRows[0]['is_delete'] === 0
        && (string)$userRows[0]['real_name'] === $marker
        && (string)$roleRows[0]['role_name'] === $marker
        && (string)$menuRows[0]['name'] === $marker
        && $roleUserCount === 1 && $roleMenuCount === 1
        && $menuApiCount === 8 && $actualUrls === $expectedUrls;
    $idRecorded = (int)($manifest['store_user_id'] ?? 0) === $uid
        && (int)($manifest['role_id'] ?? 0) === $rid
        && (int)($manifest['menu_id'] ?? 0) === $mid;
    $dbOwned = $markerOwned && $idRecorded;
    $sessionOwned = $cachePresent
        && (int)($cache['user']['store_user_id'] ?? 0) === $uid
        && (int)($cache['user']['store_id'] ?? 0) === 10001
        && (int)($cache['user']['is_super'] ?? -1) === 0
        && !empty($cache['is_login'])
        && (int)($cache['store_id'] ?? 0) === 10001;
    $ttlValid = false;
    if ($cachePresent && $sessionOwned) {
        if ($cacheFileExists) {
            $ttlValid = $cacheHeaderTtl === 300 && $cacheMtime !== false
                && (int)($manifest['expires_at'] ?? 0) === $cacheMtime + 300
                && time() < (int)$manifest['expires_at'];
        }
    }
    $detail = ['role_api_paths_exact' => $dbOwned,
        'marker_scoped_role_api_paths_exact' => $markerOwned,
        'journal_ids_match' => $idRecorded,
        'merchant_session_exact' => $sessionOwned,
        'cache_ttl_300s_exact' => $ttlValid,
        'granted_paths' => count($actualUrls)];
    if ($dbOwned && $sessionOwned && $ttlValid
        && ($manifest['status'] ?? null) === 'ready') {
        $state = 'ready_independent_readback';
    } elseif ($markerOwned && !$cachePresent) {
        $state = 'orphan_db_after_session_expiry_cleanup_candidate';
    }
} elseif ($counts['user'] === 0 && $counts['role'] === 0
    && $counts['menu'] === 0 && $counts['created_api'] === 0
    && !$cacheFileExists && !$cachePresent) {
    $userId = (int)($manifest['store_user_id'] ?? 0);
    $roleId = (int)($manifest['role_id'] ?? 0);
    $menuId = (int)($manifest['menu_id'] ?? 0);
    $leftovers = [
        'user_role' => $userId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_user_role WHERE STORE_USER_ID=?', [$userId]) : 0,
        'role_menu' => $roleId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_role_menu WHERE ROLE_ID=?', [$roleId]) : 0,
        'menu_api' => $menuId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_menu_api WHERE MENU_ID=?', [$menuId]) : 0,
        'user_relation' => $userId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_user_relation'
            . ' WHERE STORE_USER_ID=? OR RELATION_STORE_USER_ID=?', [$userId, $userId]) : 0,
        'staff_relation' => $userId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_staff_store_user WHERE STORE_USER_ID=?', [$userId]) : 0,
        'child_role' => $roleId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_role WHERE PARENT_ID=?', [$roleId]) : 0,
        'child_menu' => $menuId > 0 ? cat8_count($pdo,
            'SELECT COUNT(*) FROM yoshop_store_menu WHERE PARENT_ID=?', [$menuId]) : 0,
        'child_api' => (int)($manifest['created_api_id'] ?? 0) > 0
            ? cat8_count($pdo, 'SELECT COUNT(*) FROM yoshop_store_api WHERE PARENT_ID=?',
                [(int)$manifest['created_api_id']]) : 0,
    ];
    $detail = ['all_known_relation_counts_zero' => array_sum($leftovers) === 0,
        'cache_absent' => true];
    if (array_sum($leftovers) === 0) $state = 'independent_zero_readback';
}

echo json_encode(['status' => $state, 'target' => $target,
    'journal_phase' => (string)($manifest['phase'] ?? ''),
    'owned_base_counts' => $counts,
    'checks' => $detail, 'writes' => false,
    'recovery_hint' => $state === 'orphan_db_after_session_expiry_cleanup_candidate'
        ? 'review_exact_manifest_then_run_fixture_cleanup_mode'
        : 'none_or_manual_review'], JSON_UNESCAPED_SLASHES), PHP_EOL;
if (!in_array($state, ['ready_independent_readback',
    'independent_zero_readback'], true)) exit(3);
