<?php
// Shared helper functions for the FlixCooks food blog.

if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

require_once __DIR__ . '/config.php';

function handle_database_unavailable(DatabaseUnavailableException $e): void {
    http_response_code(503);
    $dbError = $e->getMessage();
    include __DIR__ . '/maintenance/db-unavailable.php';
    exit;
}

function db_table_has_column(PDO $pdo, string $table, string $column): bool {
    $stmt = $pdo->prepare(
        'SELECT 1 FROM information_schema.columns
         WHERE table_schema = current_schema() AND table_name = ? AND column_name = ?'
    );
    $stmt->execute([$table, $column]);
    return (bool) $stmt->fetchColumn();
}

function apply_recipe_schema(PDO $pdo): void {
    $path = __DIR__ . '/scripts/schema.sql';
    if (!file_exists($path)) {
        throw new RuntimeException('Missing scripts/schema.sql');
    }
    $pdo->exec(file_get_contents($path));
}

function recipe_empty_i18n(): array {
    return [
        'title' => '',
        'description' => '',
        'category' => '',
        'difficulty' => '',
        'tags' => [],
        'ingredients' => [],
        'utensils' => [],
        'steps' => [],
        'step_videos' => [],
        'step_timers' => [],
    ];
}

function recipe_base_from_row(array $row): array {
    return [
        'slug' => $row['slug'],
        'hero' => $row['hero'] ?? '',
        'prep_time' => (int) ($row['prep_time'] ?? 0),
        'cook_time' => (int) ($row['cook_time'] ?? 0),
        'total_time' => (int) ($row['total_time'] ?? 0),
        'servings' => (int) ($row['servings'] ?? 2),
        'featured' => (bool) ($row['featured'] ?? false),
        'coming_soon' => (bool) ($row['coming_soon'] ?? false),
        'nutrition' => [
            'calories' => (int) ($row['calories'] ?? 0),
            'protein' => (int) ($row['protein'] ?? 0),
            'carbs' => (int) ($row['carbs'] ?? 0),
            'fat' => (int) ($row['fat'] ?? 0),
        ],
        'i18n' => [
            'en' => recipe_empty_i18n(),
            'de' => recipe_empty_i18n(),
        ],
    ];
}

function decode_legacy_jsonb_value($value): ?array {
    if (is_array($value)) {
        return $value;
    }
    if (is_string($value)) {
        $decoded = json_decode($value, true);
        return is_array($decoded) ? $decoded : null;
    }
    if ($value !== null) {
        $decoded = json_decode(json_encode($value), true);
        return is_array($decoded) ? $decoded : null;
    }
    return null;
}

function migrate_legacy_jsonb_storage(PDO $pdo): void {
    if (!db_table_has_column($pdo, 'recipes', 'data')) {
        return;
    }

    $legacy = [];
    $stmt = $pdo->query('SELECT slug, data FROM recipes');
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $recipe = decode_legacy_jsonb_value($row['data']);
        if (is_array($recipe) && !empty($recipe['slug'])) {
            $legacy[] = $recipe;
        }
    }

    $pdo->exec('DROP TABLE IF EXISTS recipe_steps CASCADE');
    $pdo->exec('DROP TABLE IF EXISTS recipe_utensils CASCADE');
    $pdo->exec('DROP TABLE IF EXISTS recipe_ingredients CASCADE');
    $pdo->exec('DROP TABLE IF EXISTS recipe_tags CASCADE');
    $pdo->exec('DROP TABLE IF EXISTS recipe_translations CASCADE');
    $pdo->exec('DROP TABLE IF EXISTS recipes CASCADE');

    apply_recipe_schema($pdo);

    foreach ($legacy as $recipe) {
        save_recipe($recipe, $pdo);
    }
}

function ensure_recipe_schema(PDO $pdo): void {
    static $ready = false;
    if ($ready) {
        return;
    }

    if (db_table_has_column($pdo, 'recipes', 'data') && !db_table_has_column($pdo, 'recipes', 'calories')) {
        migrate_legacy_jsonb_storage($pdo);
        $ready = true;
        return;
    }

    apply_recipe_schema($pdo);
    $ready = true;
}

function require_database(): PDO {
    $pdo = get_db_connection();
    if (!$pdo) {
        $message = getenv('DATABASE_URL')
            ? 'Database connection failed. Check DATABASE_URL and that Postgres is running.'
            : 'DATABASE_URL is not set in .env.';
        throw new DatabaseUnavailableException($message);
    }

    ensure_recipe_schema($pdo);
    return $pdo;
}

function init_db(): void {
    require_database();
}

function hydrate_recipes_from_db(PDO $pdo): array {
    $recipes = [];
    $stmt = $pdo->query(
        'SELECT * FROM recipes ORDER BY featured DESC, updated_at DESC, slug ASC'
    );
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $recipes[$row['slug']] = recipe_base_from_row($row);
    }

    if ($recipes === []) {
        return [];
    }

    $stmt = $pdo->query('SELECT * FROM recipe_translations');
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        $slug = $row['recipe_slug'];
        $lang = $row['lang'];
        if (!isset($recipes[$slug])) {
            continue;
        }
        $recipes[$slug]['i18n'][$lang] = array_merge(recipe_empty_i18n(), [
            'title' => $row['title'],
            'description' => $row['description'],
            'category' => $row['category'],
            'difficulty' => $row['difficulty'],
        ]);
    }

    $stmt = $pdo->query('SELECT recipe_slug, lang, tag FROM recipe_tags ORDER BY sort_order, id');
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        if (isset($recipes[$row['recipe_slug']]['i18n'][$row['lang']])) {
            $recipes[$row['recipe_slug']]['i18n'][$row['lang']]['tags'][] = $row['tag'];
        }
    }

    $stmt = $pdo->query('SELECT recipe_slug, lang, content FROM recipe_ingredients ORDER BY sort_order, id');
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        if (isset($recipes[$row['recipe_slug']]['i18n'][$row['lang']])) {
            $recipes[$row['recipe_slug']]['i18n'][$row['lang']]['ingredients'][] = $row['content'];
        }
    }

    $stmt = $pdo->query('SELECT recipe_slug, lang, content FROM recipe_utensils ORDER BY sort_order, id');
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        if (isset($recipes[$row['recipe_slug']]['i18n'][$row['lang']])) {
            $recipes[$row['recipe_slug']]['i18n'][$row['lang']]['utensils'][] = $row['content'];
        }
    }

    $stmt = $pdo->query(
        'SELECT recipe_slug, lang, content, video_url, timer_minutes
         FROM recipe_steps ORDER BY sort_order, id'
    );
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        if (!isset($recipes[$row['recipe_slug']]['i18n'][$row['lang']])) {
            continue;
        }
        $lang = &$recipes[$row['recipe_slug']]['i18n'][$row['lang']];
        $lang['steps'][] = $row['content'];
        $lang['step_videos'][] = $row['video_url'] ?? '';
        $lang['step_timers'][] = $row['timer_minutes'] !== null ? (string) $row['timer_minutes'] : '';
        unset($lang);
    }

    $list = array_values($recipes);
    foreach ($list as &$recipe) {
        if (!empty($recipe['hero']) && is_string($recipe['hero'])) {
            $recipe['hero'] = normalize_asset_path($recipe['hero']);
        }
    }
    unset($recipe);

    return $list;
}

function load_recipes(): array {
    $pdo = require_database();
    return hydrate_recipes_from_db($pdo);
}

function load_recipe_by_slug(string $slug): ?array {
    foreach (load_recipes() as $recipe) {
        if (($recipe['slug'] ?? '') === $slug) {
            return $recipe;
        }
    }
    return null;
}

function clear_featured_recipes(?PDO $pdo = null): void {
    $pdo = $pdo ?? require_database();
    $pdo->exec('UPDATE recipes SET featured = FALSE');
}

function delete_recipe(string $slug): bool {
    $pdo = require_database();
    $stmt = $pdo->prepare('DELETE FROM recipes WHERE slug = ?');
    $stmt->execute([$slug]);
    return $stmt->rowCount() > 0;
}

function save_recipe(array $recipe, ?PDO $pdo = null): bool {
    if (empty($recipe['slug'])) {
        return false;
    }

    $pdo = $pdo ?? require_database();
    $nutrition = $recipe['nutrition'] ?? [];
    $slug = $recipe['slug'];

    try {
        $pdo->beginTransaction();

        if (!empty($recipe['featured'])) {
            clear_featured_recipes($pdo);
        }

        $stmt = $pdo->prepare(
            'INSERT INTO recipes (
                slug, hero, prep_time, cook_time, total_time, servings,
                featured, coming_soon, calories, protein, carbs, fat, updated_at
            ) VALUES (
                :slug, :hero, :prep_time, :cook_time, :total_time, :servings,
                :featured, :coming_soon, :calories, :protein, :carbs, :fat, NOW()
            )
            ON CONFLICT (slug) DO UPDATE SET
                hero = EXCLUDED.hero,
                prep_time = EXCLUDED.prep_time,
                cook_time = EXCLUDED.cook_time,
                total_time = EXCLUDED.total_time,
                servings = EXCLUDED.servings,
                featured = EXCLUDED.featured,
                coming_soon = EXCLUDED.coming_soon,
                calories = EXCLUDED.calories,
                protein = EXCLUDED.protein,
                carbs = EXCLUDED.carbs,
                fat = EXCLUDED.fat,
                updated_at = NOW()'
        );
        $stmt->bindValue(':slug', $slug);
        $stmt->bindValue(':hero', $recipe['hero'] ?? '');
        $stmt->bindValue(':prep_time', (int) ($recipe['prep_time'] ?? 0), PDO::PARAM_INT);
        $stmt->bindValue(':cook_time', (int) ($recipe['cook_time'] ?? 0), PDO::PARAM_INT);
        $stmt->bindValue(':total_time', (int) ($recipe['total_time'] ?? 0), PDO::PARAM_INT);
        $stmt->bindValue(':servings', (int) ($recipe['servings'] ?? 2), PDO::PARAM_INT);
        $stmt->bindValue(':featured', !empty($recipe['featured']), PDO::PARAM_BOOL);
        $stmt->bindValue(':coming_soon', !empty($recipe['coming_soon']), PDO::PARAM_BOOL);
        $stmt->bindValue(':calories', (int) ($nutrition['calories'] ?? 0), PDO::PARAM_INT);
        $stmt->bindValue(':protein', (int) ($nutrition['protein'] ?? 0), PDO::PARAM_INT);
        $stmt->bindValue(':carbs', (int) ($nutrition['carbs'] ?? 0), PDO::PARAM_INT);
        $stmt->bindValue(':fat', (int) ($nutrition['fat'] ?? 0), PDO::PARAM_INT);
        $stmt->execute();

        $pdo->prepare('DELETE FROM recipe_translations WHERE recipe_slug = ?')->execute([$slug]);
        $pdo->prepare('DELETE FROM recipe_tags WHERE recipe_slug = ?')->execute([$slug]);
        $pdo->prepare('DELETE FROM recipe_ingredients WHERE recipe_slug = ?')->execute([$slug]);
        $pdo->prepare('DELETE FROM recipe_utensils WHERE recipe_slug = ?')->execute([$slug]);
        $pdo->prepare('DELETE FROM recipe_steps WHERE recipe_slug = ?')->execute([$slug]);

        $translationStmt = $pdo->prepare(
            'INSERT INTO recipe_translations (recipe_slug, lang, title, description, category, difficulty)
             VALUES (?, ?, ?, ?, ?, ?)'
        );
        $tagStmt = $pdo->prepare(
            'INSERT INTO recipe_tags (recipe_slug, lang, tag, sort_order) VALUES (?, ?, ?, ?)'
        );
        $ingredientStmt = $pdo->prepare(
            'INSERT INTO recipe_ingredients (recipe_slug, lang, content, sort_order) VALUES (?, ?, ?, ?)'
        );
        $utensilStmt = $pdo->prepare(
            'INSERT INTO recipe_utensils (recipe_slug, lang, content, sort_order) VALUES (?, ?, ?, ?)'
        );
        $stepStmt = $pdo->prepare(
            'INSERT INTO recipe_steps (recipe_slug, lang, content, video_url, timer_minutes, sort_order)
             VALUES (?, ?, ?, ?, ?, ?)'
        );

        foreach (['en', 'de'] as $lang) {
            $block = $recipe['i18n'][$lang] ?? [];
            $translationStmt->execute([
                $slug,
                $lang,
                $block['title'] ?? '',
                $block['description'] ?? '',
                $block['category'] ?? '',
                $block['difficulty'] ?? '',
            ]);

            $tagOrder = 0;
            foreach (array_values($block['tags'] ?? []) as $tag) {
                $tag = trim((string) $tag);
                if ($tag !== '') {
                    $tagStmt->execute([$slug, $lang, $tag, $tagOrder++]);
                }
            }

            $ingredientOrder = 0;
            foreach (array_values($block['ingredients'] ?? []) as $line) {
                $line = trim((string) $line);
                if ($line !== '') {
                    $ingredientStmt->execute([$slug, $lang, $line, $ingredientOrder++]);
                }
            }

            $utensilOrder = 0;
            foreach (array_values($block['utensils'] ?? []) as $line) {
                $line = trim((string) $line);
                if ($line !== '') {
                    $utensilStmt->execute([$slug, $lang, $line, $utensilOrder++]);
                }
            }

            $steps = array_values($block['steps'] ?? []);
            $videos = array_values($block['step_videos'] ?? []);
            $timers = array_values($block['step_timers'] ?? []);
            $stepOrder = 0;
            foreach ($steps as $i => $step) {
                $step = trim((string) $step);
                if ($step === '') {
                    continue;
                }
                $video = trim((string) ($videos[$i] ?? ''));
                $timerRaw = $timers[$i] ?? '';
                $timerMinutes = ($timerRaw !== '' && is_numeric($timerRaw)) ? (int) $timerRaw : null;
                $stepStmt->execute([$slug, $lang, $step, $video, $timerMinutes, $stepOrder++]);
            }
        }

        $pdo->commit();
        return true;
    } catch (\Exception $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        error_log('save_recipe failed: ' . $e->getMessage());
        return false;
    }
}

/**
 * Default settings for imprint and privacy pages.
 */
function default_site_settings(): array {
    $today = date('Y-m-d');
    return [
        'imprint' => [
            'de' => [
                'owner_name' => 'FlixCooks (bitte anpassen)',
                'address' => 'Straße Hausnummer, PLZ Ort, Österreich',
                'email' => 'contact@flixcooks.at',
                'phone' => '+43 660 0000000',
                'legal_form' => 'Kleines Impressum; kein Firmenbucheintrag/UID hinterlegt.',
                'business_purpose' => 'Foodblog & Rezeptmarketing.',
                'wko_membership' => '',
                'authority' => '',
                'uid' => '',
                'odr' => 'https://ec.europa.eu/consumers/odr',
                'last_updated' => $today,
            ],
            'en' => [
                'owner_name' => 'FlixCooks (please update)',
                'address' => 'Street number, ZIP City, Austria',
                'email' => 'contact@flixcooks.at',
                'phone' => '+43 660 0000000',
                'legal_form' => 'Small website notice; no commercial register/UID provided.',
                'business_purpose' => 'Food blog & recipe marketing.',
                'wko_membership' => '',
                'authority' => '',
                'uid' => '',
                'odr' => 'https://consumer-redress.ec.europa.eu/',
                'last_updated' => $today,
            ],
        ],
        'privacy' => [
            'de' => [
                'controller' => 'FlixCooks (bitte anpassen)',
                'contact_email' => 'contact@flixcooks.at',
                'contact_phone' => '+43 660 0000000',
                'address' => 'Straße Hausnummer, PLZ Ort, Österreich',
                'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Deutschland',
                'log_retention_days' => 30,
                'cookie_statement' => 'Keine Tracking- oder Marketing-Cookies; nur technisch notwendige.',
                'purposes' => 'Betrieb und Bereitstellung der Website, Beantwortung von Kontaktanfragen.',
                'last_updated' => $today,
            ],
            'en' => [
                'controller' => 'FlixCooks (please update)',
                'contact_email' => 'contact@flixcooks.at',
                'contact_phone' => '+43 660 0000000',
                'address' => 'Street number, ZIP City, Austria',
                'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany',
                'log_retention_days' => 30,
                'cookie_statement' => 'No tracking or marketing cookies; only technically necessary cookies.',
                'purposes' => 'Operating and providing the website, responding to contact requests.',
                'last_updated' => $today,
            ],
        ],
    ];
}

/**
 * Load site-wide settings for legal pages, merging with defaults.
 */
function load_site_settings(): array {
    $defaults = default_site_settings();
    $path = __DIR__ . '/data/site.json';
    if (!file_exists($path)) {
        return $defaults;
    }
    $raw = file_get_contents($path);
    $data = json_decode($raw, true);
    if (!is_array($data)) {
        return $defaults;
    }
    return array_replace_recursive($defaults, $data);
}

/**
 * Persist site-wide settings for legal pages.
 */
function save_site_settings(array $settings): bool {
    $path = __DIR__ . '/data/site.json';
    $json = json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    return (bool) file_put_contents($path, $json);
}

function slugify(string $text): string {
    $text = strtolower($text);
    $text = preg_replace('~[^a-z0-9]+~', '-', $text);
    $text = trim($text, '-');
    return $text ?: uniqid('recipe-');
}

function find_recipe_by_slug(array $recipes, string $slug): ?array {
    foreach ($recipes as $recipe) {
        if (($recipe['slug'] ?? '') === $slug) {
            return $recipe;
        }
    }
    return null;
}

function localize_recipe(array $recipe, string $lang): array {
    $localized = $recipe;
    $i18n = $recipe['i18n'][$lang] ?? ($recipe['i18n']['en'] ?? []);
    foreach ($i18n as $key => $value) {
        $localized[$key] = $value;
    }
    return $localized;
}

function localize_recipes(array $recipes, string $lang): array {
    return array_map(function ($recipe) use ($lang) {
        return localize_recipe($recipe, $lang);
    }, $recipes);
}

function filter_recipes(array $recipes, ?string $query = null, ?string $tag = null, string $lang = 'en'): array {
    $query = $query ? strtolower($query) : null;
    $tag = $tag ? strtolower($tag) : null;

    $localized = localize_recipes($recipes, $lang);

    return array_values(array_filter($localized, function ($recipe) use ($query, $tag) {
        $matchesQuery = true;
        if ($query) {
            $haystack = strtolower(($recipe['title'] ?? '') . ' ' . ($recipe['description'] ?? ''));
            $matchesQuery = strpos($haystack, $query) !== false;
        }

        $matchesTag = true;
        if ($tag) {
            $tags = array_map('strtolower', $recipe['tags'] ?? []);
            $matchesTag = in_array($tag, $tags, true);
        }

        return $matchesQuery && $matchesTag;
    }));
}

function format_minutes(int $minutes): string {
    if ($minutes < 60) {
        return $minutes . ' min';
    }
    $hours = intdiv($minutes, 60);
    $mins = $minutes % 60;
    return $hours . ' hr' . ($hours > 1 ? 's ' : ' ') . ($mins ? $mins . ' min' : '');
}

function e(?string $value): string {
    return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}

function normalize_asset_path(string $path): string {
    if (!str_starts_with($path, '/assets/')) {
        return $path;
    }

    $absolute = __DIR__ . $path;
    if (file_exists($absolute)) {
        return $path;
    }

    $dir = dirname($absolute);
    $target = basename($absolute);
    if (!is_dir($dir)) {
        return $path;
    }

    foreach (scandir($dir) ?: [] as $entry) {
        if (strcasecmp($entry, $target) === 0) {
            return rtrim(dirname($path), '/') . '/' . $entry;
        }
    }

    return $path;
}

/**
 * Build a URL to the current path with a specific language parameter, preserving other query params.
 */
function lang_url(string $lang): string {
    $params = $_GET;
    $params['lang'] = $lang;
    $path = strtok($_SERVER['REQUEST_URI'], '?') ?: '/';
    return $path . '?' . http_build_query($params);
}

// Fallback polyfills for environments without mbstring extension
if (!function_exists('mb_substr')) {
    function mb_substr(string $string, int $start, ?int $length = null, ?string $encoding = null): string {
        return substr($string, $start, $length ?? strlen($string));
    }
}

if (!function_exists('mb_strlen')) {
    function mb_strlen(string $string, ?string $encoding = null): int {
        return strlen($string);
    }
}
