<?php
declare(strict_types=1);

// Nonproduction C list-only regression. No business HTTP unless approved.
require __DIR__ . '/c_catalog_match_readonly.php';
const CDX_CATALOG_LIST_HTTP_TOKEN = 'CDX_CATALOG_LIST_HTTP_APPROVED_20261002_V1';
$stage = 'approved_dishes_service_source';
$expectedServiceSha = '8a60bb62974ab9b9662ad19ad12b4d0d4d9d1f9c89bef81c913d6e89887bb166';
foreach (['/tmp/zhct-ai-http-20260929.3XDj3w/app/api/service/dishes/Dishes.php',
    '/tmp/zhct-dm-poc-20260928.fcpiiN/ai_api/app/api/service/dishes/Dishes.php'] as $source) {
    if (!is_file($source) || hash_file('sha256', $source) !== $expectedServiceSha) {
        throw new RuntimeException('C DishesService source mismatch');
    }
}
$stage = 'catalog_two_natural_fixture';
if (!$naturalNonemptyGroup || !is_string($candidateKey)) {
    throw new RuntimeException('Natural mapped menu not safely available');
}
[$restaurantIdRaw, $mealDate, $mealTimesRaw] = explode('|', $candidateKey, 3);
$restaurantId = (int)$restaurantIdRaw;
$mealTimes = (int)$mealTimesRaw;
if ($restaurantId < 1 || $mealTimes < 1
    || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $mealDate)) {
    throw new RuntimeException('Catalog candidate metadata invalid');
}
$ai->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$store->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$rows = static function (PDO $pdo, string $sql, array $bind = []): array {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($bind);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
};
$orderScopeCounts = static function () use ($store, $restaurantId,
    $mealDate, $mealTimes): array {
    $order = $store->prepare('SELECT COUNT(*) FROM YDY_MEAL_ORDER'
        . ' WHERE RESTAURANT_ID=? AND MEAL_DATE=? AND MEAL_TIMES=?');
    $order->execute([$restaurantId, $mealDate, $mealTimes]);
    $menu = $store->prepare('SELECT COUNT(*) FROM YDY_INITIAL_MENU m'
        . ' INNER JOIN YDY_MEAL_ORDER o ON o.ID=m.MEAL_ORDER_ID'
        . ' WHERE o.RESTAURANT_ID=? AND o.MEAL_DATE=? AND o.MEAL_TIMES=?');
    $menu->execute([$restaurantId, $mealDate, $mealTimes]);
    return ['orders' => (int)$order->fetchColumn(),
        'joined_initial_menu' => (int)$menu->fetchColumn()];
};
$hashRows = static function (array $items): string {
    return hash('sha256', json_encode($items,
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
};
$categoryRows = static function () use ($ai, $rows): array {
    return $rows($ai, 'SELECT * FROM YOSHOP_DISHES_CATE'
        . ' WHERE PID=0 ORDER BY ID');
};
$recipeRows = static function () use ($store, $rows, $restaurantId,
    $mealDate, $mealTimes): array {
    return $rows($store,
        'SELECT r.ID,r.DISHES_UUID,r.RESTAURANT_ID,r.MENU_DATE,r.MEAL_TIMES,'
        . ' r.LIMIT_NUM,d.AI_DISHES_ID,d.PRICE,d.ORDER_NUM'
        . ' FROM YDY_RECIPES r INNER JOIN YDY_DISHES d'
        . ' ON d.UUID=r.DISHES_UUID INNER JOIN YDY_RESTAURANTS x'
        . ' ON x.ID=r.RESTAURANT_ID WHERE r.RESTAURANT_ID=?'
        . ' AND r.MENU_DATE=? AND r.MEAL_TIMES=?'
        . ' AND x.STATUS=1 AND x.IS_DELETE=2'
        . ' AND d.IS_DELETE=0 AND d.AI_DISHES_ID>0 ORDER BY r.ID DESC',
        [$restaurantId, $mealDate, $mealTimes]);
};
$aiDishRows = static function (array $recipeItems) use ($ai, $rows): array {
    $ids = array_values(array_unique(array_map('intval',
        array_column($recipeItems, 'ai_dishes_id'))));
    if (!$ids) return [];
    $params = implode(',', array_fill(0, count($ids), '?'));
    return $rows($ai, 'SELECT * FROM YOSHOP_DISHES'
        . ' WHERE STORE_ID=10001 AND IS_DELETE=0'
        . ' AND ID IN (' . $params . ') ORDER BY ID', $ids);
};
$stage = 'category_rows_preflight';
$catsBefore = $categoryRows();
$stage = 'no_cross_user_order_scope_preflight';
if ($orderScopeCounts() !== ['orders' => 0, 'joined_initial_menu' => 0]) {
    throw new RuntimeException('Natural group has other-user order data');
}
$stage = 'recipe_rows_preflight';
$recipesBefore = $recipeRows();
$stage = 'ai_dish_rows_preflight';
$dishesBefore = $aiDishRows($recipesBefore);
$dishIdSet = array_fill_keys(array_map('intval',
    array_column($dishesBefore, 'id')), true);
$expectedIds = [];
foreach ($recipesBefore as $recipe) {
    $id = (int)$recipe['ai_dishes_id'];
    if (isset($dishIdSet[$id])) $expectedIds[] = $id;
}
sort($expectedIds);
$baseline = [
    'categories' => $hashRows($catsBefore),
    'recipes' => $hashRows($recipesBefore),
    'dishes' => $hashRows($dishesBefore),
];
if (count($catsBefore) < 1 || count($recipesBefore) < 1
    || count($recipesBefore) > 15 || count($expectedIds) < 1) {
    throw new RuntimeException('Nonempty natural catalog fixture not isolated');
}
if (!hash_equals(CDX_CATALOG_LIST_HTTP_TOKEN,
    (string)getenv('CDX_CATALOG_LIST_HTTP_APPROVAL'))) {
    echo json_encode(['status' => 'catalog-list-c-default-readonly',
        'isolated_http_runtime' => true, 'external_stubs' => true,
        'approved_dishes_service_source' => true,
        'natural_recipe_rows_within_first_page' => true,
        'mapped_ai_dish_rows_positive' => true,
        'cross_user_orders_and_initial_menu_zero' => true,
        'candidate_group_hash' => hash('sha256', $candidateKey),
        'business_http_count' => 0, 'writes' => 0],
        JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

$safeFieldNames = array_fill_keys([
    'data', 'list', 'id', 'name', 'pid', 'children', 'sort', 'category',
    'total', 'per_page', 'current_page', 'last_page', 'last_page_url',
    'next_page_url', 'prev_page_url', 'first_page_url', 'path', 'from', 'to',
    'ai_dishes_id', 'price', 'order_num', 'weight', 'energy',
    'carbohydrate', 'protein', 'fat', 'file_id', 'file_path', 'tag',
    'quantity', 'limit_num', 'sold_num', 'bookable_num',
    'dishes_name', 'dishes_weight', 'dishes_number', 'unit',
    'store_id', 'is_delete', 'create_time', 'update_time',
], true);
$shape = null;
$shape = static function ($value, int $depth = 0) use (&$shape,
    $safeFieldNames) {
    if (!is_array($value)) return gettype($value);
    if ($depth >= 4) return ['array_count' => count($value)];
    if ($value === []) return ['list_count' => 0];
    if (array_keys($value) === range(0, count($value) - 1)) {
        return ['list_count' => count($value),
            'first' => $shape($value[0], $depth + 1)];
    }
    $out = [];
    $dynamic = 0;
    foreach ($value as $key => $item) {
        if (!is_string($key) || !isset($safeFieldNames[$key])) {
            $dynamic++;
            continue;
        }
        $out[$key] = $shape($item, $depth + 1);
    }
    if ($dynamic > 0) $out['dynamic_key_count'] = $dynamic;
    return $out;
};
$hasSensitive = null;
$hasSensitive = static function ($value) use (&$hasSensitive): bool {
    if (!is_array($value)) return false;
    foreach ($value as $key => $item) {
        if (is_string($key) && in_array(strtolower($key), [
            'api_key', 'password', 'secret', 'access_token',
            'refresh_token', 'authorization', 'zpkey', 'zpchargekey',
        ], true)) return true;
        if ($hasSensitive($item)) return true;
    }
    return false;
};
$request = static function (string $path, array $params, bool $withBody): array {
    $options = [
        'method' => 'POST', 'timeout' => 25, 'ignore_errors' => true,
        'header' => "platform: POC\r\nContent-Type: application/json;charset=utf-8\r\n",
    ];
    if ($withBody) {
        $options['content'] = json_encode($params,
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
    $text = file_get_contents('http://127.0.0.1:18102'
        . $path . '?storeId=10001', false,
        stream_context_create(['http' => $options]));
    $line = $http_response_header[0] ?? '';
    return [
        'http' => preg_match('/HTTP\/\S+\s+(\d+)/', $line, $match)
            ? (int)$match[1] : 0,
        'body' => is_string($text) ? json_decode($text, true) : null,
    ];
};
$cases = [];
$error = null;
try {
    $stage = 'dishes_order_list_real_http';
    if ($orderScopeCounts() !== ['orders' => 0, 'joined_initial_menu' => 0]) {
        throw new RuntimeException('Other-user order appeared before list request');
    }
    $order = $request('/api/dishes.dishes/list', [
        'categoryId' => 0, 'page' => 1,
        'restaurant_id' => $restaurantId,
        'meal_date' => $mealDate,
        'meal_times' => $mealTimes,
    ], true);
    $body = $order['body'];
    $data = is_array($body) ? ($body['data'] ?? null) : null;
    $list = is_array($data) ? ($data['list'] ?? null) : null;
    $items = is_array($list) ? ($list['data'] ?? null) : null;
    $sensitive = $hasSensitive($data);
    $actualIds = is_array($items)
        ? array_map('intval', array_column($items, 'ai_dishes_id')) : [];
    sort($actualIds);
    $allFiveFixed2 = is_array($items) && count($items) > 0;
    $priceTypes = [];
    foreach (is_array($items) ? $items : [] as $item) {
        if (!is_array($item)) {
            $allFiveFixed2 = false;
            continue;
        }
        foreach (['weight', 'energy', 'carbohydrate', 'protein', 'fat'] as $field) {
            if (!is_string($item[$field] ?? null)
                || preg_match('/^-?[0-9]+\.[0-9]{2}$/', $item[$field]) !== 1) {
                $allFiveFixed2 = false;
            }
        }
        $priceTypes[gettype($item['price'] ?? null)] = true;
    }
    $listOk = $order['http'] === 200 && is_array($body)
        && (int)($body['status'] ?? -1) === 200
        && is_array($items) && count($items) > 0
        && !$sensitive && $actualIds === $expectedIds && $allFiveFixed2
        && $orderScopeCounts() === ['orders' => 0, 'joined_initial_menu' => 0];
    $cases['dishes_order_list'] = [
        'method' => 'POST', 'path' => '/api/dishes.dishes/list',
        'http' => $order['http'],
        'business_status' => is_array($body) ? ($body['status'] ?? null) : null,
        'data_shape' => $sensitive ? 'blocked_sensitive_key' : $shape($data),
        'response_count' => is_array($items) ? count($items) : null,
        'ids_match_store_recipes_and_ai_dishes' => $actualIds === $expectedIds,
        'all_five_ai_decimals_fixed_two_string' => $allFiveFixed2,
        'price_json_types_only' => array_keys($priceTypes),
        'sensitive_key_present' => $sensitive, 'pass' => $listOk,
    ];
    if (!$listOk) throw new RuntimeException('Dishes order list not closed');
} catch (Throwable $failure) {
    $error = ['class' => get_class($failure),
        'code' => (string)$failure->getCode(), 'stage' => $stage];
}
$unchanged = $hashRows($categoryRows()) === $baseline['categories']
    && $hashRows($recipeRows()) === $baseline['recipes']
    && $hashRows($aiDishRows($recipesBefore)) === $baseline['dishes']
    && $orderScopeCounts() === ['orders' => 0, 'joined_initial_menu' => 0];
$pass = $error === null && count($cases) === 1
    && !in_array(false, array_column($cases, 'pass'), true) && $unchanged;
echo json_encode(['status' => $pass ? 'catalog-list-c-pass'
        : 'catalog-list-c-not-closed',
    'candidate_group_hash' => hash('sha256', $candidateKey),
    'cases' => $cases, 'source_rows_unchanged' => $unchanged,
    'error' => $error, 'writes' => 0],
    JSON_UNESCAPED_SLASHES), PHP_EOL;
exit($pass ? 0 : 3);
