<?php
declare(strict_types=1);

// Read-only, bounded acceptance for two Store screen APIs. No fixture or DML.
$lane = (string)($argv[1] ?? '');
$mode = (string)($argv[2] ?? '');
if (!in_array($lane, ['B', 'C'], true) || !in_array($mode, ['preflight', 'run'], true)) {
    fwrite(STDERR, "DENIED: fixed lane/mode required\n");
    exit(78);
}
$root = $lane === 'B' ? '/tmp/zhct-http-store-mysql.dQTTBP'
    : '/tmp/zhct-dm-poc-20260928.fcpiiN/store';
$port = $lane === 'B' ? 18081 : 18080;
$config = require $root . '/application/database.php';
if (($config['database'] ?? '') !== ($lane === 'B' ? 'zhct' : 'ZHCT_APP')
    || ($config['prefix'] ?? '') !== 'ydy_') {
    throw new RuntimeException('Unexpected Store database identity');
}
if ($lane === 'B') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    foreach (['hostname', 'database', 'username', 'password'] as $key) {
        if (($local[$key] ?? null) !== ($config[$key] ?? null)) {
            throw new RuntimeException('B does not use code-local development MySQL');
        }
    }
} elseif (($config['hostname'] ?? '') !== '127.0.0.1'
    || (int)($config['hostport'] ?? 0) !== 5237
    || getenv('POC_DIALECT') !== 'dm' || getenv('POC_SCHEMA') !== 'ZHCT_APP') {
    throw new RuntimeException('C is not isolated Store DM');
}
$dsn = $lane === 'B'
    ? 'mysql:host=' . $config['hostname'] . ';port=' . ($config['hostport'] ?? 3306)
        . ';dbname=zhct;charset=utf8'
    : 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
$db = new PDO($dsn, $config['username'], $config['password'], [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_CASE => PDO::CASE_LOWER,
]);
if ($lane === 'C' && $db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
    ->fetchColumn() !== 'ZHCT_APP') {
    throw new RuntimeException('C schema mismatch');
}
$rows = static function (string $sql, array $params = []) use ($db): array {
    $stmt = $db->prepare($sql);
    $stmt->execute($params);
    return array_map(static function (array $row): array {
        return array_change_key_case($row, CASE_LOWER);
    }, $stmt->fetchAll(PDO::FETCH_ASSOC));
};
$shape = static function ($value, bool $semantic = false) use (&$shape) {
    if (!is_array($value)) {
        $type = gettype($value);
        return $semantic && in_array($type, ['integer', 'double'], true) ? 'number' : $type;
    }
    if ($value === []) return ['array' => 'empty'];
    if (array_keys($value) === range(0, count($value) - 1)) {
        $variants = [];
        foreach ($value as $item) {
            $itemShape = $shape($item, $semantic);
            $variants[hash('sha256', json_encode($itemShape))] = $itemShape;
        }
        ksort($variants);
        return ['list' => array_values($variants)];
    }
    $mapped = [];
    foreach ($value as $key => $item) $mapped[(string)$key] = $shape($item, $semantic);
    ksort($mapped);
    return ['object' => $mapped];
};
$today = date('Y-m-d');
$restaurant = $rows('SELECT MORNING_START,MORNING_END,NOON_START,NOON_END'
    . ' FROM ydy_restaurants WHERE ID=1');
if (count($restaurant) !== 1) throw new RuntimeException('Restaurant 1 missing');
$clock = date('H:i:s');
$times = $restaurant[0];
$meal = $clock > substr((string)$times['morning_start'], 0, 8)
    && $clock <= substr((string)$times['morning_end'], 0, 8) ? 1
    : ($clock > substr((string)$times['noon_start'], 0, 8)
        && $clock < substr((string)$times['noon_end'], 0, 8) ? 2 : 3);
$supplySource = $rows('SELECT r.DISHES_UUID,d.NAME,d.CATEGORY FROM ydy_recipes r'
    . ' JOIN ydy_dishes d ON r.DISHES_UUID=d.UUID'
    . ' WHERE r.RESTAURANT_ID=1 AND r.MENU_DATE=? AND r.MEAL_TIMES=?', [$today, $meal]);
$candidateSql = 'SELECT a.STAFF_UUID,a.MENU_DATE,a.MEAL_TIMES,COUNT(*) AS N'
    . ' FROM ydy_date_menu a JOIN ydy_staff s ON a.STAFF_UUID=s.UUID'
    . ' JOIN ydy_dishes d ON a.DISHES_UUID=d.UUID'
    . ' WHERE a.MEAL_TYPE=3 AND a.MENU_DATE<=? AND s.IS_SHOW=1 AND s.WEIGHT>0'
    . ' GROUP BY a.STAFF_UUID,a.MENU_DATE,a.MEAL_TIMES'
    . ' HAVING COUNT(*)>0 ORDER BY a.MENU_DATE DESC,COUNT(*) DESC';
$candidateSql .= $lane === 'B' ? ' LIMIT 8' : ' FETCH FIRST 8 ROWS ONLY';
$candidates = $rows($candidateSql, [$today]);
$out = ['lane' => $lane, 'mode' => $mode, 'clock_meal' => $meal,
    'restaurant_one_recipes' => count($supplySource),
    'actual_meal_candidates' => count($candidates), 'business_dml' => 0];
if ($mode === 'preflight') {
    $out['candidate_dates'] = array_values(array_unique(array_map(static function (array $row): string {
        return substr((string)$row['menu_date'], 0, 10);
    }, $candidates)));
    echo json_encode($out, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}
$http = static function (string $path, string $method, array $form = []) use ($port): array {
    $options = ['method' => $method, 'ignore_errors' => true,
        'follow_location' => 0, 'timeout' => 30];
    if ($method === 'POST') {
        $options['header'] = "Content-Type: application/x-www-form-urlencoded\r\n";
        $options['content'] = http_build_query($form, '', '&', PHP_QUERY_RFC3986);
    }
    $handle = @fopen('http://127.0.0.1:' . $port . $path, 'rb', false,
        stream_context_create(['http' => $options]));
    if (!is_resource($handle)) throw new RuntimeException('Isolated HTTP unavailable at ' . $path);
    $raw = stream_get_contents($handle, 4194305);
    $headers = stream_get_meta_data($handle)['wrapper_data'] ?? [];
    fclose($handle);
    if (!is_string($raw) || strlen($raw) > 4194304) throw new RuntimeException('HTTP response exceeds cap');
    $body = json_decode($raw, true);
    $status = preg_match('/^HTTP\/\S+\s+(\d+)/', (string)($headers[0] ?? ''), $m)
        ? (int)$m[1] : 0;
    return ['status' => $status, 'body' => $body, 'sha256' => hash('sha256', $raw)];
};
$get = $http('/m/controlScreen/everyMealSupply', 'GET');
$post = $http('/m/controlScreen/everyMealSupply', 'POST', ['restaurant_id' => 1]);
$getBody = $get['body'];
$postBody = $post['body'];
$out['supply'] = ['get_status' => $get['status'], 'post_status' => $post['status'],
    'get_code' => $getBody['code'] ?? null, 'post_code' => $postBody['code'] ?? null,
    'get_post_json_equal' => $getBody === $postBody];
if ($get['status'] !== 200 || $post['status'] !== 200
    || !is_array($getBody) || !is_array($postBody)
    || (int)($getBody['code'] ?? -1) !== 0
    || $getBody !== $postBody) {
    throw new RuntimeException('Supply GET/POST same-lane contract mismatch');
}
$supply = $getBody['data'] ?? null;
if (!is_array($supply) || (int)($supply['meal_times'] ?? -1) !== $meal) {
    throw new RuntimeException('Supply current meal/source mismatch');
}
$sourceNames = [];
foreach ($supplySource as $item) {
    $category = (int)$item['category'];
    $sourceNames[$category][(string)$item['name']] = true;
}
$displayedNames = 0;
foreach (($supply['cookbook'] ?? []) as $category => $group) {
    $names = $group['data'] ?? null;
    if (!is_array($names) || (int)($group['num'] ?? -1) !== count(array_unique($names))) {
        throw new RuntimeException('Supply cookbook deduplicated count mismatch');
    }
    foreach ($names as $name) {
        if (!isset($sourceNames[(int)$category][(string)$name])) {
            throw new RuntimeException('Supply dish name absent from own recipe source');
        }
        $displayedNames++;
    }
}
$out['supply']['displayed_names'] = $displayedNames;
$out['supply']['source_nonempty'] = count($supplySource) > 0;
$out['supply']['data_keys'] = array_keys($supply);
$out['supply']['data_type_sha256'] = hash('sha256', json_encode(array_map('gettype', $supply)));
$out['supply']['recursive_type_sha256'] = hash('sha256', json_encode($shape($supply)));
$out['supply']['json_semantic_type_sha256'] = hash('sha256', json_encode($shape($supply, true)));
$supplyFieldTypeHashes = [];
foreach ($supply as $field => $value) {
    $supplyFieldTypeHashes[$field] = hash('sha256', json_encode($shape($value)));
}
$out['supply']['per_field_type_sha256'] = $supplyFieldTypeHashes;
$out['supply']['json_sha256'] = $get['sha256'];
$success = null;
$attempts = [];
foreach ($candidates as $candidate) {
    $date = substr((string)$candidate['menu_date'], 0, 10);
    $mealTimes = (int)$candidate['meal_times'];
    $staffUuid = (string)$candidate['staff_uuid'];
    $resp = $http('/m/OneMeal/oneMealForScreen', 'POST', [
        'date' => $date, 'meal' => $mealTimes, 'userid' => $staffUuid,
    ]);
    $body = $resp['body'];
    $attempts[] = ['date' => $date, 'meal' => $mealTimes,
        'source_rows' => (int)$candidate['n'], 'status' => $resp['status'],
        'code' => is_array($body) ? ($body['code'] ?? null) : null];
    if ($resp['status'] === 200 && is_array($body)
        && (int)($body['code'] ?? -1) === 0
        && is_array($body['data'] ?? null)) {
        $success = [$candidate, $body['data'], $resp['sha256']];
        break;
    }
}
$out['one_meal_attempts'] = $attempts;
if ($success !== null) {
    [$candidate, $data, $sha] = $success;
    $date = substr((string)$candidate['menu_date'], 0, 10);
    $mealTimes = (int)$candidate['meal_times'];
    $staffUuid = (string)$candidate['staff_uuid'];
    $source = $rows('SELECT a.DISHES_UUID,d.NAME,d.CATEGORY'
        . ' FROM ydy_date_menu a JOIN ydy_dishes d ON a.DISHES_UUID=d.UUID'
        . ' WHERE a.STAFF_UUID=? AND a.MENU_DATE=? AND a.MEAL_TIMES=?'
        . ' AND a.MEAL_TYPE=3', [$staffUuid, $date, $mealTimes]);
    $menu = $data['menu_list'] ?? null;
    $sourceDishes = [];
    foreach ($source as $item) {
        $sourceDishes[(string)$item['dishes_uuid']] = (string)$item['name'];
    }
    $matched = 0;
    $firstTypes = null;
    if (is_array($menu)) {
        foreach ($menu as $item) {
            if (!is_array($item)) continue;
            $uuid = (string)($item['uuid'] ?? '');
            if (!isset($sourceDishes[$uuid])
                || (string)($item['name'] ?? '') !== $sourceDishes[$uuid]) {
                throw new RuntimeException('OneMeal dish UUID/name not in own date_menu source');
            }
            if ($firstTypes === null) {
                $firstTypes = array_map('gettype', $item);
                ksort($firstTypes);
            }
            $matched++;
        }
    }
    if ($matched === 0 || $matched !== count($source)) {
        throw new RuntimeException('OneMeal returned dish count differs from own source');
    }
    $sourceAfter = $rows('SELECT a.DISHES_UUID,d.NAME,d.CATEGORY'
        . ' FROM ydy_date_menu a JOIN ydy_dishes d ON a.DISHES_UUID=d.UUID'
        . ' WHERE a.STAFF_UUID=? AND a.MENU_DATE=? AND a.MEAL_TIMES=?'
        . ' AND a.MEAL_TYPE=3', [$staffUuid, $date, $mealTimes]);
    if ($source !== $sourceAfter) throw new RuntimeException('Own source changed during read');
    $out['one_meal'] = ['business_ok' => true, 'date' => $date,
        'meal' => $mealTimes, 'staff_uuid_sha256' => hash('sha256', $staffUuid),
        'source_rows' => count($source), 'matched_dishes' => $matched,
        'data_keys' => array_keys($data),
        'data_type_sha256' => hash('sha256', json_encode(array_map('gettype', $data))),
        'recursive_type_sha256' => hash('sha256', json_encode($shape($data))),
        'json_semantic_type_sha256' => hash('sha256', json_encode($shape($data, true))),
        'first_dish_types' => $firstTypes,
        'json_sha256' => $sha];
    $fieldTypeHashes = [];
    foreach ($data as $field => $value) {
        $fieldTypeHashes[$field] = hash('sha256', json_encode($shape($value)));
    }
    $out['one_meal']['per_field_type_sha256'] = $fieldTypeHashes;
    $out['one_meal']['else_nutriend_types'] = is_array($data['else_nutriend'] ?? null)
        ? array_map('gettype', $data['else_nutriend']) : null;
} else {
    $out['one_meal'] = ['business_ok' => false,
        'reason' => count($candidates) ? 'candidate_http_failed' : 'no_natural_actual_meal_source'];
}
$supplySourceAfter = $rows('SELECT r.DISHES_UUID,d.NAME,d.CATEGORY FROM ydy_recipes r'
    . ' JOIN ydy_dishes d ON r.DISHES_UUID=d.UUID'
    . ' WHERE r.RESTAURANT_ID=1 AND r.MENU_DATE=? AND r.MEAL_TIMES=?', [$today, $meal]);
if ($supplySource !== $supplySourceAfter) {
    throw new RuntimeException('Supply original recipe source changed during read');
}
$out['sources_unchanged'] = true;
echo json_encode($out, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), PHP_EOL;
