<?php
// Serveur Yumigo sur youmigo.ch (hébergement web Infomaniak, PHP 8.1+).
//
//   GET  /confidentialite          politique de confidentialité (aussi /privacy)
//   GET  /join?code=FAM-XXXXXX     page d'accueil d'une invitation famille
//   GET  /v1/world/CH              recettes « Cuisine du monde » (world/CH.json)
//   GET  /v1/elite                 catalogue Yumigo Élite du mois (elite/current.json)
//   GET  /v1/photos/<id>.jpg       photographie d'une recette (photos/<id>.jpg)
//   GET  /v1/prices/CH?items=…     prix des rayons (Pepesto / Apify / Barcode Lookup, cache 24 h)
//   GET  /v1/distance?origin=…&destinations=…  trajets avec trafic (Google)
//   POST /v1/vision/inventory      photo du frigo → produits (LogMeal)
//   POST /v1/voice/transcribe      dictée (audio m4a) → texte (OpenAI Whisper)
//
// Les recettes IA (/v1/recipes/leftovers) et les suggestions (/v1/feedback)
// restent sur le serveur Node : ici elles répondent 501 et l'application
// bascule sur ses recettes hors-ligne.

declare(strict_types=1);

// Dossier privé : à côté du dossier web (recommandé), sinon dedans (protégé
// par son .htaccess).
$private = is_dir(__DIR__ . '/../private') ? __DIR__ . '/../private' : __DIR__ . '/private';
require $private . '/gateway.php';

function send(int $status, mixed $body): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: content-type');
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    echo json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function send_file(string $file, string $type, int $maxAge): never
{
    if (!is_file($file)) send(404, ['error' => 'Introuvable']);
    header("Content-Type: $type");
    header("Cache-Control: public, max-age=$maxAge");
    header('Access-Control-Allow-Origin: *');
    header('Content-Length: ' . filesize($file));
    if ($_SERVER['REQUEST_METHOD'] !== 'HEAD') readfile($file);
    exit;
}

function read_raw(int $limit): string
{
    $length = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
    if ($length > $limit) send(413, ['error' => 'Requête trop grande']);
    $body = (string) file_get_contents('php://input', false, null, 0, $limit + 1);
    if (strlen($body) > $limit) send(413, ['error' => 'Requête trop grande']);
    return $body;
}

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$path = rtrim((string) parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH), '/');
$ip = $_SERVER['REMOTE_ADDR'] ?? '?';
$get = $method === 'GET' || $method === 'HEAD';

if ($method === 'OPTIONS') send(204, new stdClass());

if ($get && in_array($path, ['/confidentialite', '/confidentialite.html', '/privacy'], true)) {
    send_file(__DIR__ . '/confidentialite.html', 'text/html; charset=utf-8', 3600);
}

// Lien d'invitation ouvert hors de l'appli : on affiche le code à saisir.
if ($get && $path === '/join') {
    $code = preg_match('/^FAM-[2-9A-Z]{6}$/', $_GET['code'] ?? '') ? $_GET['code'] : null;
    header('Content-Type: text/html; charset=utf-8');
    echo '<!doctype html><html lang="fr"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">'
        . '<title>Rejoindre une famille Yumigo</title><body style="font-family:system-ui;background:#0E0C1A;color:#fff;text-align:center;padding:48px 16px">'
        . '<h1>👨‍👩‍👧 Rejoindre la famille</h1><p>Ouvrez Yumigo → menu ☰ → « Rejoindre ma famille », puis tapez le code :</p>'
        . ($code ? '<p style="font-size:32px;font-weight:900;letter-spacing:4px;color:#FFB347">' . htmlspecialchars($code) . '</p>' : '')
        . '</body></html>';
    exit;
}

// Recettes du monde : code ISO strictement sur 2 lettres (aucun chemin arbitraire).
if ($get && preg_match('#^/v1/world/([A-Z]{2})$#', $path, $m)) {
    $file = __DIR__ . "/world/{$m[1]}.json";
    $data = is_file($file) ? json_decode((string) file_get_contents($file), true) : null;
    if (!is_array($data['recipes'] ?? null)) send(404, ['error' => 'Pas encore de recettes pour ce pays']);
    send_file($file, 'application/json; charset=utf-8', 3600);
}

if ($get && $path === '/v1/elite') {
    $file = __DIR__ . '/elite/current.json';
    $data = is_file($file) ? json_decode((string) file_get_contents($file), true) : null;
    if (!is_string($data['month'] ?? null) || !is_array($data['recipes'] ?? null)) send(404, ['error' => 'Pas de catalogue Élite publié']);
    send_file($file, 'application/json; charset=utf-8', 3600);
}

if ($get && preg_match('#^/v1/photos/([a-z0-9-]{1,80})\.jpg$#', $path, $m)) {
    if (!is_file(__DIR__ . "/photos/{$m[1]}.jpg")) send(404, ['error' => 'Pas encore de photo']);
    send_file(__DIR__ . "/photos/{$m[1]}.jpg", 'image/jpeg', 604800);
}

try {
    if ($method === 'GET' && preg_match('#^/v1/prices/([A-Z]{2})$#', $path, $m)) {
        if (!allow($ip, 'prices')) send(429, ['error' => 'Trop de demandes de prix']);
        send(200, prices($m[1], clean_items($_GET['items'] ?? null)));
    }
    if ($method === 'GET' && $path === '/v1/distance') {
        if (!allow($ip, 'distance')) send(429, ['error' => 'Trop de demandes de trajet']);
        $destinations = array_values(array_filter(explode('|', (string) ($_GET['destinations'] ?? ''))));
        send(200, ['legs' => distances((string) ($_GET['origin'] ?? ''), $destinations)]);
    }
    if ($method === 'POST' && $path === '/v1/vision/inventory') {
        if (!allow($ip, 'vision')) send(429, ['error' => 'Trop de photos, réessayez plus tard']);
        $body = json_decode(read_raw(4_200_000), true);
        if (!is_string($body['image'] ?? null)) send(400, ['error' => 'Photo invalide']);
        send(200, vision_logmeal($body['image']));
    }
    if ($method === 'POST' && $path === '/v1/voice/transcribe') {
        if (!allow($ip, 'voice')) send(429, ['error' => 'Trop de dictées, réessayez plus tard']);
        $audio = read_raw(4_000_000);
        if (strlen($audio) < 1000) send(400, ['error' => 'Audio vide']);
        $language = ($_GET['language'] ?? 'fr') === 'en' ? 'en' : 'fr';
        send(200, ['text' => transcribe($audio, $_SERVER['CONTENT_TYPE'] ?? 'audio/mp4', $language)]);
    }
    if ($method === 'POST' && in_array($path, ['/v1/recipes/leftovers', '/v1/feedback'], true)) {
        send(501, ['error' => 'Service assuré par le serveur Node']);
    }
} catch (GatewayError $e) {
    send($e->status, ['error' => $e->getMessage()]);
} catch (Throwable $e) {
    error_log('Yumigo : ' . $e->getMessage());
    send(502, ['error' => 'Service externe indisponible']);
}

send(404, ['error' => 'Introuvable']);
