<?php
declare(strict_types=1);

ini_set('display_errors', '0');
error_reporting(E_ALL);

define('ROOT_DIR', dirname(__DIR__));
define('DATA_DIR', ROOT_DIR . '/data');
define('API_DIR',  __DIR__);

require_once ROOT_DIR . '/config/app.php';
require_once API_DIR  . '/Honeypot.php';
require_once API_DIR  . '/Auth.php';

// ── Session ───────────────────────────────────────────────────────────────────
session_name('hw_session');
session_set_cookie_params([
    'lifetime' => 0,
    'path'     => '/',
    'secure'   => isset($_SERVER['HTTPS']),
    'httponly' => true,
    'samesite' => 'Lax',
]);
session_start();

header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Pragma: no-cache');

// ── Honeypot ──────────────────────────────────────────────────────────────────
$honeypot = new Honeypot(DATA_DIR);
if (!empty($_GET['_honeypot'])) $honeypot->handleHoneypotRequest();
$honeypot->enforceBlock();

// ── Route parsing ─────────────────────────────────────────────────────────────
$uri      = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$uri      = trim(preg_replace('#^.*?/api(?:/index\.php)?#', '', $uri), '/');
$segments = $uri ? explode('/', $uri) : [];
$resource = $segments[0] ?? '';
$sub      = $segments[1] ?? '';
$id       = $segments[2] ?? '';
$method   = $_SERVER['REQUEST_METHOD'];

$auth = new Auth(DATA_DIR);

// ── Helpers ───────────────────────────────────────────────────────────────────
function getInput(): array
{
    static $parsed = null;
    if ($parsed !== null) return $parsed;
    $raw    = file_get_contents('php://input');
    $parsed = json_decode($raw, true) ?? [];
    return $parsed;
}

function respond(mixed $data, int $status = 200): never
{
    http_response_code($status);
    echo json_encode(['success' => true, 'data' => $data], JSON_UNESCAPED_UNICODE);
    exit;
}

function respondError(string $message, int $status = 400): never
{
    http_response_code($status);
    echo json_encode(['success' => false, 'error' => $message, 'code' => $status], JSON_UNESCAPED_UNICODE);
    exit;
}

function requireCsrf(Auth $auth): void
{
    $token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!$auth->verifyCsrfToken($token)) {
        respondError('Invalid request', 403);
    }
}

// ── Router ────────────────────────────────────────────────────────────────────
try {
    switch ($resource) {

        // ── CSRF token (public) ───────────────────────────────────────────────
        case 'csrf':
            if ($method === 'GET') respond(['token' => $auth->generateCsrfToken()]);
            respondError('Not found', 404);

        // ── Auth ──────────────────────────────────────────────────────────────
        case 'auth':
            if ($sub === 'login' && $method === 'POST') {
                $input = getInput();
                if ($auth->login($input['password'] ?? '')) respond(['authenticated' => true]);
                respondError('Invalid password', 401);
            }
            if ($sub === 'logout' && $method === 'POST') {
                $auth->requireAuth();
                $auth->logout();
                respond(['authenticated' => false]);
            }
            if ($sub === 'status' && $method === 'GET') {
                respond(['authenticated' => $auth->isAuthenticated()]);
            }
            respondError('Not found', 404);

        // ── Sections ──────────────────────────────────────────────────────────
        case 'sections':
            $auth->requireAuth();
            require_once API_DIR . '/Sections.php';
            $sections = new Sections(DATA_DIR);

            if ($method === 'GET') {
                respond($sections->getAll());
            }
            if ($method === 'POST') {
                respond($sections->create(getInput()), 201);
            }
            // PUT /sections/order
            if ($method === 'PUT' && $sub === 'order') {
                $sections->reorder(getInput()['ids'] ?? []);
                respond(['reordered' => true]);
            }
            // PUT /sections/{id}
            if ($method === 'PUT' && $sub) {
                respond($sections->update($sub, getInput()));
            }
            // DELETE /sections/{id}
            if ($method === 'DELETE' && $sub) {
                $sections->delete($sub);
                respond(['deleted' => true]);
            }
            respondError('Not found', 404);

        // ── Design ────────────────────────────────────────────────────────────
        case 'design':
            $auth->requireAuth();
            require_once API_DIR . '/Design.php';
            $design = new Design(DATA_DIR);

            if ($method === 'GET') respond($design->get());
            if ($method === 'PUT') respond($design->update(getInput()));
            respondError('Not found', 404);

        // ── Gallery ───────────────────────────────────────────────────────────
        case 'gallery':
            $auth->requireAuth();
            require_once API_DIR . '/Gallery.php';
            $gallery = new Gallery(DATA_DIR, ROOT_DIR);

            // Albums
            if ($sub === 'albums') {
                // Reorder: PUT /gallery/albums/order
                if ($id === 'order' && $method === 'PUT') {
                    $gallery->reorderAlbums(getInput()['ids'] ?? []);
                    respond(['reordered' => true]);
                }
                if ($method === 'GET')    respond($gallery->getAlbums());
                if ($method === 'POST')   respond($gallery->createAlbum(getInput()), 201);
                if ($method === 'PUT'  && $id) respond($gallery->updateAlbum($id, getInput()));
                if ($method === 'DELETE' && $id) { $gallery->deleteAlbum($id); respond(['deleted' => true]); }
                respondError('Not found', 404);
            }

            // Bulk assign album: PUT /gallery/bulk
            if ($sub === 'bulk' && $method === 'PUT') {
                $input   = getInput();
                $albumId = (isset($input['album_id']) && $input['album_id']) ? $input['album_id'] : null;
                $gallery->bulkAssignAlbum($input['ids'] ?? [], $albumId);
                respond(['updated' => true]);
            }

            // Upload
            if ($sub === 'upload' && $method === 'POST') {
                respond($gallery->upload($_FILES));
            }

            // Order
            if ($sub === 'order' && $method === 'PUT') {
                $gallery->reorderImages(getInput()['ids'] ?? []);
                respond(['reordered' => true]);
            }

            // Assign album to image: PUT /gallery/{id}/album
            if ($id === 'album' && $method === 'PUT') {
                respond($gallery->assignAlbum($sub, getInput()['album_id'] ?? null));
            }

            // Delete image
            if ($method === 'DELETE' && $sub) {
                $gallery->delete($sub);
                respond(['deleted' => true]);
            }

            // Get all
            if ($method === 'GET') respond($gallery->getAll());
            respondError('Not found', 404);

        // ── Guests ────────────────────────────────────────────────────────────
        case 'guests':
            $auth->requireAuth();
            require_once API_DIR . '/Guests.php';
            $guests = new Guests(DATA_DIR);

            // Groups
            if ($sub === 'groups') {
                if ($method === 'GET')    respond($guests->getGroups());
                if ($method === 'POST')   respond($guests->createGroup(getInput()), 201);
                if ($method === 'PUT'  && $id) respond($guests->updateGroup($id, getInput()));
                if ($method === 'DELETE' && $id) { $guests->deleteGroup($id); respond(['deleted' => true]); }
                respondError('Not found', 404);
            }

            // CSV export: GET /guests/export
            if ($sub === 'export' && $method === 'GET') {
                $groupId = $_GET['group'] ?? null;
                $csv     = $guests->exportCsv($groupId);
                header('Content-Type: text/csv; charset=utf-8');
                header('Content-Disposition: attachment; filename="gaesteliste.csv"');
                echo "\xEF\xBB\xBF" . $csv; // BOM for Excel
                exit;
            }

            // Mail: GET /guests/mail → status   POST /guests/mail → start batch send
            if ($sub === 'mail') {
                require_once API_DIR . '/Mailer.php';
                $mailer    = new Mailer(DATA_DIR, ROOT_DIR);
                $draftFile = DATA_DIR . '/mail_draft.json';

                // Config (public, non-sensitive): GET /guests/mail/config
                if ($id === 'config' && $method === 'GET') {
                    $cfg = file_exists(DATA_DIR . '/mail_config.json')
                        ? (json_decode(file_get_contents(DATA_DIR . '/mail_config.json'), true) ?? [])
                        : [];
                    respond([
                        'wedding_date' => $cfg['wedding_date'] ?? '',
                        'from_name'    => $cfg['from_name']    ?? '',
                        'site_url'     => $cfg['site_url']     ?? '',
                    ]);
                }

                // Draft: GET /guests/mail/draft  POST /guests/mail/draft
                if ($id === 'draft') {
                    if ($method === 'GET') {
                        $draft = file_exists($draftFile)
                            ? (json_decode(file_get_contents($draftFile), true) ?? [])
                            : [];
                        respond($draft);
                    }
                    if ($method === 'POST') {
                        $raw     = getInput();
                        $allowed = ['templates', 'default_lang', 'header_image', 'header_image_thumb', 'filter', 'filter_value', 'design'];
                        $input   = array_intersect_key($raw, array_flip($allowed));
                        if (isset($input['templates'])) {
                            if (!is_array($input['templates'])) respondError('templates must be an object.', 422);
                            foreach ($input['templates'] as $tpl) {
                                if (isset($tpl['subject']) && mb_strlen($tpl['subject']) > 255)
                                    respondError('Subject too long (max 255 chars).', 422);
                                if (isset($tpl['body']) && mb_strlen($tpl['body']) > 102400)
                                    respondError('Body too long (max 100 KB).', 422);
                            }
                        }
                        $input['saved_at'] = date('c');
                        file_put_contents($draftFile, json_encode($input, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
                        respond(['saved' => true, 'saved_at' => $input['saved_at']]);
                    }
                    respondError('Not found', 404);
                }

                if ($method === 'GET') {
                    respond($mailer->getStatus());
                }

                if ($method === 'POST') {
                    $input       = getInput();
                    $templates   = $input['templates']         ?? [];
                    $defaultLang = trim($input['default_lang'] ?? 'de');
                    $filter      = $input['filter']            ?? 'all';
                    $filterVal   = $input['filter_value']      ?? '';
                    $headerImage = trim($input['header_image'] ?? '');
                    $design      = is_array($input['design'] ?? null) ? $input['design'] : [];

                    if (!is_array($templates) || empty($templates)) respondError('templates is required.', 422);
                    $defTpl = $templates[$defaultLang] ?? reset($templates);
                    if (empty(trim($defTpl['subject'] ?? ''))) respondError('Subject is required.', 422);
                    if (empty(trim($defTpl['body']    ?? ''))) respondError('Body is required.',    422);
                    if ($mailer->isRunning())                  respondError('A mail job is already running.', 409);

                    // Resolve recipients + validate e-mail format
                    $allGuests = $guests->getAll(
                        $filter === 'group' ? $filterVal : null,
                        $filter === 'rsvp'  ? $filterVal : null
                    );
                    $recipients = array_values(array_filter(
                        $allGuests,
                        fn($g) => !empty($g['email']) && filter_var($g['email'], FILTER_VALIDATE_EMAIL)
                    ));

                    if (empty($recipients)) respondError('No recipients with an email address found.', 422);

                    // Send HTTP response NOW, close the connection, then keep sending mails.
                    // This way the frontend gets an immediate answer and can start polling.
                    $total        = count($recipients);
                    $responseBody = json_encode(
                        ['success' => true, 'data' => ['started' => true, 'total' => $total]],
                        JSON_UNESCAPED_UNICODE
                    );
                    http_response_code(200);
                    header('Content-Type: application/json; charset=utf-8');
                    header('Content-Length: ' . strlen($responseBody));
                    header('Connection: close');
                    if (ob_get_level()) ob_end_clean();
                    echo $responseBody;
                    flush();

                    // PHP process continues (ignore_user_abort is set inside sendBatch)
                    $mailer->sendBatch($recipients, $templates, $defaultLang, $headerImage, $design);
                    exit;
                }

                respondError('Not found', 404);
            }

            // CRUD
            if ($method === 'GET') {
                respond([
                    'groups' => $guests->getGroups(),
                    'guests' => $guests->getAll($_GET['group'] ?? null, $_GET['rsvp'] ?? null),
                ]);
            }
            if ($method === 'POST')   respond($guests->create(getInput()), 201);
            if ($method === 'PUT'  && $sub) respond($guests->update($sub, getInput()));
            if ($method === 'DELETE' && $sub) { $guests->delete($sub); respond(['deleted' => true]); }
            respondError('Not found', 404);

        // ── Kuchenliste ───────────────────────────────────────────────────────
        case 'kuchenliste':
            require_once API_DIR . '/Kuchenliste.php';
            $kl = new Kuchenliste(DATA_DIR);

            // POST /kuchenliste — public submit
            if ($method === 'POST' && !$sub) {
                $honeypot->enforceBlock();
                requireCsrf($auth);
                respond($kl->submit(getInput()));
            }
            // GET /kuchenliste/list — admin or public stripped
            if ($sub === 'list' && $method === 'GET') {
                if ($auth->isAuthenticated()) {
                    respond($kl->getAll());
                } else {
                    respond($kl->getPublic());
                }
            }
            // PUT /kuchenliste/item/{id}
            if ($sub === 'item' && $id && $method === 'PUT') {
                $auth->requireAuth();
                respond($kl->update($id, getInput()));
            }
            // DELETE /kuchenliste/item/{id}
            if ($sub === 'item' && $id && $method === 'DELETE') {
                $auth->requireAuth();
                $kl->delete($id);
                respond(['deleted' => true]);
            }
            respondError('Not found', 404);

        // ── Geschenketisch ────────────────────────────────────────────────────
        case 'geschenketisch':
            require_once API_DIR . '/Geschenketisch.php';

            // POST /geschenketisch/reserve — public
            if ($sub === 'reserve' && $method === 'POST') {
                $honeypot->enforceBlock();
                requireCsrf($auth);
                $g = new Geschenketisch(DATA_DIR);
                respond($g->reserve(getInput()));
            }
            // PUT /geschenketisch/item/{sectionId} — admin
            if ($sub === 'item' && $id && $method === 'PUT') {
                $auth->requireAuth();
                $input     = getInput();
                $itemIndex = (int)($input['item_index'] ?? -1);
                if ($itemIndex < 0) respondError('item_index fehlt', 400);
                respond((new Geschenketisch(DATA_DIR))->updateItem($id, $itemIndex, $input));
            }
            respondError('Not found', 404);

        // ── Gallery ZIP download ───────────────────────────────────────────────
        case 'gallery-zip':
            // Public endpoint — no auth required (XL images are already public)
            require_once API_DIR . '/Gallery.php';
            $gallery = new Gallery(DATA_DIR, ROOT_DIR);
            $gallery->downloadZip($_GET['album'] ?? null);

        // ── RSVP (public) ─────────────────────────────────────────────────────
        case 'rsvp':
            if ($method === 'POST') {
                requireCsrf($auth);
                require_once API_DIR . '/Rsvp.php';
                require_once API_DIR . '/Guests.php';
                $rsvp   = new Rsvp(DATA_DIR);
                $guests = new Guests(DATA_DIR);
                respond($rsvp->submit(getInput(), $guests));
            }
            respondError('Not found', 404);

        // ── Languages ─────────────────────────────────────────────────────────
        case 'languages':
            $auth->requireAuth();
            require_once API_DIR . '/Languages.php';
            $languages = new Languages(DATA_DIR);

            if ($method === 'GET') respond($languages->get());
            if ($method === 'PUT') respond($languages->update(getInput()));
            respondError('Not found', 404);

        // ── Navigation ────────────────────────────────────────────────────────
        case 'navigation':
            $auth->requireAuth();
            require_once API_DIR . '/Navigation.php';
            $nav = new Navigation(DATA_DIR);
            if ($method === 'GET') respond($nav->get());
            if ($method === 'PUT') respond($nav->update(getInput()));
            respondError('Not found', 404);

        // ── Media (image library for section editor) ───────────────────────────
        case 'media':
            $auth->requireAuth();

            // POST /media/generate-thumbs — samples only
            if ($sub === 'generate-thumbs' && $method === 'POST') {
                ini_set('memory_limit', '512M'); set_time_limit(120);
                if (!function_exists('imagecreatefromjpeg')) respond(['count' => 0, 'error' => 'GD extension not available']);
                respond(generateThumbsForDir(str_replace('\\', '/', ROOT_DIR . '/assets/img/samples')));
            }

            // POST /media/generate-mailheader-thumbs — samples-mailheader only
            if ($sub === 'generate-mailheader-thumbs' && $method === 'POST') {
                ini_set('memory_limit', '512M'); set_time_limit(120);
                if (!function_exists('imagecreatefromjpeg')) respond(['count' => 0, 'error' => 'GD extension not available']);
                respond(generateMailHeaderThumbs(str_replace('\\', '/', ROOT_DIR . '/assets/img/samples-mailheader')));
            }

            // POST /media/upload — upload user image with WebP conversion + thumbnail
            if ($sub === 'upload' && $method === 'POST') {
                respond(uploadUserImage($_FILES, ROOT_DIR));
            }

            // DELETE /media/delete — delete a user-uploaded image
            if ($sub === 'delete' && $method === 'DELETE') {
                $input = getInput();
                respond(deleteUserImage($input['url'] ?? '', ROOT_DIR));
            }

            if ($method === 'GET') {
                // Whitelist of allowed sample folders
                $folderParam  = $_GET['folder'] ?? 'samples';
                $allowedFolders = ['samples', 'mailheader'];
                if (!in_array($folderParam, $allowedFolders, true)) $folderParam = 'samples';
                $folderSlug = $folderParam === 'mailheader' ? 'samples-mailheader' : 'samples';

                $samplePath = ROOT_DIR . '/assets/img/' . $folderSlug;
                $thumbPath  = $samplePath . '/thumbs';
                $urlBase    = 'assets/img/' . $folderSlug;
                $samples    = [];
                if (is_dir($samplePath)) {
                    if ($folderParam === 'mailheader') {
                        // Mail-header images: enumerate by --600.jpg (email-safe JPEG, 600px wide)
                        foreach (glob($samplePath . '/*--600.jpg') as $f) {
                            $base      = preg_replace('/--600$/', '', pathinfo($f, PATHINFO_FILENAME));
                            $thumbFile = $thumbPath . '/' . $base . '--thumb.avif';
                            if (!file_exists($thumbFile)) {
                                $thumbFile = $thumbPath . '/' . $base . '--thumb.webp';
                            }
                            $thumbUrl  = file_exists($thumbFile)
                                ? $urlBase . '/thumbs/' . $base . '--thumb.' . pathinfo($thumbFile, PATHINFO_EXTENSION)
                                : $urlBase . '/' . basename($f);
                            $samples[] = [
                                'name'      => $base,
                                'url'       => $urlBase . '/' . basename($f),
                                'thumb_url' => $thumbUrl,
                            ];
                        }
                    } else {
                        // Section sample images: enumerate by --size-xl AVIF/WebP
                        foreach (glob($samplePath . '/*--size-xl.{avif,webp}', GLOB_BRACE) as $f) {
                            $base    = preg_replace('/--size-xl$/', '', pathinfo($f, PATHINFO_FILENAME));
                            $imgExt  = pathinfo($f, PATHINFO_EXTENSION);
                            // url stored in section data: the --size-m variant (canonical for imgAttrs srcset)
                            $url     = $urlBase . '/thumbs/' . $base . '--size-m.' . $imgExt;
                            $samples[] = [
                                'name'      => $base,
                                'url'       => $url,
                                'thumb_url' => $url,
                            ];
                        }
                        // SVG files are served as-is
                        foreach (glob($samplePath . '/*.svg') as $f) {
                            $name = pathinfo($f, PATHINFO_FILENAME);
                            $samples[] = ['name' => $name, 'url' => $urlBase . '/' . basename($f)];
                        }
                    }
                }
                // Enumerate user uploads (newest first)
                $uploadsDir  = ROOT_DIR . '/uploads/user';
                $uploadsList = [];
                if (is_dir($uploadsDir)) {
                    $uFiles = glob($uploadsDir . '/*--size-m.{avif,webp}', GLOB_BRACE) ?: [];
                    usort($uFiles, fn($a, $b) => filemtime($b) - filemtime($a));
                    foreach ($uFiles as $uf) {
                        if (!preg_match('#([a-f0-9]{32})--size-m\.(avif|webp)$#', basename($uf), $um)) continue;
                        [, $uid, $uext] = $um;
                        $tp = $uploadsDir . '/thumbs/' . $uid . '.webp';
                        $uploadsList[] = [
                            'name'      => $uid,
                            'url'       => 'uploads/user/' . $uid . '--size-m.' . $uext,
                            'thumb_url' => file_exists($tp) ? 'uploads/user/thumbs/' . $uid . '.webp' : null,
                        ];
                    }
                }
                respond(['samples' => $samples, 'uploads' => $uploadsList]);
            }
            respondError('Not found', 404);

        // ── Settings ──────────────────────────────────────────────────────────
        case 'settings':
            require_once API_DIR . '/Settings.php';
            $settings = new Settings(DATA_DIR);
            if ($method === 'GET') { $auth->requireAuth(); respond($settings->get()); }
            if ($method === 'PUT') { $auth->requireAuth(); respond($settings->update(getInput())); }
            respondError('Not found', 404);

        // ── Impressum ─────────────────────────────────────────────────────────
        case 'impressum':
            require_once API_DIR . '/Settings.php';
            $settings = new Settings(DATA_DIR);
            if ($method === 'GET') { $auth->requireAuth(); respond($settings->getImpressum()); }
            if ($method === 'PUT') { $auth->requireAuth(); respond($settings->updateImpressum(getInput())); }
            respondError('Not found', 404);

        // ── Setup ─────────────────────────────────────────────────────────────
        case 'setup':
            require_once API_DIR . '/Settings.php';
            if ($method === 'POST') respond((new Settings(DATA_DIR))->setup(getInput()));
            respondError('Not found', 404);

        // ── Status (API-Key) ──────────────────────────────────────────────────
        case 'status':
            if ($method === 'GET') {
                $auth->requireApiKey();
                require_once API_DIR . '/Settings.php';
                respond((new Settings(DATA_DIR))->getStatus());
            }
            respondError('Not found', 404);

        // ── Remote (API-Key) ──────────────────────────────────────────────────
        case 'remote':
            $auth->requireApiKey();
            require_once API_DIR . '/Settings.php';
            $settings = new Settings(DATA_DIR);
            if ($sub === 'expires' && $method === 'PUT')  respond($settings->updateExpiry(getInput()));
            if ($sub === 'addon'   && $method === 'POST') respond($settings->setAddon(getInput()));
            if ($sub === 'smtp'    && $method === 'PUT')  respond($settings->updateSmtpConfig(getInput()));
            respondError('Not found', 404);

        // ── Update ────────────────────────────────────────────────────────────
        case 'update':
            // Session auth for UI-triggered updates; API-Key also accepted for developer push
            if (!$auth->isAuthenticated()) $auth->requireApiKey();

            require_once API_DIR . '/Updater.php';
            $updater = new Updater(DATA_DIR, ROOT_DIR);

            // GET /update/check – check for available update (disabled on local dev)
            if ($sub === 'check' && $method === 'GET') {
                $host = $_SERVER['HTTP_HOST'] ?? '';
                if (str_ends_with($host, '.local') || $host === 'localhost' || str_starts_with($host, '127.') || str_starts_with($host, '192.168.')) {
                    respond(['available' => false, 'dev_mode' => true]);
                }
                respond($updater->check());
            }

            // GET /update/status – poll update progress
            if ($sub === 'status' && $method === 'GET') {
                respond($updater->getProgress());
            }

            // POST /update/start – begin update (returns immediately, runs in background)
            if ($sub === 'start' && $method === 'POST') {
                $responseBody = json_encode(
                    ['success' => true, 'data' => ['started' => true]],
                    JSON_UNESCAPED_UNICODE
                );
                http_response_code(200);
                header('Content-Type: application/json; charset=utf-8');
                header('Content-Length: ' . strlen($responseBody));
                header('Connection: close');
                if (ob_get_level()) ob_end_clean();
                echo $responseBody;
                flush();

                $updater->start();
                exit;
            }

            // POST /update/rollback – restore previous version from backup
            if ($sub === 'rollback' && $method === 'POST') {
                respond($updater->rollback());
            }

            respondError('Not found', 404);

        default:
            respondError('Not found', 404);
    }

} catch (InvalidArgumentException $e) {
    respondError($e->getMessage(), 400);
} catch (OverflowException $e) {
    respondError($e->getMessage(), 429);
} catch (RuntimeException $e) {
    respondError($e->getMessage(), 404);
} catch (Throwable $e) {
    error_log('[HW API] ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
    respondError('Internal server error', 500);
}

// ── Responsive image helpers ─────────────────────────────────────────────────

function avifSupported(): bool
{
    return function_exists('imageavif');
}

/**
 * Loads a JPEG or PNG image from $src, scales it so the longest edge ≤ $maxEdge,
 * and saves it as AVIF (quality $quality) — or falls back to WebP if AVIF is unavailable.
 * Returns the actual destination path (with .avif or .webp extension).
 * Throws RuntimeException for HEIC or unsupported formats.
 */
function convertToResponsiveImage(string $src, string $destBase, int $maxEdge, int $quality = 80): string
{
    $mime = mime_content_type($src);

    if (in_array($mime, ['image/heic', 'image/heif'], true)) {
        throw new RuntimeException('HEIC-Bilder werden nicht unterstützt. Bitte konvertiere das Bild zuerst zu JPG oder PNG.');
    }

    $img = match($mime) {
        'image/jpeg' => imagecreatefromjpeg($src),
        'image/png'  => imagecreatefrompng($src),
        'image/webp' => imagecreatefromwebp($src),
        'image/avif' => function_exists('imagecreatefromavif') ? imagecreatefromavif($src) : false,
        default      => false,
    };
    if (!$img) throw new RuntimeException("Bildformat nicht unterstützt: $mime");

    [$w, $h] = getimagesize($src);

    if ($w > $maxEdge || $h > $maxEdge) {
        $scale = min($maxEdge / $w, $maxEdge / $h);
        $newW  = (int)round($w * $scale);
        $newH  = (int)round($h * $scale);
        $out   = imagecreatetruecolor($newW, $newH);
        imagealphablending($out, false);
        imagesavealpha($out, true);
        imagecopyresampled($out, $img, 0, 0, 0, 0, $newW, $newH, $w, $h);
        imagedestroy($img);
        $img = $out;
    }

    if (avifSupported()) {
        $dest = $destBase . '.avif';
        imageavif($img, $dest, $quality);
    } else {
        $dest = $destBase . '.webp';
        imagewebp($img, $dest, $quality);
    }
    imagedestroy($img);
    return $dest;
}

// ── Sample thumbnail generator ───────────────────────────────────────────────

/**
 * Generate responsive AVIF/WebP sizes for one image directory.
 * --size-m (920px) and --size-l (1440px) go into $samplePath/thumbs/.
 * --size-xl (2700px) stays in $samplePath/.
 * Originals are deleted after all 3 sizes are created.
 * Returns [count, total, errors].
 */
function generateThumbsForDir(string $samplePath): array
{
    $thumbPath = $samplePath . '/thumbs';
    $count     = 0;
    $errors    = [];

    if (!is_dir($samplePath)) return ['count' => 0, 'total' => 0, 'errors' => []];

    // Clear thumbs dir for fresh regeneration
    if (is_dir($thumbPath)) {
        foreach (glob($thumbPath . '/*') ?: [] as $f) {
            @unlink($f);
        }
    } elseif (!mkdir($thumbPath, 0755, true)) {
        return ['count' => 0, 'total' => 0, 'errors' => ['Cannot create thumbs directory: ' . $thumbPath]];
    }

    // Raw originals (no --size- suffix, any format) → full pipeline: m+l thumbs + xl
    $rawFiles = array_values(array_filter(
        glob($samplePath . '/*.{jpg,jpeg,png,webp,avif}', GLOB_BRACE) ?: [],
        fn($f) => !preg_match('/--size-[a-z]+\./i', basename($f))
    ));

    // Existing --size-xl files → regenerate thumbs only (m+l)
    $xlFiles = glob($samplePath . '/*--size-xl.{avif,webp,jpg,jpeg,png}', GLOB_BRACE) ?: [];

    $total = count($rawFiles) + count($xlFiles);

    foreach ($rawFiles as $file) {
        try {
            $name = pathinfo($file, PATHINFO_FILENAME);
            $mime = mime_content_type($file);

            if (in_array($mime, ['image/heic', 'image/heif'], true)) {
                $errors[] = basename($file) . ': HEIC wird nicht unterstützt – bitte zu JPG/PNG konvertieren.';
                continue;
            }

            $mPath  = convertToResponsiveImage($file, $thumbPath  . '/' . $name . '--size-m',  920,  80);
            $lPath  = convertToResponsiveImage($file, $thumbPath  . '/' . $name . '--size-l',  1440, 80);
            $xlPath = convertToResponsiveImage($file, $samplePath . '/' . $name . '--size-xl', 2700, 80);

            if (file_exists($mPath) && file_exists($lPath) && file_exists($xlPath)) {
                @unlink($file);
            }

            $count++;
        } catch (Throwable $e) {
            $errors[] = basename($file) . ': ' . $e->getMessage();
        }
    }

    foreach ($xlFiles as $file) {
        try {
            $baseName = preg_replace('/--size-xl$/', '', pathinfo($file, PATHINFO_FILENAME));
            convertToResponsiveImage($file, $thumbPath . '/' . $baseName . '--size-m', 920,  80);
            convertToResponsiveImage($file, $thumbPath . '/' . $baseName . '--size-l', 1440, 80);
            $count++;
        } catch (Throwable $e) {
            $errors[] = basename($file) . ': ' . $e->getMessage();
        }
    }

    return ['count' => $count, 'total' => $total, 'errors' => $errors];
}

/**
 * Convert a JPEG/PNG/WebP to a fixed-width JPEG for email compatibility.
 * Width is scaled to $targetWidth; height proportional.
 * Returns actual destination path with .jpg extension.
 */
function convertToJpeg600(string $src, string $destBase, int $targetWidth = 600, int $quality = 85): string
{
    $mime = mime_content_type($src);
    $img  = match($mime) {
        'image/jpeg' => imagecreatefromjpeg($src),
        'image/png'  => imagecreatefrompng($src),
        'image/webp' => imagecreatefromwebp($src),
        default      => throw new RuntimeException("Bildformat nicht unterstützt: $mime"),
    };
    if (!$img) throw new RuntimeException('Bild konnte nicht geladen werden');

    [$origW, $origH] = getimagesize($src);
    $scale   = $targetWidth / $origW;
    $newW    = $targetWidth;
    $newH    = (int)round($origH * $scale);

    $out = imagecreatetruecolor($newW, $newH);
    // Fill white background (PNG transparency → white for JPEG)
    imagefill($out, 0, 0, imagecolorallocate($out, 255, 255, 255));
    imagecopyresampled($out, $img, 0, 0, 0, 0, $newW, $newH, $origW, $origH);
    imagedestroy($img);

    $dest = $destBase . '.jpg';
    imagejpeg($out, $dest, $quality);
    imagedestroy($out);
    return $dest;
}

/**
 * Generate mail-header variants: one 600px JPEG (email-safe) in $samplePath/,
 * one AVIF/WebP thumbnail in $samplePath/thumbs/ for backstage preview.
 * Originals are deleted after both files are created.
 */
function generateMailHeaderThumbs(string $samplePath): array
{
    $thumbsDir = $samplePath . '/thumbs';
    $allowed   = ['jpg', 'jpeg', 'png'];
    $count     = 0;
    $errors    = [];

    if (!is_dir($samplePath)) return ['count' => 0, 'total' => 0, 'errors' => []];

    // Clear thumbs dir for fresh regeneration
    if (is_dir($thumbsDir)) {
        foreach (glob($thumbsDir . '/*') ?: [] as $f) {
            @unlink($f);
        }
    } elseif (!mkdir($thumbsDir, 0755, true)) {
        return ['count' => 0, 'total' => 0, 'errors' => ['Cannot create thumbs directory: ' . $thumbsDir]];
    }

    // Raw originals (no --600 suffix) → full pipeline: --600.jpg + --thumb
    $pattern  = $samplePath . '/*.{' . implode(',', $allowed) . '}';
    $rawFiles = array_values(array_filter(
        glob($pattern, GLOB_BRACE) ?: [],
        fn($f) => !preg_match('/--600\.jpg$/', basename($f))
    ));

    // Existing --600.jpg files → regenerate --thumb only
    $jpg600Files = glob($samplePath . '/*--600.jpg') ?: [];

    $total = count($rawFiles) + count($jpg600Files);

    foreach ($rawFiles as $file) {
        try {
            $name    = pathinfo($file, PATHINFO_FILENAME);
            $mime    = mime_content_type($file);
            if (in_array($mime, ['image/heic', 'image/heif'], true)) {
                $errors[] = basename($file) . ': HEIC wird nicht unterstützt – bitte zu JPG/PNG konvertieren.';
                continue;
            }
            $jpgPath   = convertToJpeg600($file, $samplePath . '/' . $name . '--600');
            $thumbPath = convertToResponsiveImage($file, $thumbsDir . '/' . $name . '--thumb', 300, 80);
            if (file_exists($jpgPath) && file_exists($thumbPath)) {
                @unlink($file);
            }
            $count++;
        } catch (Throwable $e) {
            $errors[] = basename($file) . ': ' . $e->getMessage();
        }
    }

    foreach ($jpg600Files as $file) {
        try {
            $baseName = preg_replace('/--600$/', '', pathinfo($file, PATHINFO_FILENAME));
            convertToResponsiveImage($file, $thumbsDir . '/' . $baseName . '--thumb', 300, 80);
            $count++;
        } catch (Throwable $e) {
            $errors[] = basename($file) . ': ' . $e->getMessage();
        }
    }

    return ['count' => $count, 'total' => $total, 'errors' => $errors];
}

// ── User image upload (3 AVIF/WebP sizes + WebP thumbnail) ──────────────────
function uploadUserImage(array $files, string $rootDir): array
{
    if (!function_exists('imagecreatefromjpeg')) {
        throw new RuntimeException('Image processing not available (GD missing)');
    }
    ini_set('memory_limit', '512M');
    set_time_limit(120);

    $allowed   = ['image/jpeg', 'image/png'];
    $uploadDir = $rootDir . '/uploads/user';
    $thumbDir  = $uploadDir . '/thumbs';
    if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
    if (!is_dir($thumbDir))  mkdir($thumbDir,  0755, true);

    $file = $files['file'] ?? null;
    if (!$file || $file['error'] !== UPLOAD_ERR_OK) throw new RuntimeException('Upload failed');
    if ($file['size'] > 20971520) throw new InvalidArgumentException('File too large');

    $mime = mime_content_type($file['tmp_name']);

    if (in_array($mime, ['image/heic', 'image/heif'], true)) {
        throw new InvalidArgumentException('HEIC-Bilder werden nicht unterstützt. Bitte konvertiere das Bild zuerst zu JPG oder PNG.');
    }
    if (!in_array($mime, $allowed, true)) throw new InvalidArgumentException('Invalid file type');

    $id = bin2hex(random_bytes(16));

    // Generate 3 responsive sizes (AVIF or WebP fallback)
    $mPath  = convertToResponsiveImage($file['tmp_name'], $uploadDir . '/' . $id . '--size-m',  920,  80);
    $lPath  = convertToResponsiveImage($file['tmp_name'], $uploadDir . '/' . $id . '--size-l',  1440, 80);
    $xlPath = convertToResponsiveImage($file['tmp_name'], $uploadDir . '/' . $id . '--size-xl', 2700, 80);

    // Derive the actual extension used (avif or webp)
    $ext = pathinfo($mPath, PATHINFO_EXTENSION);

    // Generate 250×250 WebP thumbnail for the editor media-picker
    $thumbDst = $thumbDir . '/' . $id . '.webp';
    userImageGenerateThumb($file['tmp_name'], $thumbDst, 250);

    return [
        'url'       => 'uploads/user/' . $id . '--size-m.' . $ext,
        'url_l'     => 'uploads/user/' . $id . '--size-l.'  . $ext,
        'url_xl'    => 'uploads/user/' . $id . '--size-xl.' . $ext,
        'thumb_url' => 'uploads/user/thumbs/' . $id . '.webp',
    ];
}

function userImageGenerateThumb(string $src, string $dest, int $size): void
{
    $mime = mime_content_type($src);
    $img  = match($mime) {
        'image/jpeg' => imagecreatefromjpeg($src),
        'image/png'  => imagecreatefrompng($src),
        default      => false,
    };
    if (!$img) throw new RuntimeException('Could not read image for thumbnail');

    [$w, $h] = getimagesize($src);

    // Pre-scale to max 1000px before crop to save memory
    if ($w > 1000 || $h > 1000) {
        $preScale = min(1000 / $w, 1000 / $h);
        $preW     = (int)round($w * $preScale);
        $preH     = (int)round($h * $preScale);
        $pre      = imagescale($img, $preW, $preH, IMG_BILINEAR_FIXED);
        imagedestroy($img);
        $img = $pre;
        $w   = $preW;
        $h   = $preH;
    }

    $ratio   = max($size / $w, $size / $h);
    $newW    = (int)round($w * $ratio);
    $newH    = (int)round($h * $ratio);

    $resized = imagecreatetruecolor($newW, $newH);
    imagealphablending($resized, false);
    imagesavealpha($resized, true);
    imagecopyresampled($resized, $img, 0, 0, 0, 0, $newW, $newH, $w, $h);
    imagedestroy($img);

    $thumb = imagecreatetruecolor($size, $size);
    imagealphablending($thumb, false);
    imagesavealpha($thumb, true);
    $x = (int)(($newW - $size) / 2);
    $y = (int)(($newH - $size) / 2);
    imagecopy($thumb, $resized, 0, 0, $x, $y, $size, $size);
    imagedestroy($resized);

    imagewebp($thumb, $dest, 80);
    imagedestroy($thumb);
}

function deleteUserImage(string $url, string $rootDir): array
{
    // Strictly allow only uploads/user/ paths — prevent traversal
    $url = ltrim($url, '/');
    if (!preg_match('#^uploads/user/[a-f0-9]{32}--size-m\.(avif|webp)$#', $url)) {
        throw new InvalidArgumentException('Invalid file path');
    }
    // Derive base id and extension from the medium URL
    preg_match('#^uploads/user/([a-f0-9]{32})--size-m\.(avif|webp)$#', $url, $m);
    $id  = $m[1];
    $ext = $m[2];

    foreach (['--size-m', '--size-l', '--size-xl'] as $suffix) {
        $path = $rootDir . '/uploads/user/' . $id . $suffix . '.' . $ext;
        if (file_exists($path)) unlink($path);
    }
    $thumbFile = $rootDir . '/uploads/user/thumbs/' . $id . '.webp';
    if (file_exists($thumbFile)) unlink($thumbFile);

    return ['deleted' => true];
}

// ── Section image upload helper ───────────────────────────────────────────────
function uploadSectionImage(array $files, string $rootDir): array
{
    $allowed   = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    $uploadDir = $rootDir . '/uploads/sections';
    if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);

    $file = $files['file'] ?? null;
    if (!$file || $file['error'] !== UPLOAD_ERR_OK) throw new RuntimeException('Upload failed');
    if ($file['size'] > 20971520) throw new InvalidArgumentException('File too large');

    $mime = mime_content_type($file['tmp_name']);
    if (!in_array($mime, $allowed, true)) throw new InvalidArgumentException('Invalid file type');

    $ext  = match($mime) { 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp', default => 'jpg' };
    $name = bin2hex(random_bytes(12)) . '.' . $ext;
    $dest = $uploadDir . '/' . $name;

    if (!move_uploaded_file($file['tmp_name'], $dest)) throw new RuntimeException('Could not save file');

    return ['url' => 'uploads/sections/' . $name, 'name' => $name];
}
