<?php
declare(strict_types=1);

// Resolve registered routes only: never boot App or instantiate a controller.
$root = '/workspace/zhct/zhctproject/store/';
$control = '/workspace/zhct/zhctprompt/';
$coverage = $control . 'work/2026-09-28-smart-canteen-dm8-poc/http-cron-coverage-matrix.csv';
define('APP_PATH', $root . 'application/');
define('ROOT_PATH', $root);
define('TP_ENV', 'local');
define('BUSINESS', '');
require $root . 'thinkphp/base.php';
\think\Config::set('route_check_cache', false);
\think\Config::set('url_convert', false);
\think\App::$debug = true;
$routeSource = file_get_contents(APP_PATH . 'route.php');
if (strpos($routeSource, 'before_behavior') !== false || preg_match('/[\x27"]callback[\x27"]\s*=>/', $routeSource)) {
    throw new RuntimeException('Route callback requires separate safety review');
}
require APP_PATH . 'route.php';
set_exception_handler(static function (Throwable $error) {
    fwrite(STDERR, get_class($error) . ': ' . $error->getMessage() . "\n");
    exit(1);
});

$handle = fopen($coverage, 'r');
$header = fgetcsv($handle);
$keys = [];
while (($cells = fgetcsv($handle)) !== false) {
    $row = array_combine($header, $cells);
    if ($row['kind'] !== 'http' || $row['repo'] !== 'store') continue;
    foreach (explode('|', strtoupper($row['method'])) as $method) {
        $method = trim($method);
        $path = '/' . trim(explode('?', explode('#', $row['route_or_command'])[0])[0], '/');
        $key = $method . ' ' . strtolower($path);
        if (!isset($keys[$key])) $keys[$key] = ['registered_method' => $method, 'path' => $path,
            'risk' => $row['risk'], 'module' => $row['module'], 'source' => $row['source']];
    }
}
fclose($handle);

$findFile = static function (string $base, string $relative): ?string {
    $at = rtrim($base, '/');
    foreach (explode('/', $relative) as $segment) {
        if ($segment === '' || $segment === '..' || !is_dir($at)) return null;
        if (is_file($at . '/' . $segment) || is_dir($at . '/' . $segment)) {
            $at .= '/' . $segment;
            continue;
        }
        $matches = array_values(array_filter(scandir($at), static function ($name) use ($segment) {
            return strcasecmp($name, $segment) === 0;
        }));
        if (count($matches) !== 1) return null;
        $at .= '/' . $matches[0];
    }
    return is_file($at) ? $at : null;
};
$declaredMethods = static function (string $file): array {
    $tokens = token_get_all(file_get_contents($file));
    $methods = [];
    foreach ($tokens as $index => $token) {
        if (!is_array($token) || $token[0] !== T_FUNCTION) continue;
        for ($next = $index + 1; $next < count($tokens); $next++) {
            $candidate = $tokens[$next];
            if (is_array($candidate) && in_array($candidate[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) continue;
            if ($candidate === '&') continue;
            // PHP permits reserved tokens such as T_LIST as method names.
            if (is_array($candidate) && preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/D', $candidate[1])) {
                $methods[] = strtolower($candidate[1]);
            }
            break;
        }
    }
    return $methods;
};

$results = [];
$stats = [];
foreach ($keys as $key => $candidate) {
    // '*' does not establish either actual HTTP method; preserve that uncertainty.
    $probeMethods = $candidate['registered_method'] === '*' ? ['GET', 'POST'] : [$candidate['registered_method']];
    foreach ($probeMethods as $method) {
        $request = \think\Request::create($candidate['path'], $method);
        \think\Request::instance($request);
        $dispatch = \think\Route::check($request, ltrim($candidate['path'], '/'), '/', false);
        $item = $candidate + ['inventory_key' => $key, 'probe_method' => $method,
            'wildcard_probe_only' => $candidate['registered_method'] === '*',
            'dispatch' => $dispatch, 'route_info' => $request->routeInfo()];
        if ($dispatch === false) {
            $item['classification'] = 'no_declared_match_default_dispatch_not_evaluated';
        } elseif (($dispatch['type'] ?? '') !== 'module') {
            $item['classification'] = 'non_module_dispatch_not_executed';
        } else {
            [$module, $controller, $action] = $dispatch['module'];
            $item['resolved_target'] = compact('module', 'controller', 'action');
            if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/D', $module)
                || !is_dir(APP_PATH . $module)) {
                $item['classification'] = 'declared_target_module_directory_missing';
            } elseif (!preg_match('/^[A-Za-z0-9_.\/]+$/D', $controller)) {
                $item['classification'] = 'dynamic_controller_requires_review';
            } else {
                $file = $findFile(APP_PATH . $module . '/controller', str_replace('.', '/', $controller) . '.php');
                if ($file === null) {
                    $item['classification'] = 'declared_target_controller_file_missing_empty_fallback_not_evaluated';
                } else {
                    $item['controller_file'] = substr($file, strlen($root));
                    $item['controller_sha256'] = hash_file('sha256', $file);
                    $item['declared_action_found_case_insensitive'] = in_array(strtolower($action), $declaredMethods($file), true);
                    $item['classification'] = $item['declared_action_found_case_insensitive']
                        ? 'declared_target_source_action_exists_not_HTTP_verified'
                        : 'action_not_declared_in_file_inheritance_not_evaluated';
                }
            }
        }
        $stats[$item['classification']] = ($stats[$item['classification']] ?? 0) + 1;
        $results[] = $item;
    }
}
ksort($stats);
$report = ['status' => 'ROUTE_RESOLUTION_AND_SOURCE_ONLY_NOT_COMPATIBILITY_PASS',
    'coverage_input_sha256' => hash_file('sha256', $coverage),
    'route_source_sha256' => hash_file('sha256', APP_PATH . 'route.php'),
    'framework_route_sha256' => hash_file('sha256', $root . 'thinkphp/library/think/Route.php'),
    'registered_store_keys' => count($keys), 'method_resolution_attempts' => count($results),
    'statistics' => $stats, 'HTTP' => 0, 'PDO_connections' => 0, 'business_SQL' => 0,
    'controllers_constructed' => 0, 'matrix_mutations' => 0,
    'case_insensitive_file_search_is_source_reference_not_runtime_autoload_proof' => true,
    'domain_binding_default_dispatch_inheritance_authorization_DB_not_tested' => true,
    'results' => $results];
file_put_contents(__DIR__ . '/store-route-audit-v2.json', json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n");
unset($report['results']);
echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), "\n";
